zebra_rpc/
methods.rs

1//! Zebra supported RPC methods.
2//!
3//! Based on the [`zcashd` RPC methods](https://zcash.github.io/rpc/)
4//! as used by `lightwalletd.`
5//!
6//! Some parts of the `zcashd` RPC documentation are outdated.
7//! So this implementation follows the `zcashd` server and `lightwalletd` client implementations.
8//!
9//! # Developing this module
10//!
11//! If RPCs are added or changed, ensure the following:
12//!
13//! - Request types can be instantiated from dependent crates, and
14//!   response types are fully-readable (up to each leaf component), meaning
15//!   every field on response types can be read, and any types used in response
16//!   types has an appropriate API for either directly accessing their fields, or
17//!   has an appropriate API for accessing any relevant data.
18//!
19//!   This should be achieved, wherever possible, by:
20//!   - Using `derive(Getters, new)` to keep new code succinct and consistent.
21//!     Ensure that fields on response types that implement `Copy` are tagged
22//!     with `#[getter(copy)]` field attributes to avoid unnecessary references.
23//!     This should be easily noticeable in the `serialization_tests` test crate, where
24//!     any fields implementing `Copy` but not tagged with `#[getter(Copy)]` will
25//!     be returned by reference, and will require dereferencing with the dereference
26//!     operator, `*`. If a value returned by a getter method requires dereferencing,
27//!     the associated field in the response type should likely be tagged with `#[getter(Copy)]`.
28//!   - If a field is added, use `#[new(...)]` so that it's not added to the
29//!     constructor. If that is unavoidable, then it will require a major
30//!     version bump.
31//!
32//! - A test has been added to the `serialization_tests` test crate to ensure the above.
33
34use std::{
35    cmp,
36    collections::{HashMap, HashSet},
37    fmt,
38    ops::RangeInclusive,
39    sync::Arc,
40    time::Duration,
41};
42
43use chrono::Utc;
44use derive_getters::Getters;
45use derive_new::new;
46use futures::{future::OptionFuture, stream::FuturesOrdered, StreamExt, TryFutureExt};
47use hex::{FromHex, ToHex};
48use indexmap::IndexMap;
49use jsonrpsee::core::{async_trait, RpcResult as Result};
50use jsonrpsee_proc_macros::rpc;
51use jsonrpsee_types::{ErrorCode, ErrorObject};
52use rand::{rngs::OsRng, RngCore};
53use tokio::{
54    sync::{broadcast, mpsc, watch},
55    task::JoinHandle,
56};
57use tower::{Service, ServiceExt};
58use tracing::Instrument;
59
60use zcash_address::{unified::Encoding, TryFromAddress};
61use zcash_primitives::consensus::Parameters;
62
63use zebra_chain::{
64    amount::{self, Amount, NegativeAllowed, NonNegative},
65    block::{self, Block, Commitment, Height, SerializedBlock, TryIntoHeight},
66    chain_sync_status::ChainSyncStatus,
67    chain_tip::{ChainTip, NetworkChainTipHeightEstimator},
68    parameters::{
69        subsidy::{
70            block_subsidy, funding_stream_values, miner_subsidy, FundingStreamReceiver,
71            ParameterSubsidy,
72        },
73        ConsensusBranchId, Network, NetworkUpgrade, POW_AVERAGING_WINDOW,
74    },
75    serialization::{BytesInDisplayOrder, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
76    subtree::NoteCommitmentSubtreeIndex,
77    transaction::{self, SerializedTransaction, Transaction, UnminedTx},
78    transparent::{self, Address, OutputIndex},
79    value_balance::ValueBalance,
80    work::{
81        difficulty::{CompactDifficulty, ExpandedDifficulty, ParameterDifficulty, U256},
82        equihash::Solution,
83    },
84};
85use zebra_consensus::{funding_stream_address, RouterError};
86use zebra_network::{address_book_peers::AddressBookPeers, types::PeerServices, PeerSocketAddr};
87use zebra_node_services::mempool;
88use zebra_state::{
89    AnyTx, HashOrHeight, OutputLocation, ReadRequest, ReadResponse, TransactionLocation,
90};
91
92use crate::{
93    client::Treestate,
94    config,
95    methods::types::{validate_address::validate_address, z_validate_address::z_validate_address},
96    queue::Queue,
97    server::{
98        self,
99        error::{MapError, OkOrError},
100    },
101};
102
103pub(crate) mod hex_data;
104pub(crate) mod trees;
105pub(crate) mod types;
106
107use hex_data::HexData;
108use trees::{GetSubtreesByIndexResponse, GetTreestateResponse, SubtreeRpcData};
109use types::{
110    get_block_template::{
111        constants::{
112            DEFAULT_SOLUTION_RATE_WINDOW_SIZE, MEMPOOL_LONG_POLL_INTERVAL,
113            ZCASHD_FUNDING_STREAM_ORDER,
114        },
115        proposal::proposal_block_from_template,
116        BlockTemplateResponse, BlockTemplateTimeSource, GetBlockTemplateHandler,
117        GetBlockTemplateParameters, GetBlockTemplateResponse,
118    },
119    get_blockchain_info::GetBlockchainInfoBalance,
120    get_mempool_info::GetMempoolInfoResponse,
121    get_mining_info::GetMiningInfoResponse,
122    get_raw_mempool::{self, GetRawMempoolResponse},
123    long_poll::LongPollInput,
124    network_info::{GetNetworkInfoResponse, NetworkInfo},
125    peer_info::PeerInfo,
126    submit_block::{SubmitBlockErrorResponse, SubmitBlockParameters, SubmitBlockResponse},
127    subsidy::GetBlockSubsidyResponse,
128    transaction::TransactionObject,
129    unified_address::ZListUnifiedReceiversResponse,
130    validate_address::ValidateAddressResponse,
131    z_validate_address::ZValidateAddressResponse,
132};
133
134#[cfg(test)]
135mod tests;
136
137#[rpc(server)]
138/// RPC method signatures.
139pub trait Rpc {
140    /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct.
141    ///
142    /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html)
143    /// method: post
144    /// tags: control
145    ///
146    /// # Notes
147    ///
148    /// [The zcashd reference](https://zcash.github.io/rpc/getinfo.html) might not show some fields
149    /// in Zebra's [`GetInfo`]. Zebra uses the field names and formats from the
150    /// [zcashd code](https://github.com/zcash/zcash/blob/v4.6.0-1/src/rpc/misc.cpp#L86-L87).
151    ///
152    /// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields
153    /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95)
154    #[method(name = "getinfo")]
155    async fn get_info(&self) -> Result<GetInfoResponse>;
156
157    /// Returns blockchain state information, as a [`GetBlockchainInfoResponse`] JSON struct.
158    ///
159    /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html)
160    /// method: post
161    /// tags: blockchain
162    ///
163    /// # Notes
164    ///
165    /// Some fields from the zcashd reference are missing from Zebra's [`GetBlockchainInfoResponse`]. It only contains the fields
166    /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89)
167    #[method(name = "getblockchaininfo")]
168    async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse>;
169
170    /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance.
171    ///
172    /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html)
173    /// method: post
174    /// tags: address
175    ///
176    /// # Parameters
177    ///
178    /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry
179    ///     - `addresses`: (array of strings) A list of base-58 encoded addresses.
180    ///
181    /// # Notes
182    ///
183    /// zcashd also accepts a single string parameter instead of an array of strings, but Zebra
184    /// doesn't because lightwalletd always calls this RPC with an array of addresses.
185    ///
186    /// zcashd also returns the total amount of Zatoshis received by the addresses, but Zebra
187    /// doesn't because lightwalletd doesn't use that information.
188    ///
189    /// The RPC documentation says that the returned object has a string `balance` field, but
190    /// zcashd actually [returns an
191    /// integer](https://github.com/zcash/lightwalletd/blob/bdaac63f3ee0dbef62bde04f6817a9f90d483b00/common/common.go#L128-L130).
192    #[method(name = "getaddressbalance")]
193    async fn get_address_balance(
194        &self,
195        address_strings: GetAddressBalanceRequest,
196    ) -> Result<GetAddressBalanceResponse>;
197
198    /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid.
199    /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string.
200    ///
201    /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html)
202    /// method: post
203    /// tags: transaction
204    ///
205    /// # Parameters
206    ///
207    /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes.
208    /// - `allow_high_fees`: (bool, optional) A legacy parameter accepted by zcashd but ignored by Zebra.
209    ///
210    /// # Notes
211    ///
212    /// zcashd accepts an optional `allowhighfees` parameter. Zebra doesn't support this parameter,
213    /// because lightwalletd doesn't use it.
214    #[method(name = "sendrawtransaction")]
215    async fn send_raw_transaction(
216        &self,
217        raw_transaction_hex: String,
218        _allow_high_fees: Option<bool>,
219    ) -> Result<SendRawTransactionResponse>;
220
221    /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
222    /// If the block is not in Zebra's state, returns
223    /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) if a height was
224    /// passed or -5 if a hash was passed.
225    ///
226    /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
227    /// method: post
228    /// tags: blockchain
229    ///
230    /// # Parameters
231    ///
232    /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
233    /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data.
234    ///
235    /// # Notes
236    ///
237    /// The `size` field is only returned with verbosity=2.
238    ///
239    /// The undocumented `chainwork` field is not returned.
240    #[method(name = "getblock")]
241    async fn get_block(
242        &self,
243        hash_or_height: String,
244        verbosity: Option<u8>,
245    ) -> Result<GetBlockResponse>;
246
247    /// Returns the requested block header by hash or height, as a [`GetBlockHeader`] JSON string.
248    /// If the block is not in Zebra's state,
249    /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
250    /// if a height was passed or -5 if a hash was passed.
251    ///
252    /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html)
253    /// method: post
254    /// tags: blockchain
255    ///
256    /// # Parameters
257    ///
258    /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
259    /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, true for a json object
260    ///
261    /// # Notes
262    ///
263    /// The undocumented `chainwork` field is not returned.
264    #[method(name = "getblockheader")]
265    async fn get_block_header(
266        &self,
267        hash_or_height: String,
268        verbose: Option<bool>,
269    ) -> Result<GetBlockHeaderResponse>;
270
271    /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
272    ///
273    /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
274    /// method: post
275    /// tags: blockchain
276    #[method(name = "getbestblockhash")]
277    fn get_best_block_hash(&self) -> Result<GetBlockHashResponse>;
278
279    /// Returns the height and hash of the current best blockchain tip block, as a [`GetBlockHeightAndHashResponse`] JSON struct.
280    ///
281    /// zcashd reference: none
282    /// method: post
283    /// tags: blockchain
284    #[method(name = "getbestblockheightandhash")]
285    fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse>;
286
287    /// Returns details on the active state of the TX memory pool.
288    ///
289    /// zcash reference: [`getmempoolinfo`](https://zcash.github.io/rpc/getmempoolinfo.html)
290    #[method(name = "getmempoolinfo")]
291    async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse>;
292
293    /// Returns all transaction ids in the memory pool, as a JSON array.
294    ///
295    /// # Parameters
296    ///
297    /// - `verbose`: (boolean, optional, default=false) true for a json object, false for array of transaction ids.
298    ///
299    /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html)
300    /// method: post
301    /// tags: blockchain
302    #[method(name = "getrawmempool")]
303    async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse>;
304
305    /// Returns information about the given block's Sapling & Orchard tree state.
306    ///
307    /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html)
308    /// method: post
309    /// tags: blockchain
310    ///
311    /// # Parameters
312    ///
313    /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height.
314    ///
315    /// # Notes
316    ///
317    /// The zcashd doc reference above says that the parameter "`height` can be
318    /// negative where -1 is the last known valid block". On the other hand,
319    /// `lightwalletd` only uses positive heights, so Zebra does not support
320    /// negative heights.
321    #[method(name = "z_gettreestate")]
322    async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse>;
323
324    /// Returns information about a range of Sapling or Orchard subtrees.
325    ///
326    /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link
327    /// method: post
328    /// tags: blockchain
329    ///
330    /// # Parameters
331    ///
332    /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard".
333    /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return.
334    /// - `limit`: (number, optional) The maximum number of subtree values to return.
335    ///
336    /// # Notes
337    ///
338    /// While Zebra is doing its initial subtree index rebuild, subtrees will become available
339    /// starting at the chain tip. This RPC will return an empty list if the `start_index` subtree
340    /// exists, but has not been rebuilt yet. This matches `zcashd`'s behaviour when subtrees aren't
341    /// available yet. (But `zcashd` does its rebuild before syncing any blocks.)
342    #[method(name = "z_getsubtreesbyindex")]
343    async fn z_get_subtrees_by_index(
344        &self,
345        pool: String,
346        start_index: NoteCommitmentSubtreeIndex,
347        limit: Option<NoteCommitmentSubtreeIndex>,
348    ) -> Result<GetSubtreesByIndexResponse>;
349
350    /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure.
351    ///
352    /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html)
353    /// method: post
354    /// tags: transaction
355    ///
356    /// # Parameters
357    ///
358    /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned.
359    /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object.
360    /// - `blockhash` (string, optional) The block in which to look for the transaction
361    #[method(name = "getrawtransaction")]
362    async fn get_raw_transaction(
363        &self,
364        txid: String,
365        verbose: Option<u8>,
366        block_hash: Option<String>,
367    ) -> Result<GetRawTransactionResponse>;
368
369    /// Returns the transaction ids made by the provided transparent addresses.
370    ///
371    /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html)
372    /// method: post
373    /// tags: address
374    ///
375    /// # Parameters
376    ///
377    /// - `request`: (required) Either:
378    ///     - A single address string (e.g., `"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"`), or
379    ///     - An object with the following named fields:
380    ///         - `addresses`: (array of strings, required) The addresses to get transactions from.
381    ///         - `start`: (numeric, optional) The lower height to start looking for transactions (inclusive).
382    ///         - `end`: (numeric, optional) The upper height to stop looking for transactions (inclusive).
383    ///
384    /// Example of the object form:
385    /// ```json
386    /// {
387    ///   "addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"],
388    ///   "start": 1000,
389    ///   "end": 2000
390    /// }
391    /// ```
392    ///
393    /// # Notes
394    ///
395    /// - Only the multi-argument format is used by lightwalletd and this is what we currently support:
396    /// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L97-L102>
397    /// - It is recommended that users call the method with start/end heights such that the response can't be too large.
398    #[method(name = "getaddresstxids")]
399    async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
400
401    /// Returns all unspent outputs for a list of addresses.
402    ///
403    /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html)
404    /// method: post
405    /// tags: address
406    ///
407    /// # Parameters
408    ///
409    /// - `request`: (required) Either:
410    ///     - A single address string (e.g., `"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"`), or
411    ///     - An object with the following named fields:
412    ///         - `addresses`: (array, required, example=[\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]) The addresses to get outputs from.
413    ///         - `chaininfo`: (boolean, optional, default=false) Include chain info with results
414    ///
415    /// # Notes
416    ///
417    /// lightwalletd always uses the multi-address request, without chaininfo:
418    /// <https://github.com/zcash/lightwalletd/blob/master/frontend/service.go#L402>
419    #[method(name = "getaddressutxos")]
420    async fn get_address_utxos(
421        &self,
422        request: GetAddressUtxosRequest,
423    ) -> Result<GetAddressUtxosResponse>;
424
425    /// Stop the running zebrad process.
426    ///
427    /// # Notes
428    ///
429    /// - Works for non windows targets only.
430    /// - Works only if the network of the running zebrad process is `Regtest`.
431    ///
432    /// zcashd reference: [`stop`](https://zcash.github.io/rpc/stop.html)
433    /// method: post
434    /// tags: control
435    #[method(name = "stop")]
436    fn stop(&self) -> Result<String>;
437
438    /// Returns the height of the most recent block in the best valid block chain (equivalently,
439    /// the number of blocks in this chain excluding the genesis block).
440    ///
441    /// zcashd reference: [`getblockcount`](https://zcash.github.io/rpc/getblockcount.html)
442    /// method: post
443    /// tags: blockchain
444    #[method(name = "getblockcount")]
445    fn get_block_count(&self) -> Result<u32>;
446
447    /// Returns the hash of the block of a given height iff the index argument correspond
448    /// to a block in the best chain.
449    ///
450    /// zcashd reference: [`getblockhash`](https://zcash-rpc.github.io/getblockhash.html)
451    /// method: post
452    /// tags: blockchain
453    ///
454    /// # Parameters
455    ///
456    /// - `index`: (numeric, required, example=1) The block index.
457    ///
458    /// # Notes
459    ///
460    /// - If `index` is positive then index = block height.
461    /// - If `index` is negative then -1 is the last known valid block.
462    #[method(name = "getblockhash")]
463    async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse>;
464
465    /// Returns a block template for mining new Zcash blocks.
466    ///
467    /// # Parameters
468    ///
469    /// - `jsonrequestobject`: (string, optional) A JSON object containing arguments.
470    ///
471    /// zcashd reference: [`getblocktemplate`](https://zcash-rpc.github.io/getblocktemplate.html)
472    /// method: post
473    /// tags: mining
474    ///
475    /// # Notes
476    ///
477    /// Arguments to this RPC are currently ignored.
478    /// Long polling, block proposals, server lists, and work IDs are not supported.
479    ///
480    /// Miners can make arbitrary changes to blocks, as long as:
481    /// - the data sent to `submitblock` is a valid Zcash block, and
482    /// - the parent block is a valid block that Zebra already has, or will receive soon.
483    ///
484    /// Zebra verifies blocks in parallel, and keeps recent chains in parallel,
485    /// so moving between chains and forking chains is very cheap.
486    #[method(name = "getblocktemplate")]
487    async fn get_block_template(
488        &self,
489        parameters: Option<GetBlockTemplateParameters>,
490    ) -> Result<GetBlockTemplateResponse>;
491
492    /// Submits block to the node to be validated and committed.
493    /// Returns the [`SubmitBlockResponse`] for the operation, as a JSON string.
494    ///
495    /// zcashd reference: [`submitblock`](https://zcash.github.io/rpc/submitblock.html)
496    /// method: post
497    /// tags: mining
498    ///
499    /// # Parameters
500    ///
501    /// - `hexdata`: (string, required)
502    /// - `jsonparametersobject`: (string, optional) - currently ignored
503    ///
504    /// # Notes
505    ///
506    ///  - `jsonparametersobject` holds a single field, workid, that must be included in submissions if provided by the server.
507    #[method(name = "submitblock")]
508    async fn submit_block(
509        &self,
510        hex_data: HexData,
511        _parameters: Option<SubmitBlockParameters>,
512    ) -> Result<SubmitBlockResponse>;
513
514    /// Returns mining-related information.
515    ///
516    /// zcashd reference: [`getmininginfo`](https://zcash.github.io/rpc/getmininginfo.html)
517    /// method: post
518    /// tags: mining
519    #[method(name = "getmininginfo")]
520    async fn get_mining_info(&self) -> Result<GetMiningInfoResponse>;
521
522    /// Returns the estimated network solutions per second based on the last `num_blocks` before
523    /// `height`.
524    ///
525    /// If `num_blocks` is not supplied, uses 120 blocks. If it is 0 or -1, uses the difficulty
526    /// averaging window.
527    /// If `height` is not supplied or is -1, uses the tip height.
528    ///
529    /// zcashd reference: [`getnetworksolps`](https://zcash.github.io/rpc/getnetworksolps.html)
530    /// method: post
531    /// tags: mining
532    #[method(name = "getnetworksolps")]
533    async fn get_network_sol_ps(&self, num_blocks: Option<i32>, height: Option<i32>)
534        -> Result<u64>;
535
536    /// Returns the estimated network solutions per second based on the last `num_blocks` before
537    /// `height`.
538    ///
539    /// This method name is deprecated, use [`getnetworksolps`](Self::get_network_sol_ps) instead.
540    /// See that method for details.
541    ///
542    /// zcashd reference: [`getnetworkhashps`](https://zcash.github.io/rpc/getnetworkhashps.html)
543    /// method: post
544    /// tags: mining
545    #[method(name = "getnetworkhashps")]
546    async fn get_network_hash_ps(
547        &self,
548        num_blocks: Option<i32>,
549        height: Option<i32>,
550    ) -> Result<u64> {
551        self.get_network_sol_ps(num_blocks, height).await
552    }
553
554    /// Returns an object containing various state info regarding P2P networking.
555    ///
556    /// zcashd reference: [`getnetworkinfo`](https://zcash.github.io/rpc/getnetworkinfo.html)
557    /// method: post
558    /// tags: network
559    #[method(name = "getnetworkinfo")]
560    async fn get_network_info(&self) -> Result<GetNetworkInfoResponse>;
561
562    /// Returns data about each connected network node.
563    ///
564    /// zcashd reference: [`getpeerinfo`](https://zcash.github.io/rpc/getpeerinfo.html)
565    /// method: post
566    /// tags: network
567    #[method(name = "getpeerinfo")]
568    async fn get_peer_info(&self) -> Result<Vec<PeerInfo>>;
569
570    /// Checks if a zcash transparent address of type P2PKH, P2SH or TEX is valid.
571    /// Returns information about the given address if valid.
572    ///
573    /// zcashd reference: [`validateaddress`](https://zcash.github.io/rpc/validateaddress.html)
574    /// method: post
575    /// tags: util
576    ///
577    /// # Parameters
578    ///
579    /// - `address`: (string, required) The zcash address to validate.
580    #[method(name = "validateaddress")]
581    async fn validate_address(&self, address: String) -> Result<ValidateAddressResponse>;
582
583    /// Checks if a zcash address of type P2PKH, P2SH, TEX, SAPLING or UNIFIED is valid.
584    /// Returns information about the given address if valid.
585    ///
586    /// zcashd reference: [`z_validateaddress`](https://zcash.github.io/rpc/z_validateaddress.html)
587    /// method: post
588    /// tags: util
589    ///
590    /// # Parameters
591    ///
592    /// - `address`: (string, required) The zcash address to validate.
593    ///
594    /// # Notes
595    ///
596    /// - No notes
597    #[method(name = "z_validateaddress")]
598    async fn z_validate_address(&self, address: String) -> Result<ZValidateAddressResponse>;
599
600    /// Returns the block subsidy reward of the block at `height`, taking into account the mining slow start.
601    /// Returns an error if `height` is less than the height of the first halving for the current network.
602    ///
603    /// zcashd reference: [`getblocksubsidy`](https://zcash.github.io/rpc/getblocksubsidy.html)
604    /// method: post
605    /// tags: mining
606    ///
607    /// # Parameters
608    ///
609    /// - `height`: (numeric, optional, example=1) Can be any valid current or future height.
610    ///
611    /// # Notes
612    ///
613    /// If `height` is not supplied, uses the tip height.
614    #[method(name = "getblocksubsidy")]
615    async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse>;
616
617    /// Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
618    ///
619    /// zcashd reference: [`getdifficulty`](https://zcash.github.io/rpc/getdifficulty.html)
620    /// method: post
621    /// tags: blockchain
622    #[method(name = "getdifficulty")]
623    async fn get_difficulty(&self) -> Result<f64>;
624
625    /// Returns the list of individual payment addresses given a unified address.
626    ///
627    /// zcashd reference: [`z_listunifiedreceivers`](https://zcash.github.io/rpc/z_listunifiedreceivers.html)
628    /// method: post
629    /// tags: wallet
630    ///
631    /// # Parameters
632    ///
633    /// - `address`: (string, required) The zcash unified address to get the list from.
634    ///
635    /// # Notes
636    ///
637    /// - No notes
638    #[method(name = "z_listunifiedreceivers")]
639    async fn z_list_unified_receivers(
640        &self,
641        address: String,
642    ) -> Result<ZListUnifiedReceiversResponse>;
643
644    /// Invalidates a block if it is not yet finalized, removing it from the non-finalized
645    /// state if it is present and rejecting it during contextual validation if it is submitted.
646    ///
647    /// # Parameters
648    ///
649    /// - `block_hash`: (hex-encoded block hash, required) The block hash to invalidate.
650    // TODO: Invalidate block hashes even if they're not present in the non-finalized state (#9553).
651    #[method(name = "invalidateblock")]
652    async fn invalidate_block(&self, block_hash: String) -> Result<()>;
653
654    /// Reconsiders a previously invalidated block if it exists in the cache of previously invalidated blocks.
655    ///
656    /// # Parameters
657    ///
658    /// - `block_hash`: (hex-encoded block hash, required) The block hash to reconsider.
659    #[method(name = "reconsiderblock")]
660    async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>>;
661
662    #[method(name = "generate")]
663    /// Mine blocks immediately. Returns the block hashes of the generated blocks.
664    ///
665    /// # Parameters
666    ///
667    /// - `num_blocks`: (numeric, required, example=1) Number of blocks to be generated.
668    ///
669    /// # Notes
670    ///
671    /// Only works if the network of the running zebrad process is `Regtest`.
672    ///
673    /// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html)
674    /// method: post
675    /// tags: generating
676    async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHashResponse>>;
677
678    #[method(name = "addnode")]
679    /// Add or remove a node from the address book.
680    ///
681    /// # Parameters
682    ///
683    /// - `addr`: (string, required) The address of the node to add or remove.
684    /// - `command`: (string, required) The command to execute, either "add", "onetry", or "remove".
685    ///
686    /// # Notes
687    ///
688    /// Only the "add" command is currently supported.
689    ///
690    /// zcashd reference: [`addnode`](https://zcash.github.io/rpc/addnode.html)
691    /// method: post
692    /// tags: network
693    async fn add_node(&self, addr: PeerSocketAddr, command: AddNodeCommand) -> Result<()>;
694}
695
696/// RPC method implementations.
697#[derive(Clone)]
698pub struct RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
699where
700    Mempool: Service<
701            mempool::Request,
702            Response = mempool::Response,
703            Error = zebra_node_services::BoxError,
704        > + Clone
705        + Send
706        + Sync
707        + 'static,
708    Mempool::Future: Send,
709    State: Service<
710            zebra_state::Request,
711            Response = zebra_state::Response,
712            Error = zebra_state::BoxError,
713        > + Clone
714        + Send
715        + Sync
716        + 'static,
717    State::Future: Send,
718    ReadState: Service<
719            zebra_state::ReadRequest,
720            Response = zebra_state::ReadResponse,
721            Error = zebra_state::BoxError,
722        > + Clone
723        + Send
724        + Sync
725        + 'static,
726    ReadState::Future: Send,
727    Tip: ChainTip + Clone + Send + Sync + 'static,
728    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
729    BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
730        + Clone
731        + Send
732        + Sync
733        + 'static,
734    <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
735    SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
736{
737    // Configuration
738    //
739    /// Zebra's application version, with build metadata.
740    build_version: String,
741
742    /// Zebra's RPC user agent.
743    user_agent: String,
744
745    /// The configured network for this RPC service.
746    network: Network,
747
748    /// Test-only option that makes Zebra say it is at the chain tip,
749    /// no matter what the estimated height or local clock is.
750    debug_force_finished_sync: bool,
751
752    // Services
753    //
754    /// A handle to the mempool service.
755    mempool: Mempool,
756
757    /// A handle to the state service.
758    state: State,
759
760    /// A handle to the state service.
761    read_state: ReadState,
762
763    /// Allows efficient access to the best tip of the blockchain.
764    latest_chain_tip: Tip,
765
766    // Tasks
767    //
768    /// A sender component of a channel used to send transactions to the mempool queue.
769    queue_sender: broadcast::Sender<UnminedTx>,
770
771    /// Peer address book.
772    address_book: AddressBook,
773
774    /// The last warning or error event logged by the server.
775    last_warn_error_log_rx: LoggedLastEvent,
776
777    /// Handler for the `getblocktemplate` RPC.
778    gbt: GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>,
779}
780
781/// A type alias for the last event logged by the server.
782pub type LoggedLastEvent = watch::Receiver<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>;
783
784impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> fmt::Debug
785    for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
786where
787    Mempool: Service<
788            mempool::Request,
789            Response = mempool::Response,
790            Error = zebra_node_services::BoxError,
791        > + Clone
792        + Send
793        + Sync
794        + 'static,
795    Mempool::Future: Send,
796    State: Service<
797            zebra_state::Request,
798            Response = zebra_state::Response,
799            Error = zebra_state::BoxError,
800        > + Clone
801        + Send
802        + Sync
803        + 'static,
804    State::Future: Send,
805    ReadState: Service<
806            zebra_state::ReadRequest,
807            Response = zebra_state::ReadResponse,
808            Error = zebra_state::BoxError,
809        > + Clone
810        + Send
811        + Sync
812        + 'static,
813    ReadState::Future: Send,
814    Tip: ChainTip + Clone + Send + Sync + 'static,
815    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
816    BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
817        + Clone
818        + Send
819        + Sync
820        + 'static,
821    <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
822    SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
823{
824    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825        // Skip fields without Debug impls, and skip channels
826        f.debug_struct("RpcImpl")
827            .field("build_version", &self.build_version)
828            .field("user_agent", &self.user_agent)
829            .field("network", &self.network)
830            .field("debug_force_finished_sync", &self.debug_force_finished_sync)
831            .field("getblocktemplate", &self.gbt)
832            .finish()
833    }
834}
835
836impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
837    RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
838where
839    Mempool: Service<
840            mempool::Request,
841            Response = mempool::Response,
842            Error = zebra_node_services::BoxError,
843        > + Clone
844        + Send
845        + Sync
846        + 'static,
847    Mempool::Future: Send,
848    State: Service<
849            zebra_state::Request,
850            Response = zebra_state::Response,
851            Error = zebra_state::BoxError,
852        > + Clone
853        + Send
854        + Sync
855        + 'static,
856    State::Future: Send,
857    ReadState: Service<
858            zebra_state::ReadRequest,
859            Response = zebra_state::ReadResponse,
860            Error = zebra_state::BoxError,
861        > + Clone
862        + Send
863        + Sync
864        + 'static,
865    ReadState::Future: Send,
866    Tip: ChainTip + Clone + Send + Sync + 'static,
867    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
868    BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
869        + Clone
870        + Send
871        + Sync
872        + 'static,
873    <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
874    SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
875{
876    /// Create a new instance of the RPC handler.
877    //
878    // TODO:
879    // - put some of the configs or services in their own struct?
880    #[allow(clippy::too_many_arguments)]
881    pub fn new<VersionString, UserAgentString>(
882        network: Network,
883        mining_config: config::mining::Config,
884        debug_force_finished_sync: bool,
885        build_version: VersionString,
886        user_agent: UserAgentString,
887        mempool: Mempool,
888        state: State,
889        read_state: ReadState,
890        block_verifier_router: BlockVerifierRouter,
891        sync_status: SyncStatus,
892        latest_chain_tip: Tip,
893        address_book: AddressBook,
894        last_warn_error_log_rx: LoggedLastEvent,
895        mined_block_sender: Option<mpsc::Sender<(block::Hash, block::Height)>>,
896    ) -> (Self, JoinHandle<()>)
897    where
898        VersionString: ToString + Clone + Send + 'static,
899        UserAgentString: ToString + Clone + Send + 'static,
900    {
901        let (runner, queue_sender) = Queue::start();
902
903        let mut build_version = build_version.to_string();
904        let user_agent = user_agent.to_string();
905
906        // Match zcashd's version format, if the version string has anything in it
907        if !build_version.is_empty() && !build_version.starts_with('v') {
908            build_version.insert(0, 'v');
909        }
910
911        let gbt = GetBlockTemplateHandler::new(
912            &network,
913            mining_config.clone(),
914            block_verifier_router,
915            sync_status,
916            mined_block_sender,
917        );
918
919        let rpc_impl = RpcImpl {
920            build_version,
921            user_agent,
922            network: network.clone(),
923            debug_force_finished_sync,
924            mempool: mempool.clone(),
925            state: state.clone(),
926            read_state: read_state.clone(),
927            latest_chain_tip: latest_chain_tip.clone(),
928            queue_sender,
929            address_book,
930            last_warn_error_log_rx,
931            gbt,
932        };
933
934        // run the process queue
935        let rpc_tx_queue_task_handle = tokio::spawn(
936            runner
937                .run(mempool, read_state, latest_chain_tip, network)
938                .in_current_span(),
939        );
940
941        (rpc_impl, rpc_tx_queue_task_handle)
942    }
943
944    /// Returns a reference to the configured network.
945    pub fn network(&self) -> &Network {
946        &self.network
947    }
948}
949
950#[async_trait]
951impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> RpcServer
952    for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
953where
954    Mempool: Service<
955            mempool::Request,
956            Response = mempool::Response,
957            Error = zebra_node_services::BoxError,
958        > + Clone
959        + Send
960        + Sync
961        + 'static,
962    Mempool::Future: Send,
963    State: Service<
964            zebra_state::Request,
965            Response = zebra_state::Response,
966            Error = zebra_state::BoxError,
967        > + Clone
968        + Send
969        + Sync
970        + 'static,
971    State::Future: Send,
972    ReadState: Service<
973            zebra_state::ReadRequest,
974            Response = zebra_state::ReadResponse,
975            Error = zebra_state::BoxError,
976        > + Clone
977        + Send
978        + Sync
979        + 'static,
980    ReadState::Future: Send,
981    Tip: ChainTip + Clone + Send + Sync + 'static,
982    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
983    BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
984        + Clone
985        + Send
986        + Sync
987        + 'static,
988    <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
989    SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
990{
991    async fn get_info(&self) -> Result<GetInfoResponse> {
992        let version = GetInfoResponse::version_from_string(&self.build_version)
993            .expect("invalid version string");
994
995        let connections = self.address_book.recently_live_peers(Utc::now()).len();
996
997        let last_error_recorded = self.last_warn_error_log_rx.borrow().clone();
998        let (last_error_log, _level, last_error_log_time) = last_error_recorded.unwrap_or((
999            GetInfoResponse::default().errors,
1000            tracing::Level::INFO,
1001            Utc::now(),
1002        ));
1003
1004        let tip_height = self
1005            .latest_chain_tip
1006            .best_tip_height()
1007            .unwrap_or(Height::MIN);
1008        let testnet = self.network.is_a_test_network();
1009
1010        // This field is behind the `ENABLE_WALLET` feature flag in zcashd:
1011        // https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L113
1012        // However it is not documented as optional:
1013        // https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L70
1014        // For compatibility, we keep the field in the response, but always return 0.
1015        let pay_tx_fee = 0.0;
1016
1017        let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
1018            / (zebra_chain::amount::COIN as f64);
1019        let difficulty = chain_tip_difficulty(self.network.clone(), self.read_state.clone(), true)
1020            .await
1021            .expect("should always be Ok when `should_use_default` is true");
1022
1023        let response = GetInfoResponse {
1024            version,
1025            build: self.build_version.clone(),
1026            subversion: self.user_agent.clone(),
1027            protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
1028            blocks: tip_height.0,
1029            connections,
1030            proxy: None,
1031            difficulty,
1032            testnet,
1033            pay_tx_fee,
1034            relay_fee,
1035            errors: last_error_log,
1036            errors_timestamp: last_error_log_time.to_string(),
1037        };
1038
1039        Ok(response)
1040    }
1041
1042    #[allow(clippy::unwrap_in_result)]
1043    async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse> {
1044        let debug_force_finished_sync = self.debug_force_finished_sync;
1045        let network = &self.network;
1046
1047        let (usage_info_rsp, tip_pool_values_rsp, chain_tip_difficulty) = {
1048            use zebra_state::ReadRequest::*;
1049            let state_call = |request| self.read_state.clone().oneshot(request);
1050            tokio::join!(
1051                state_call(UsageInfo),
1052                state_call(TipPoolValues),
1053                chain_tip_difficulty(network.clone(), self.read_state.clone(), true)
1054            )
1055        };
1056
1057        let (size_on_disk, (tip_height, tip_hash), value_balance, difficulty) = {
1058            use zebra_state::ReadResponse::*;
1059
1060            let UsageInfo(size_on_disk) = usage_info_rsp.map_misc_error()? else {
1061                unreachable!("unmatched response to a TipPoolValues request")
1062            };
1063
1064            let (tip, value_balance) = match tip_pool_values_rsp {
1065                Ok(TipPoolValues {
1066                    tip_height,
1067                    tip_hash,
1068                    value_balance,
1069                }) => ((tip_height, tip_hash), value_balance),
1070                Ok(_) => unreachable!("unmatched response to a TipPoolValues request"),
1071                Err(_) => ((Height::MIN, network.genesis_hash()), Default::default()),
1072            };
1073
1074            let difficulty = chain_tip_difficulty
1075                .expect("should always be Ok when `should_use_default` is true");
1076
1077            (size_on_disk, tip, value_balance, difficulty)
1078        };
1079
1080        let now = Utc::now();
1081        let (estimated_height, verification_progress) = self
1082            .latest_chain_tip
1083            .best_tip_height_and_block_time()
1084            .map(|(tip_height, tip_block_time)| {
1085                let height =
1086                    NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, network)
1087                        .estimate_height_at(now);
1088
1089                // If we're testing the mempool, force the estimated height to be the actual tip height, otherwise,
1090                // check if the estimated height is below Zebra's latest tip height, or if the latest tip's block time is
1091                // later than the current time on the local clock.
1092                let height =
1093                    if tip_block_time > now || height < tip_height || debug_force_finished_sync {
1094                        tip_height
1095                    } else {
1096                        height
1097                    };
1098
1099                (height, f64::from(tip_height.0) / f64::from(height.0))
1100            })
1101            // TODO: Add a `genesis_block_time()` method on `Network` to use here.
1102            .unwrap_or((Height::MIN, 0.0));
1103
1104        let verification_progress = if network.is_regtest() {
1105            1.0
1106        } else {
1107            verification_progress
1108        };
1109
1110        // `upgrades` object
1111        //
1112        // Get the network upgrades in height order, like `zcashd`.
1113        let mut upgrades = IndexMap::new();
1114        for (activation_height, network_upgrade) in network.full_activation_list() {
1115            // Zebra defines network upgrades based on incompatible consensus rule changes,
1116            // but zcashd defines them based on ZIPs.
1117            //
1118            // All the network upgrades with a consensus branch ID are the same in Zebra and zcashd.
1119            if let Some(branch_id) = network_upgrade.branch_id() {
1120                // zcashd's RPC seems to ignore Disabled network upgrades, so Zebra does too.
1121                let status = if tip_height >= activation_height {
1122                    NetworkUpgradeStatus::Active
1123                } else {
1124                    NetworkUpgradeStatus::Pending
1125                };
1126
1127                let upgrade = NetworkUpgradeInfo {
1128                    name: network_upgrade,
1129                    activation_height,
1130                    status,
1131                };
1132                upgrades.insert(ConsensusBranchIdHex(branch_id), upgrade);
1133            }
1134        }
1135
1136        // `consensus` object
1137        let next_block_height =
1138            (tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
1139        let consensus = TipConsensusBranch {
1140            chain_tip: ConsensusBranchIdHex(
1141                NetworkUpgrade::current(network, tip_height)
1142                    .branch_id()
1143                    .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1144            ),
1145            next_block: ConsensusBranchIdHex(
1146                NetworkUpgrade::current(network, next_block_height)
1147                    .branch_id()
1148                    .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1149            ),
1150        };
1151
1152        let response = GetBlockchainInfoResponse {
1153            chain: network.bip70_network_name(),
1154            blocks: tip_height,
1155            best_block_hash: tip_hash,
1156            estimated_height,
1157            chain_supply: GetBlockchainInfoBalance::chain_supply(value_balance),
1158            value_pools: GetBlockchainInfoBalance::value_pools(value_balance, None),
1159            upgrades,
1160            consensus,
1161            headers: tip_height,
1162            difficulty,
1163            verification_progress,
1164            // TODO: store work in the finalized state for each height (#7109)
1165            chain_work: 0,
1166            pruned: false,
1167            size_on_disk,
1168            // TODO: Investigate whether this needs to be implemented (it's sprout-only in zcashd)
1169            commitments: 0,
1170        };
1171
1172        Ok(response)
1173    }
1174
1175    async fn get_address_balance(
1176        &self,
1177        address_strings: GetAddressBalanceRequest,
1178    ) -> Result<GetAddressBalanceResponse> {
1179        let valid_addresses = address_strings.valid_addresses()?;
1180
1181        let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
1182        let response = self
1183            .read_state
1184            .clone()
1185            .oneshot(request)
1186            .await
1187            .map_misc_error()?;
1188
1189        match response {
1190            zebra_state::ReadResponse::AddressBalance { balance, received } => {
1191                Ok(GetAddressBalanceResponse {
1192                    balance: u64::from(balance),
1193                    received,
1194                })
1195            }
1196            _ => unreachable!("Unexpected response from state service: {response:?}"),
1197        }
1198    }
1199
1200    // TODO: use HexData or GetRawTransaction::Bytes to handle the transaction data argument
1201    async fn send_raw_transaction(
1202        &self,
1203        raw_transaction_hex: String,
1204        _allow_high_fees: Option<bool>,
1205    ) -> Result<SendRawTransactionResponse> {
1206        let mempool = self.mempool.clone();
1207        let queue_sender = self.queue_sender.clone();
1208
1209        // Reference for the legacy error code:
1210        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L1259-L1260>
1211        let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
1212            .map_error(server::error::LegacyCode::Deserialization)?;
1213        let raw_transaction = Transaction::zcash_deserialize(&*raw_transaction_bytes)
1214            .map_error(server::error::LegacyCode::Deserialization)?;
1215
1216        let transaction_hash = raw_transaction.hash();
1217
1218        // send transaction to the rpc queue, ignore any error.
1219        let unmined_transaction = UnminedTx::from(raw_transaction.clone());
1220        let _ = queue_sender.send(unmined_transaction);
1221
1222        let transaction_parameter = mempool::Gossip::Tx(raw_transaction.into());
1223        let request = mempool::Request::Queue(vec![transaction_parameter]);
1224
1225        let response = mempool.oneshot(request).await.map_misc_error()?;
1226
1227        let mut queue_results = match response {
1228            mempool::Response::Queued(results) => results,
1229            _ => unreachable!("incorrect response variant from mempool service"),
1230        };
1231
1232        assert_eq!(
1233            queue_results.len(),
1234            1,
1235            "mempool service returned more results than expected"
1236        );
1237
1238        let queue_result = queue_results
1239            .pop()
1240            .expect("there should be exactly one item in Vec")
1241            .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err))
1242            .map_misc_error()?
1243            .await
1244            .map_misc_error()?;
1245
1246        tracing::debug!("sent transaction to mempool: {:?}", &queue_result);
1247
1248        queue_result
1249            .map(|_| SendRawTransactionResponse(transaction_hash))
1250            // Reference for the legacy error code:
1251            // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L1290-L1301>
1252            // Note that this error code might not exactly match the one returned by zcashd
1253            // since zcashd's error code selection logic is more granular. We'd need to
1254            // propagate the error coming from the verifier to be able to return more specific
1255            // error codes.
1256            .map_error(server::error::LegacyCode::Verify)
1257    }
1258
1259    // # Performance
1260    //
1261    // `lightwalletd` calls this RPC with verosity 1 for its initial sync of 2 million blocks, the
1262    // performance of this RPC with verbosity 1 significantly affects `lightwalletd`s sync time.
1263    async fn get_block(
1264        &self,
1265        hash_or_height: String,
1266        verbosity: Option<u8>,
1267    ) -> Result<GetBlockResponse> {
1268        let verbosity = verbosity.unwrap_or(1);
1269        let network = self.network.clone();
1270        let original_hash_or_height = hash_or_height.clone();
1271
1272        // If verbosity requires a call to `get_block_header`, resolve it here
1273        let get_block_header_future = if matches!(verbosity, 1 | 2) {
1274            Some(self.get_block_header(original_hash_or_height.clone(), Some(true)))
1275        } else {
1276            None
1277        };
1278
1279        let hash_or_height =
1280            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1281                // Reference for the legacy error code:
1282                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1283                .map_error(server::error::LegacyCode::InvalidParameter)?;
1284
1285        if verbosity == 0 {
1286            let request = zebra_state::ReadRequest::Block(hash_or_height);
1287            let response = self
1288                .read_state
1289                .clone()
1290                .oneshot(request)
1291                .await
1292                .map_misc_error()?;
1293
1294            match response {
1295                zebra_state::ReadResponse::Block(Some(block)) => {
1296                    Ok(GetBlockResponse::Raw(block.into()))
1297                }
1298                zebra_state::ReadResponse::Block(None) => {
1299                    Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
1300                }
1301                _ => unreachable!("unmatched response to a block request"),
1302            }
1303        } else if let Some(get_block_header_future) = get_block_header_future {
1304            let get_block_header_result: Result<GetBlockHeaderResponse> =
1305                get_block_header_future.await;
1306
1307            let GetBlockHeaderResponse::Object(block_header) = get_block_header_result? else {
1308                panic!("must return Object")
1309            };
1310
1311            let BlockHeaderObject {
1312                hash,
1313                confirmations,
1314                height,
1315                version,
1316                merkle_root,
1317                block_commitments,
1318                final_sapling_root,
1319                sapling_tree_size,
1320                time,
1321                nonce,
1322                solution,
1323                bits,
1324                difficulty,
1325                previous_block_hash,
1326                next_block_hash,
1327            } = *block_header;
1328
1329            let transactions_request = match verbosity {
1330                1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
1331                2 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
1332                _other => panic!("get_block_header_fut should be none"),
1333            };
1334
1335            // # Concurrency
1336            //
1337            // We look up by block hash so the hash, transaction IDs, and confirmations
1338            // are consistent.
1339            let hash_or_height = hash.into();
1340            let requests = vec![
1341                // Get transaction IDs from the transaction index by block hash
1342                //
1343                // # Concurrency
1344                //
1345                // A block's transaction IDs are never modified, so all possible responses are
1346                // valid. Clients that query block heights must be able to handle chain forks,
1347                // including getting transaction IDs from any chain fork.
1348                transactions_request,
1349                // Orchard trees
1350                zebra_state::ReadRequest::OrchardTree(hash_or_height),
1351                // Block info
1352                zebra_state::ReadRequest::BlockInfo(previous_block_hash.into()),
1353                zebra_state::ReadRequest::BlockInfo(hash_or_height),
1354            ];
1355
1356            let mut futs = FuturesOrdered::new();
1357
1358            for request in requests {
1359                futs.push_back(self.read_state.clone().oneshot(request));
1360            }
1361
1362            let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
1363            let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
1364                zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
1365                    tx_ids
1366                        .ok_or_misc_error("block not found")?
1367                        .iter()
1368                        .map(|tx_id| GetBlockTransaction::Hash(*tx_id))
1369                        .collect(),
1370                    None,
1371                ),
1372                zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
1373                    let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
1374                    let block_time = block.header.time;
1375                    let transactions =
1376                        block
1377                            .transactions
1378                            .iter()
1379                            .map(|tx| {
1380                                GetBlockTransaction::Object(Box::new(
1381                                    TransactionObject::from_transaction(
1382                                        tx.clone(),
1383                                        Some(height),
1384                                        Some(confirmations.try_into().expect(
1385                                            "should be less than max block height, i32::MAX",
1386                                        )),
1387                                        &network,
1388                                        Some(block_time),
1389                                        Some(hash),
1390                                        Some(true),
1391                                        tx.hash(),
1392                                    ),
1393                                ))
1394                            })
1395                            .collect();
1396                    (transactions, Some(size))
1397                }
1398                _ => unreachable!("unmatched response to a transaction_ids_for_block request"),
1399            };
1400
1401            let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
1402            let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
1403                orchard_tree_response.map_misc_error()?
1404            else {
1405                unreachable!("unmatched response to a OrchardTree request");
1406            };
1407
1408            let nu5_activation = NetworkUpgrade::Nu5.activation_height(&network);
1409
1410            // This could be `None` if there's a chain reorg between state queries.
1411            let orchard_tree = orchard_tree.ok_or_misc_error("missing Orchard tree")?;
1412
1413            let final_orchard_root = match nu5_activation {
1414                Some(activation_height) if height >= activation_height => {
1415                    Some(orchard_tree.root().into())
1416                }
1417                _other => None,
1418            };
1419
1420            let sapling = SaplingTrees {
1421                size: sapling_tree_size,
1422            };
1423
1424            let orchard_tree_size = orchard_tree.count();
1425            let orchard = OrchardTrees {
1426                size: orchard_tree_size,
1427            };
1428
1429            let trees = GetBlockTrees { sapling, orchard };
1430
1431            let block_info_response = futs.next().await.expect("`futs` should not be empty");
1432            let zebra_state::ReadResponse::BlockInfo(prev_block_info) =
1433                block_info_response.map_misc_error()?
1434            else {
1435                unreachable!("unmatched response to a BlockInfo request");
1436            };
1437            let block_info_response = futs.next().await.expect("`futs` should not be empty");
1438            let zebra_state::ReadResponse::BlockInfo(block_info) =
1439                block_info_response.map_misc_error()?
1440            else {
1441                unreachable!("unmatched response to a BlockInfo request");
1442            };
1443
1444            let delta = block_info.as_ref().and_then(|d| {
1445                let value_pools = d.value_pools().constrain::<NegativeAllowed>().ok()?;
1446                let prev_value_pools = prev_block_info
1447                    .map(|d| d.value_pools().constrain::<NegativeAllowed>())
1448                    .unwrap_or(Ok(ValueBalance::<NegativeAllowed>::zero()))
1449                    .ok()?;
1450                (value_pools - prev_value_pools).ok()
1451            });
1452            let size = size.or(block_info.as_ref().map(|d| d.size() as usize));
1453
1454            Ok(GetBlockResponse::Object(Box::new(BlockObject {
1455                hash,
1456                confirmations,
1457                height: Some(height),
1458                version: Some(version),
1459                merkle_root: Some(merkle_root),
1460                time: Some(time),
1461                nonce: Some(nonce),
1462                solution: Some(solution),
1463                bits: Some(bits),
1464                difficulty: Some(difficulty),
1465                tx,
1466                trees,
1467                chain_supply: block_info
1468                    .as_ref()
1469                    .map(|d| GetBlockchainInfoBalance::chain_supply(*d.value_pools())),
1470                value_pools: block_info
1471                    .map(|d| GetBlockchainInfoBalance::value_pools(*d.value_pools(), delta)),
1472                size: size.map(|size| size as i64),
1473                block_commitments: Some(block_commitments),
1474                final_sapling_root: Some(final_sapling_root),
1475                final_orchard_root,
1476                previous_block_hash: Some(previous_block_hash),
1477                next_block_hash,
1478            })))
1479        } else {
1480            Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
1481        }
1482    }
1483
1484    async fn get_block_header(
1485        &self,
1486        hash_or_height: String,
1487        verbose: Option<bool>,
1488    ) -> Result<GetBlockHeaderResponse> {
1489        let verbose = verbose.unwrap_or(true);
1490        let network = self.network.clone();
1491
1492        let hash_or_height =
1493            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1494                // Reference for the legacy error code:
1495                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1496                .map_error(server::error::LegacyCode::InvalidParameter)?;
1497        let zebra_state::ReadResponse::BlockHeader {
1498            header,
1499            hash,
1500            height,
1501            next_block_hash,
1502        } = self
1503            .read_state
1504            .clone()
1505            .oneshot(zebra_state::ReadRequest::BlockHeader(hash_or_height))
1506            .await
1507            .map_err(|_| "block height not in best chain")
1508            .map_error(
1509                // ## Compatibility with `zcashd`.
1510                //
1511                // Since this function is reused by getblock(), we return the errors
1512                // expected by it (they differ whether a hash or a height was passed).
1513                if hash_or_height.hash().is_some() {
1514                    server::error::LegacyCode::InvalidAddressOrKey
1515                } else {
1516                    server::error::LegacyCode::InvalidParameter
1517                },
1518            )?
1519        else {
1520            panic!("unexpected response to BlockHeader request")
1521        };
1522
1523        let response = if !verbose {
1524            GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec().map_misc_error()?))
1525        } else {
1526            let zebra_state::ReadResponse::SaplingTree(sapling_tree) = self
1527                .read_state
1528                .clone()
1529                .oneshot(zebra_state::ReadRequest::SaplingTree(hash_or_height))
1530                .await
1531                .map_misc_error()?
1532            else {
1533                panic!("unexpected response to SaplingTree request")
1534            };
1535
1536            // This could be `None` if there's a chain reorg between state queries.
1537            let sapling_tree = sapling_tree.ok_or_misc_error("missing Sapling tree")?;
1538
1539            let zebra_state::ReadResponse::Depth(depth) = self
1540                .read_state
1541                .clone()
1542                .oneshot(zebra_state::ReadRequest::Depth(hash))
1543                .await
1544                .map_misc_error()?
1545            else {
1546                panic!("unexpected response to SaplingTree request")
1547            };
1548
1549            // From <https://zcash.github.io/rpc/getblock.html>
1550            // TODO: Deduplicate const definition, consider refactoring this to avoid duplicate logic
1551            const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
1552
1553            // Confirmations are one more than the depth.
1554            // Depth is limited by height, so it will never overflow an i64.
1555            let confirmations = depth
1556                .map(|depth| i64::from(depth) + 1)
1557                .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS);
1558
1559            let mut nonce = *header.nonce;
1560            nonce.reverse();
1561
1562            let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network);
1563            let sapling_tree_size = sapling_tree.count();
1564            let final_sapling_root: [u8; 32] =
1565                if sapling_activation.is_some() && height >= sapling_activation.unwrap() {
1566                    let mut root: [u8; 32] = sapling_tree.root().into();
1567                    root.reverse();
1568                    root
1569                } else {
1570                    [0; 32]
1571                };
1572
1573            let difficulty = header.difficulty_threshold.relative_to_network(&network);
1574
1575            let block_commitments = match header.commitment(&network, height).expect(
1576                "Unexpected failure while parsing the blockcommitments field in get_block_header",
1577            ) {
1578                Commitment::PreSaplingReserved(bytes) => bytes,
1579                Commitment::FinalSaplingRoot(_) => final_sapling_root,
1580                Commitment::ChainHistoryActivationReserved => [0; 32],
1581                Commitment::ChainHistoryRoot(root) => root.bytes_in_display_order(),
1582                Commitment::ChainHistoryBlockTxAuthCommitment(hash) => {
1583                    hash.bytes_in_display_order()
1584                }
1585            };
1586
1587            let block_header = BlockHeaderObject {
1588                hash,
1589                confirmations,
1590                height,
1591                version: header.version,
1592                merkle_root: header.merkle_root,
1593                block_commitments,
1594                final_sapling_root,
1595                sapling_tree_size,
1596                time: header.time.timestamp(),
1597                nonce,
1598                solution: header.solution,
1599                bits: header.difficulty_threshold,
1600                difficulty,
1601                previous_block_hash: header.previous_block_hash,
1602                next_block_hash,
1603            };
1604
1605            GetBlockHeaderResponse::Object(Box::new(block_header))
1606        };
1607
1608        Ok(response)
1609    }
1610
1611    fn get_best_block_hash(&self) -> Result<GetBlockHashResponse> {
1612        self.latest_chain_tip
1613            .best_tip_hash()
1614            .map(GetBlockHashResponse)
1615            .ok_or_misc_error("No blocks in state")
1616    }
1617
1618    fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse> {
1619        self.latest_chain_tip
1620            .best_tip_height_and_hash()
1621            .map(|(height, hash)| GetBlockHeightAndHashResponse { height, hash })
1622            .ok_or_misc_error("No blocks in state")
1623    }
1624
1625    async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse> {
1626        let mut mempool = self.mempool.clone();
1627
1628        let response = mempool
1629            .ready()
1630            .and_then(|service| service.call(mempool::Request::QueueStats))
1631            .await
1632            .map_misc_error()?;
1633
1634        if let mempool::Response::QueueStats {
1635            size,
1636            bytes,
1637            usage,
1638            fully_notified,
1639        } = response
1640        {
1641            Ok(GetMempoolInfoResponse {
1642                size,
1643                bytes,
1644                usage,
1645                fully_notified,
1646            })
1647        } else {
1648            unreachable!("unexpected response to QueueStats request")
1649        }
1650    }
1651
1652    async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse> {
1653        #[allow(unused)]
1654        let verbose = verbose.unwrap_or(false);
1655
1656        use zebra_chain::block::MAX_BLOCK_BYTES;
1657
1658        let mut mempool = self.mempool.clone();
1659
1660        let request = if verbose {
1661            mempool::Request::FullTransactions
1662        } else {
1663            mempool::Request::TransactionIds
1664        };
1665
1666        // `zcashd` doesn't check if it is synced to the tip here, so we don't either.
1667        let response = mempool
1668            .ready()
1669            .and_then(|service| service.call(request))
1670            .await
1671            .map_misc_error()?;
1672
1673        match response {
1674            mempool::Response::FullTransactions {
1675                mut transactions,
1676                transaction_dependencies,
1677                last_seen_tip_hash: _,
1678            } => {
1679                if verbose {
1680                    let map = transactions
1681                        .iter()
1682                        .map(|unmined_tx| {
1683                            (
1684                                unmined_tx.transaction.id.mined_id().encode_hex(),
1685                                get_raw_mempool::MempoolObject::from_verified_unmined_tx(
1686                                    unmined_tx,
1687                                    &transactions,
1688                                    &transaction_dependencies,
1689                                ),
1690                            )
1691                        })
1692                        .collect::<HashMap<_, _>>();
1693                    Ok(GetRawMempoolResponse::Verbose(map))
1694                } else {
1695                    // Sort transactions in descending order by fee/size, using
1696                    // hash in serialized byte order as a tie-breaker. Note that
1697                    // this is only done in not verbose because in verbose mode
1698                    // a dictionary is returned, where order does not matter.
1699                    transactions.sort_by_cached_key(|tx| {
1700                        // zcashd uses modified fee here but Zebra doesn't currently
1701                        // support prioritizing transactions
1702                        cmp::Reverse((
1703                            i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
1704                                / tx.transaction.size as u128,
1705                            // transaction hashes are compared in their serialized byte-order.
1706                            tx.transaction.id.mined_id(),
1707                        ))
1708                    });
1709                    let tx_ids: Vec<String> = transactions
1710                        .iter()
1711                        .map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
1712                        .collect();
1713
1714                    Ok(GetRawMempoolResponse::TxIds(tx_ids))
1715                }
1716            }
1717
1718            mempool::Response::TransactionIds(unmined_transaction_ids) => {
1719                let mut tx_ids: Vec<String> = unmined_transaction_ids
1720                    .iter()
1721                    .map(|id| id.mined_id().encode_hex())
1722                    .collect();
1723
1724                // Sort returned transaction IDs in numeric/string order.
1725                tx_ids.sort();
1726
1727                Ok(GetRawMempoolResponse::TxIds(tx_ids))
1728            }
1729
1730            _ => unreachable!("unmatched response to a transactionids request"),
1731        }
1732    }
1733
1734    async fn get_raw_transaction(
1735        &self,
1736        txid: String,
1737        verbose: Option<u8>,
1738        block_hash: Option<String>,
1739    ) -> Result<GetRawTransactionResponse> {
1740        let mut mempool = self.mempool.clone();
1741        let verbose = verbose.unwrap_or(0) != 0;
1742
1743        // Reference for the legacy error code:
1744        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L544>
1745        let txid = transaction::Hash::from_hex(txid)
1746            .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1747
1748        // Check the mempool first.
1749        if block_hash.is_none() {
1750            match mempool
1751                .ready()
1752                .and_then(|service| {
1753                    service.call(mempool::Request::TransactionsByMinedId([txid].into()))
1754                })
1755                .await
1756                .map_misc_error()?
1757            {
1758                mempool::Response::Transactions(txns) => {
1759                    if let Some(tx) = txns.first() {
1760                        return Ok(if verbose {
1761                            GetRawTransactionResponse::Object(Box::new(
1762                                TransactionObject::from_transaction(
1763                                    tx.transaction.clone(),
1764                                    None,
1765                                    None,
1766                                    &self.network,
1767                                    None,
1768                                    None,
1769                                    Some(false),
1770                                    txid,
1771                                ),
1772                            ))
1773                        } else {
1774                            let hex = tx.transaction.clone().into();
1775                            GetRawTransactionResponse::Raw(hex)
1776                        });
1777                    }
1778                }
1779
1780                _ => unreachable!("unmatched response to a `TransactionsByMinedId` request"),
1781            };
1782        }
1783
1784        let txid = if let Some(block_hash) = block_hash {
1785            let block_hash = block::Hash::from_hex(block_hash)
1786                .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1787            match self
1788                .read_state
1789                .clone()
1790                .oneshot(zebra_state::ReadRequest::AnyChainTransactionIdsForBlock(
1791                    block_hash.into(),
1792                ))
1793                .await
1794                .map_misc_error()?
1795            {
1796                zebra_state::ReadResponse::AnyChainTransactionIdsForBlock(tx_ids) => *tx_ids
1797                    .ok_or_error(
1798                        server::error::LegacyCode::InvalidAddressOrKey,
1799                        "block not found",
1800                    )?
1801                    .0
1802                    .iter()
1803                    .find(|id| **id == txid)
1804                    .ok_or_error(
1805                        server::error::LegacyCode::InvalidAddressOrKey,
1806                        "txid not found",
1807                    )?,
1808                _ => {
1809                    unreachable!("unmatched response to a `AnyChainTransactionIdsForBlock` request")
1810                }
1811            }
1812        } else {
1813            txid
1814        };
1815
1816        // If the tx wasn't in the mempool, check the state.
1817        match self
1818            .read_state
1819            .clone()
1820            .oneshot(zebra_state::ReadRequest::AnyChainTransaction(txid))
1821            .await
1822            .map_misc_error()?
1823        {
1824            zebra_state::ReadResponse::AnyChainTransaction(Some(tx)) => Ok(if verbose {
1825                match tx {
1826                    AnyTx::Mined(tx) => {
1827                        let block_hash = match self
1828                            .read_state
1829                            .clone()
1830                            .oneshot(zebra_state::ReadRequest::BestChainBlockHash(tx.height))
1831                            .await
1832                            .map_misc_error()?
1833                        {
1834                            zebra_state::ReadResponse::BlockHash(block_hash) => block_hash,
1835                            _ => {
1836                                unreachable!("unmatched response to a `BestChainBlockHash` request")
1837                            }
1838                        };
1839
1840                        GetRawTransactionResponse::Object(Box::new(
1841                            TransactionObject::from_transaction(
1842                                tx.tx.clone(),
1843                                Some(tx.height),
1844                                Some(tx.confirmations),
1845                                &self.network,
1846                                // TODO: Performance gain:
1847                                // https://github.com/ZcashFoundation/zebra/pull/9458#discussion_r2059352752
1848                                Some(tx.block_time),
1849                                block_hash,
1850                                Some(true),
1851                                txid,
1852                            ),
1853                        ))
1854                    }
1855                    AnyTx::Side((tx, block_hash)) => GetRawTransactionResponse::Object(Box::new(
1856                        TransactionObject::from_transaction(
1857                            tx.clone(),
1858                            None,
1859                            None,
1860                            &self.network,
1861                            None,
1862                            Some(block_hash),
1863                            Some(false),
1864                            txid,
1865                        ),
1866                    )),
1867                }
1868            } else {
1869                let tx: Arc<Transaction> = tx.into();
1870                let hex = tx.into();
1871                GetRawTransactionResponse::Raw(hex)
1872            }),
1873
1874            zebra_state::ReadResponse::AnyChainTransaction(None) => {
1875                Err("No such mempool or main chain transaction")
1876                    .map_error(server::error::LegacyCode::InvalidAddressOrKey)
1877            }
1878
1879            _ => unreachable!("unmatched response to a `Transaction` read request"),
1880        }
1881    }
1882
1883    async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse> {
1884        let mut read_state = self.read_state.clone();
1885        let network = self.network.clone();
1886
1887        let hash_or_height =
1888            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1889                // Reference for the legacy error code:
1890                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1891                .map_error(server::error::LegacyCode::InvalidParameter)?;
1892
1893        // Fetch the block referenced by [`hash_or_height`] from the state.
1894        //
1895        // # Concurrency
1896        //
1897        // For consistency, this lookup must be performed first, then all the other lookups must
1898        // be based on the hash.
1899        //
1900        // TODO: If this RPC is called a lot, just get the block header, rather than the whole block.
1901        let block = match read_state
1902            .ready()
1903            .and_then(|service| service.call(zebra_state::ReadRequest::Block(hash_or_height)))
1904            .await
1905            .map_misc_error()?
1906        {
1907            zebra_state::ReadResponse::Block(Some(block)) => block,
1908            zebra_state::ReadResponse::Block(None) => {
1909                // Reference for the legacy error code:
1910                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1911                return Err("the requested block is not in the main chain")
1912                    .map_error(server::error::LegacyCode::InvalidParameter);
1913            }
1914            _ => unreachable!("unmatched response to a block request"),
1915        };
1916
1917        let hash = hash_or_height
1918            .hash_or_else(|_| Some(block.hash()))
1919            .expect("block hash");
1920
1921        let height = hash_or_height
1922            .height_or_else(|_| block.coinbase_height())
1923            .expect("verified blocks have a coinbase height");
1924
1925        let time = u32::try_from(block.header.time.timestamp())
1926            .expect("Timestamps of valid blocks always fit into u32.");
1927
1928        let sapling_nu = zcash_primitives::consensus::NetworkUpgrade::Sapling;
1929        let sapling = if network.is_nu_active(sapling_nu, height.into()) {
1930            match read_state
1931                .ready()
1932                .and_then(|service| {
1933                    service.call(zebra_state::ReadRequest::SaplingTree(hash.into()))
1934                })
1935                .await
1936                .map_misc_error()?
1937            {
1938                zebra_state::ReadResponse::SaplingTree(tree) => {
1939                    tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
1940                }
1941                _ => unreachable!("unmatched response to a Sapling tree request"),
1942            }
1943        } else {
1944            None
1945        };
1946        let (sapling_tree, sapling_root) =
1947            sapling.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
1948
1949        let orchard_nu = zcash_primitives::consensus::NetworkUpgrade::Nu5;
1950        let orchard = if network.is_nu_active(orchard_nu, height.into()) {
1951            match read_state
1952                .ready()
1953                .and_then(|service| {
1954                    service.call(zebra_state::ReadRequest::OrchardTree(hash.into()))
1955                })
1956                .await
1957                .map_misc_error()?
1958            {
1959                zebra_state::ReadResponse::OrchardTree(tree) => {
1960                    tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
1961                }
1962                _ => unreachable!("unmatched response to an Orchard tree request"),
1963            }
1964        } else {
1965            None
1966        };
1967        let (orchard_tree, orchard_root) =
1968            orchard.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
1969
1970        Ok(GetTreestateResponse::new(
1971            hash,
1972            height,
1973            time,
1974            // We can't currently return Sprout data because we don't store it for
1975            // old heights.
1976            None,
1977            Treestate::new(trees::Commitments::new(sapling_root, sapling_tree)),
1978            Treestate::new(trees::Commitments::new(orchard_root, orchard_tree)),
1979        ))
1980    }
1981
1982    async fn z_get_subtrees_by_index(
1983        &self,
1984        pool: String,
1985        start_index: NoteCommitmentSubtreeIndex,
1986        limit: Option<NoteCommitmentSubtreeIndex>,
1987    ) -> Result<GetSubtreesByIndexResponse> {
1988        let mut read_state = self.read_state.clone();
1989
1990        const POOL_LIST: &[&str] = &["sapling", "orchard"];
1991
1992        if pool == "sapling" {
1993            let request = zebra_state::ReadRequest::SaplingSubtrees { start_index, limit };
1994            let response = read_state
1995                .ready()
1996                .and_then(|service| service.call(request))
1997                .await
1998                .map_misc_error()?;
1999
2000            let subtrees = match response {
2001                zebra_state::ReadResponse::SaplingSubtrees(subtrees) => subtrees,
2002                _ => unreachable!("unmatched response to a subtrees request"),
2003            };
2004
2005            let subtrees = subtrees
2006                .values()
2007                .map(|subtree| SubtreeRpcData {
2008                    root: subtree.root.to_bytes().encode_hex(),
2009                    end_height: subtree.end_height,
2010                })
2011                .collect();
2012
2013            Ok(GetSubtreesByIndexResponse {
2014                pool,
2015                start_index,
2016                subtrees,
2017            })
2018        } else if pool == "orchard" {
2019            let request = zebra_state::ReadRequest::OrchardSubtrees { start_index, limit };
2020            let response = read_state
2021                .ready()
2022                .and_then(|service| service.call(request))
2023                .await
2024                .map_misc_error()?;
2025
2026            let subtrees = match response {
2027                zebra_state::ReadResponse::OrchardSubtrees(subtrees) => subtrees,
2028                _ => unreachable!("unmatched response to a subtrees request"),
2029            };
2030
2031            let subtrees = subtrees
2032                .values()
2033                .map(|subtree| SubtreeRpcData {
2034                    root: subtree.root.encode_hex(),
2035                    end_height: subtree.end_height,
2036                })
2037                .collect();
2038
2039            Ok(GetSubtreesByIndexResponse {
2040                pool,
2041                start_index,
2042                subtrees,
2043            })
2044        } else {
2045            Err(ErrorObject::owned(
2046                server::error::LegacyCode::Misc.into(),
2047                format!("invalid pool name, must be one of: {POOL_LIST:?}").as_str(),
2048                None::<()>,
2049            ))
2050        }
2051    }
2052
2053    async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
2054        let mut read_state = self.read_state.clone();
2055        let latest_chain_tip = self.latest_chain_tip.clone();
2056
2057        let height_range = build_height_range(
2058            request.start,
2059            request.end,
2060            best_chain_tip_height(&latest_chain_tip)?,
2061        )?;
2062
2063        let valid_addresses = request.valid_addresses()?;
2064
2065        let request = zebra_state::ReadRequest::TransactionIdsByAddresses {
2066            addresses: valid_addresses,
2067            height_range,
2068        };
2069        let response = read_state
2070            .ready()
2071            .and_then(|service| service.call(request))
2072            .await
2073            .map_misc_error()?;
2074
2075        let hashes = match response {
2076            zebra_state::ReadResponse::AddressesTransactionIds(hashes) => {
2077                let mut last_tx_location = TransactionLocation::from_usize(Height(0), 0);
2078
2079                hashes
2080                    .iter()
2081                    .map(|(tx_loc, tx_id)| {
2082                        // Check that the returned transactions are in chain order.
2083                        assert!(
2084                            *tx_loc > last_tx_location,
2085                            "Transactions were not in chain order:\n\
2086                                 {tx_loc:?} {tx_id:?} was after:\n\
2087                                 {last_tx_location:?}",
2088                        );
2089
2090                        last_tx_location = *tx_loc;
2091
2092                        tx_id.to_string()
2093                    })
2094                    .collect()
2095            }
2096            _ => unreachable!("unmatched response to a TransactionsByAddresses request"),
2097        };
2098
2099        Ok(hashes)
2100    }
2101
2102    async fn get_address_utxos(
2103        &self,
2104        utxos_request: GetAddressUtxosRequest,
2105    ) -> Result<GetAddressUtxosResponse> {
2106        let mut read_state = self.read_state.clone();
2107        let mut response_utxos = vec![];
2108
2109        let valid_addresses = utxos_request.valid_addresses()?;
2110
2111        // get utxos data for addresses
2112        let request = zebra_state::ReadRequest::UtxosByAddresses(valid_addresses);
2113        let response = read_state
2114            .ready()
2115            .and_then(|service| service.call(request))
2116            .await
2117            .map_misc_error()?;
2118        let utxos = match response {
2119            zebra_state::ReadResponse::AddressUtxos(utxos) => utxos,
2120            _ => unreachable!("unmatched response to a UtxosByAddresses request"),
2121        };
2122
2123        let mut last_output_location = OutputLocation::from_usize(Height(0), 0, 0);
2124
2125        for utxo_data in utxos.utxos() {
2126            let address = utxo_data.0;
2127            let txid = *utxo_data.1;
2128            let height = utxo_data.2.height();
2129            let output_index = utxo_data.2.output_index();
2130            let script = utxo_data.3.lock_script.clone();
2131            let satoshis = u64::from(utxo_data.3.value);
2132
2133            let output_location = *utxo_data.2;
2134            // Check that the returned UTXOs are in chain order.
2135            assert!(
2136                output_location > last_output_location,
2137                "UTXOs were not in chain order:\n\
2138                     {output_location:?} {address:?} {txid:?} was after:\n\
2139                     {last_output_location:?}",
2140            );
2141
2142            let entry = Utxo {
2143                address,
2144                txid,
2145                output_index,
2146                script,
2147                satoshis,
2148                height,
2149            };
2150            response_utxos.push(entry);
2151
2152            last_output_location = output_location;
2153        }
2154
2155        if !utxos_request.chain_info {
2156            Ok(GetAddressUtxosResponse::Utxos(response_utxos))
2157        } else {
2158            let (height, hash) = utxos
2159                .last_height_and_hash()
2160                .ok_or_misc_error("No blocks in state")?;
2161
2162            Ok(GetAddressUtxosResponse::UtxosAndChainInfo(
2163                GetAddressUtxosResponseObject {
2164                    utxos: response_utxos,
2165                    hash,
2166                    height,
2167                },
2168            ))
2169        }
2170    }
2171
2172    fn stop(&self) -> Result<String> {
2173        #[cfg(not(target_os = "windows"))]
2174        if self.network.is_regtest() {
2175            match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
2176                Ok(_) => Ok("Zebra server stopping".to_string()),
2177                Err(error) => Err(ErrorObject::owned(
2178                    ErrorCode::InternalError.code(),
2179                    format!("Failed to shut down: {error}").as_str(),
2180                    None::<()>,
2181                )),
2182            }
2183        } else {
2184            Err(ErrorObject::borrowed(
2185                ErrorCode::MethodNotFound.code(),
2186                "stop is only available on regtest networks",
2187                None,
2188            ))
2189        }
2190        #[cfg(target_os = "windows")]
2191        Err(ErrorObject::borrowed(
2192            ErrorCode::MethodNotFound.code(),
2193            "stop is not available in windows targets",
2194            None,
2195        ))
2196    }
2197
2198    fn get_block_count(&self) -> Result<u32> {
2199        best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
2200    }
2201
2202    async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse> {
2203        let mut read_state = self.read_state.clone();
2204        let latest_chain_tip = self.latest_chain_tip.clone();
2205
2206        // TODO: look up this height as part of the state request?
2207        let tip_height = best_chain_tip_height(&latest_chain_tip)?;
2208
2209        let height = height_from_signed_int(index, tip_height)?;
2210
2211        let request = zebra_state::ReadRequest::BestChainBlockHash(height);
2212        let response = read_state
2213            .ready()
2214            .and_then(|service| service.call(request))
2215            .await
2216            .map_error(server::error::LegacyCode::default())?;
2217
2218        match response {
2219            zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHashResponse(hash)),
2220            zebra_state::ReadResponse::BlockHash(None) => Err(ErrorObject::borrowed(
2221                server::error::LegacyCode::InvalidParameter.into(),
2222                "Block not found",
2223                None,
2224            )),
2225            _ => unreachable!("unmatched response to a block request"),
2226        }
2227    }
2228
2229    async fn get_block_template(
2230        &self,
2231        parameters: Option<GetBlockTemplateParameters>,
2232    ) -> Result<GetBlockTemplateResponse> {
2233        use types::get_block_template::{
2234            check_parameters, check_synced_to_tip, fetch_mempool_transactions,
2235            fetch_state_tip_and_local_time, validate_block_proposal,
2236            zip317::select_mempool_transactions,
2237        };
2238
2239        // Clone Configs
2240        let network = self.network.clone();
2241        let extra_coinbase_data = self.gbt.extra_coinbase_data();
2242
2243        // Clone Services
2244        let mempool = self.mempool.clone();
2245        let mut latest_chain_tip = self.latest_chain_tip.clone();
2246        let sync_status = self.gbt.sync_status();
2247        let read_state = self.read_state.clone();
2248
2249        if let Some(HexData(block_proposal_bytes)) = parameters
2250            .as_ref()
2251            .and_then(GetBlockTemplateParameters::block_proposal_data)
2252        {
2253            return validate_block_proposal(
2254                self.gbt.block_verifier_router(),
2255                block_proposal_bytes,
2256                network,
2257                latest_chain_tip,
2258                sync_status,
2259            )
2260            .await;
2261        }
2262
2263        // To implement long polling correctly, we split this RPC into multiple phases.
2264        check_parameters(&parameters)?;
2265
2266        let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id);
2267
2268        let miner_address = self
2269            .gbt
2270            .miner_address()
2271            .ok_or_misc_error("miner_address not configured")?;
2272
2273        // - Checks and fetches that can change during long polling
2274        //
2275        // Set up the loop.
2276        let mut max_time_reached = false;
2277
2278        // The loop returns the server long poll ID,
2279        // which should be different to the client long poll ID.
2280        let (
2281            server_long_poll_id,
2282            chain_tip_and_local_time,
2283            mempool_txs,
2284            mempool_tx_deps,
2285            submit_old,
2286        ) = loop {
2287            // Check if we are synced to the tip.
2288            // The result of this check can change during long polling.
2289            //
2290            // Optional TODO:
2291            // - add `async changed()` method to ChainSyncStatus (like `ChainTip`)
2292            check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
2293            // TODO: return an error if we have no peers, like `zcashd` does,
2294            //       and add a developer config that mines regardless of how many peers we have.
2295            // https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/miner.cpp#L865-L880
2296
2297            // We're just about to fetch state data, then maybe wait for any changes.
2298            // Mark all the changes before the fetch as seen.
2299            // Changes are also ignored in any clones made after the mark.
2300            latest_chain_tip.mark_best_tip_seen();
2301
2302            // Fetch the state data and local time for the block template:
2303            // - if the tip block hash changes, we must return from long polling,
2304            // - if the local clock changes on testnet, we might return from long polling
2305            //
2306            // We always return after 90 minutes on mainnet, even if we have the same response,
2307            // because the max time has been reached.
2308            let chain_tip_and_local_time @ zebra_state::GetBlockTemplateChainInfo {
2309                tip_hash,
2310                tip_height,
2311                max_time,
2312                cur_time,
2313                ..
2314            } = fetch_state_tip_and_local_time(read_state.clone()).await?;
2315
2316            // Fetch the mempool data for the block template:
2317            // - if the mempool transactions change, we might return from long polling.
2318            //
2319            // If the chain fork has just changed, miners want to get the new block as fast
2320            // as possible, rather than wait for transactions to re-verify. This increases
2321            // miner profits (and any delays can cause chain forks). So we don't wait between
2322            // the chain tip changing and getting mempool transactions.
2323            //
2324            // Optional TODO:
2325            // - add a `MempoolChange` type with an `async changed()` method (like `ChainTip`)
2326            let Some((mempool_txs, mempool_tx_deps)) =
2327                fetch_mempool_transactions(mempool.clone(), tip_hash)
2328                    .await?
2329                    // If the mempool and state responses are out of sync:
2330                    // - if we are not long polling, omit mempool transactions from the template,
2331                    // - if we are long polling, continue to the next iteration of the loop to make fresh state and mempool requests.
2332                    .or_else(|| client_long_poll_id.is_none().then(Default::default))
2333            else {
2334                continue;
2335            };
2336
2337            // - Long poll ID calculation
2338            let server_long_poll_id = LongPollInput::new(
2339                tip_height,
2340                tip_hash,
2341                max_time,
2342                mempool_txs.iter().map(|tx| tx.transaction.id),
2343            )
2344            .generate_id();
2345
2346            // The loop finishes if:
2347            // - the client didn't pass a long poll ID,
2348            // - the server long poll ID is different to the client long poll ID, or
2349            // - the previous loop iteration waited until the max time.
2350            if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached {
2351                let mut submit_old = client_long_poll_id
2352                    .as_ref()
2353                    .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id));
2354
2355                // On testnet, the max time changes the block difficulty, so old shares are
2356                // invalid. On mainnet, this means there has been 90 minutes without a new
2357                // block or mempool transaction, which is very unlikely. So the miner should
2358                // probably reset anyway.
2359                if max_time_reached {
2360                    submit_old = Some(false);
2361                }
2362
2363                break (
2364                    server_long_poll_id,
2365                    chain_tip_and_local_time,
2366                    mempool_txs,
2367                    mempool_tx_deps,
2368                    submit_old,
2369                );
2370            }
2371
2372            // - Polling wait conditions
2373            //
2374            // TODO: when we're happy with this code, split it into a function.
2375            //
2376            // Periodically check the mempool for changes.
2377            //
2378            // Optional TODO:
2379            // Remove this polling wait if we switch to using futures to detect sync status
2380            // and mempool changes.
2381            let wait_for_mempool_request =
2382                tokio::time::sleep(Duration::from_secs(MEMPOOL_LONG_POLL_INTERVAL));
2383
2384            // Return immediately if the chain tip has changed.
2385            // The clone preserves the seen status of the chain tip.
2386            let mut wait_for_best_tip_change = latest_chain_tip.clone();
2387            let wait_for_best_tip_change = wait_for_best_tip_change.best_tip_changed();
2388
2389            // Wait for the maximum block time to elapse. This can change the block header
2390            // on testnet. (On mainnet it can happen due to a network disconnection, or a
2391            // rapid drop in hash rate.)
2392            //
2393            // This duration might be slightly lower than the actual maximum,
2394            // if cur_time was clamped to min_time. In that case the wait is very long,
2395            // and it's ok to return early.
2396            //
2397            // It can also be zero if cur_time was clamped to max_time. In that case,
2398            // we want to wait for another change, and ignore this timeout. So we use an
2399            // `OptionFuture::None`.
2400            let duration_until_max_time = max_time.saturating_duration_since(cur_time);
2401            let wait_for_max_time: OptionFuture<_> = if duration_until_max_time.seconds() > 0 {
2402                Some(tokio::time::sleep(duration_until_max_time.to_std()))
2403            } else {
2404                None
2405            }
2406            .into();
2407
2408            // Optional TODO:
2409            // `zcashd` generates the next coinbase transaction while waiting for changes.
2410            // When Zebra supports shielded coinbase, we might want to do this in parallel.
2411            // But the coinbase value depends on the selected transactions, so this needs
2412            // further analysis to check if it actually saves us any time.
2413
2414            tokio::select! {
2415                // Poll the futures in the listed order, for efficiency.
2416                // We put the most frequent conditions first.
2417                biased;
2418
2419                // This timer elapses every few seconds
2420                _elapsed = wait_for_mempool_request => {
2421                    tracing::debug!(
2422                        ?max_time,
2423                        ?cur_time,
2424                        ?server_long_poll_id,
2425                        ?client_long_poll_id,
2426                        MEMPOOL_LONG_POLL_INTERVAL,
2427                        "checking for a new mempool change after waiting a few seconds"
2428                    );
2429                }
2430
2431                // The state changes after around a target block interval (75s)
2432                tip_changed_result = wait_for_best_tip_change => {
2433                    match tip_changed_result {
2434                        Ok(()) => {
2435                            // Spurious updates shouldn't happen in the state, because the
2436                            // difficulty and hash ordering is a stable total order. But
2437                            // since they could cause a busy-loop, guard against them here.
2438                            latest_chain_tip.mark_best_tip_seen();
2439
2440                            let new_tip_hash = latest_chain_tip.best_tip_hash();
2441                            if new_tip_hash == Some(tip_hash) {
2442                                tracing::debug!(
2443                                    ?max_time,
2444                                    ?cur_time,
2445                                    ?server_long_poll_id,
2446                                    ?client_long_poll_id,
2447                                    ?tip_hash,
2448                                    ?tip_height,
2449                                    "ignoring spurious state change notification"
2450                                );
2451
2452                                // Wait for the mempool interval, then check for any changes.
2453                                tokio::time::sleep(Duration::from_secs(
2454                                    MEMPOOL_LONG_POLL_INTERVAL,
2455                                )).await;
2456
2457                                continue;
2458                            }
2459
2460                            tracing::debug!(
2461                                ?max_time,
2462                                ?cur_time,
2463                                ?server_long_poll_id,
2464                                ?client_long_poll_id,
2465                                "returning from long poll because state has changed"
2466                            );
2467                        }
2468
2469                        Err(recv_error) => {
2470                            // This log is rare and helps with debugging, so it's ok to be info.
2471                            tracing::info!(
2472                                ?recv_error,
2473                                ?max_time,
2474                                ?cur_time,
2475                                ?server_long_poll_id,
2476                                ?client_long_poll_id,
2477                                "returning from long poll due to a state error.\
2478                                Is Zebra shutting down?"
2479                            );
2480
2481                            return Err(recv_error).map_error(server::error::LegacyCode::default());
2482                        }
2483                    }
2484                }
2485
2486                // The max time does not elapse during normal operation on mainnet,
2487                // and it rarely elapses on testnet.
2488                Some(_elapsed) = wait_for_max_time => {
2489                    // This log is very rare so it's ok to be info.
2490                    tracing::info!(
2491                        ?max_time,
2492                        ?cur_time,
2493                        ?server_long_poll_id,
2494                        ?client_long_poll_id,
2495                        "returning from long poll because max time was reached"
2496                    );
2497
2498                    max_time_reached = true;
2499                }
2500            }
2501        };
2502
2503        // - Processing fetched data to create a transaction template
2504        //
2505        // Apart from random weighted transaction selection,
2506        // the template only depends on the previously fetched data.
2507        // This processing never fails.
2508
2509        // Calculate the next block height.
2510        let next_block_height =
2511            (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX");
2512
2513        tracing::debug!(
2514            mempool_tx_hashes = ?mempool_txs
2515                .iter()
2516                .map(|tx| tx.transaction.id.mined_id())
2517                .collect::<Vec<_>>(),
2518            "selecting transactions for the template from the mempool"
2519        );
2520
2521        // Randomly select some mempool transactions.
2522        let mempool_txs = select_mempool_transactions(
2523            &network,
2524            next_block_height,
2525            &miner_address,
2526            mempool_txs,
2527            mempool_tx_deps,
2528            extra_coinbase_data.clone(),
2529        );
2530
2531        tracing::debug!(
2532            selected_mempool_tx_hashes = ?mempool_txs
2533                .iter()
2534                .map(|#[cfg(not(test))] tx, #[cfg(test)] (_, tx)| tx.transaction.id.mined_id())
2535                .collect::<Vec<_>>(),
2536            "selected transactions for the template from the mempool"
2537        );
2538
2539        // - After this point, the template only depends on the previously fetched data.
2540
2541        let response = BlockTemplateResponse::new_internal(
2542            &network,
2543            &miner_address,
2544            &chain_tip_and_local_time,
2545            server_long_poll_id,
2546            mempool_txs,
2547            submit_old,
2548            extra_coinbase_data,
2549        );
2550
2551        Ok(response.into())
2552    }
2553
2554    async fn submit_block(
2555        &self,
2556        HexData(block_bytes): HexData,
2557        _parameters: Option<SubmitBlockParameters>,
2558    ) -> Result<SubmitBlockResponse> {
2559        let mut block_verifier_router = self.gbt.block_verifier_router();
2560
2561        let block: Block = match block_bytes.zcash_deserialize_into() {
2562            Ok(block_bytes) => block_bytes,
2563            Err(error) => {
2564                tracing::info!(
2565                    ?error,
2566                    "submit block failed: block bytes could not be deserialized into a structurally valid block"
2567                );
2568
2569                return Ok(SubmitBlockErrorResponse::Rejected.into());
2570            }
2571        };
2572
2573        let height = block
2574            .coinbase_height()
2575            .ok_or_error(0, "coinbase height not found")?;
2576        let block_hash = block.hash();
2577
2578        let block_verifier_router_response = block_verifier_router
2579            .ready()
2580            .await
2581            .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
2582            .call(zebra_consensus::Request::Commit(Arc::new(block)))
2583            .await;
2584
2585        let chain_error = match block_verifier_router_response {
2586            // Currently, this match arm returns `null` (Accepted) for blocks committed
2587            // to any chain, but Accepted is only for blocks in the best chain.
2588            //
2589            // TODO (#5487):
2590            // - Inconclusive: check if the block is on a side-chain
2591            // The difference is important to miners, because they want to mine on the best chain.
2592            Ok(hash) => {
2593                tracing::info!(?hash, ?height, "submit block accepted");
2594
2595                self.gbt
2596                    .advertise_mined_block(hash, height)
2597                    .map_error_with_prefix(0, "failed to send mined block to gossip task")?;
2598
2599                return Ok(SubmitBlockResponse::Accepted);
2600            }
2601
2602            // Turns BoxError into Result<VerifyChainError, BoxError>,
2603            // by downcasting from Any to VerifyChainError.
2604            Err(box_error) => {
2605                let error = box_error
2606                    .downcast::<RouterError>()
2607                    .map(|boxed_chain_error| *boxed_chain_error);
2608
2609                tracing::info!(
2610                    ?error,
2611                    ?block_hash,
2612                    ?height,
2613                    "submit block failed verification"
2614                );
2615
2616                error
2617            }
2618        };
2619
2620        let response = match chain_error {
2621            Ok(source) if source.is_duplicate_request() => SubmitBlockErrorResponse::Duplicate,
2622
2623            // Currently, these match arms return Reject for the older duplicate in a queue,
2624            // but queued duplicates should be DuplicateInconclusive.
2625            //
2626            // Optional TODO (#5487):
2627            // - DuplicateInconclusive: turn these non-finalized state duplicate block errors
2628            //   into BlockError enum variants, and handle them as DuplicateInconclusive:
2629            //   - "block already sent to be committed to the state"
2630            //   - "replaced by newer request"
2631            // - keep the older request in the queue,
2632            //   and return a duplicate error for the newer request immediately.
2633            //   This improves the speed of the RPC response.
2634            //
2635            // Checking the download queues and BlockVerifierRouter buffer for duplicates
2636            // might require architectural changes to Zebra, so we should only do it
2637            // if mining pools really need it.
2638            Ok(_verify_chain_error) => SubmitBlockErrorResponse::Rejected,
2639
2640            // This match arm is currently unreachable, but if future changes add extra error types,
2641            // we want to turn them into `Rejected`.
2642            Err(_unknown_error_type) => SubmitBlockErrorResponse::Rejected,
2643        };
2644
2645        Ok(response.into())
2646    }
2647
2648    async fn get_mining_info(&self) -> Result<GetMiningInfoResponse> {
2649        let network = self.network.clone();
2650        let mut read_state = self.read_state.clone();
2651
2652        let chain_tip = self.latest_chain_tip.clone();
2653        let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0;
2654
2655        let mut current_block_tx = None;
2656        if tip_height > 0 {
2657            let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids();
2658            current_block_tx =
2659                (!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1));
2660        }
2661
2662        let solution_rate_fut = self.get_network_sol_ps(None, None);
2663        // Get the current block size.
2664        let mut current_block_size = None;
2665        if tip_height > 0 {
2666            let request = zebra_state::ReadRequest::TipBlockSize;
2667            let response: zebra_state::ReadResponse = read_state
2668                .ready()
2669                .and_then(|service| service.call(request))
2670                .await
2671                .map_error(server::error::LegacyCode::default())?;
2672            current_block_size = match response {
2673                zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size),
2674                _ => None,
2675            };
2676        }
2677
2678        Ok(GetMiningInfoResponse::new_internal(
2679            tip_height,
2680            current_block_size,
2681            current_block_tx,
2682            network,
2683            solution_rate_fut.await?,
2684        ))
2685    }
2686
2687    async fn get_network_sol_ps(
2688        &self,
2689        num_blocks: Option<i32>,
2690        height: Option<i32>,
2691    ) -> Result<u64> {
2692        // Default number of blocks is 120 if not supplied.
2693        let mut num_blocks = num_blocks.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
2694        // But if it is 0 or negative, it uses the proof of work averaging window.
2695        if num_blocks < 1 {
2696            num_blocks = i32::try_from(POW_AVERAGING_WINDOW).expect("fits in i32");
2697        }
2698        let num_blocks =
2699            usize::try_from(num_blocks).expect("just checked for negatives, i32 fits in usize");
2700
2701        // Default height is the tip height if not supplied. Negative values also mean the tip
2702        // height. Since negative values aren't valid heights, we can just use the conversion.
2703        let height = height.and_then(|height| height.try_into_height().ok());
2704
2705        let mut read_state = self.read_state.clone();
2706
2707        let request = ReadRequest::SolutionRate { num_blocks, height };
2708
2709        let response = read_state
2710            .ready()
2711            .and_then(|service| service.call(request))
2712            .await
2713            .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2714
2715        let solution_rate = match response {
2716            // zcashd returns a 0 rate when the calculation is invalid
2717            ReadResponse::SolutionRate(solution_rate) => solution_rate.unwrap_or(0),
2718
2719            _ => unreachable!("unmatched response to a solution rate request"),
2720        };
2721
2722        Ok(solution_rate
2723            .try_into()
2724            .expect("per-second solution rate always fits in u64"))
2725    }
2726
2727    async fn get_network_info(&self) -> Result<GetNetworkInfoResponse> {
2728        let version = GetInfoResponse::version_from_string(&self.build_version)
2729            .expect("invalid version string");
2730
2731        let subversion = self.user_agent.clone();
2732
2733        let protocol_version = zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0;
2734
2735        // TODO: return actual supported local services when Zebra exposes them
2736        let local_services = format!("{:016x}", PeerServices::NODE_NETWORK);
2737
2738        // Deprecated: zcashd always returns 0.
2739        let timeoffset = 0;
2740
2741        let connections = self.address_book.recently_live_peers(Utc::now()).len();
2742
2743        // TODO: make `limited`, `reachable`, and `proxy` dynamic if Zebra supports network filtering
2744        let networks = vec![
2745            NetworkInfo::new("ipv4".to_string(), false, true, "".to_string(), false),
2746            NetworkInfo::new("ipv6".to_string(), false, true, "".to_string(), false),
2747            NetworkInfo::new("onion".to_string(), false, false, "".to_string(), false),
2748        ];
2749
2750        let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
2751            / (zebra_chain::amount::COIN as f64);
2752
2753        // TODO: populate local addresses when Zebra supports exposing bound or advertised addresses
2754        let local_addresses = vec![];
2755
2756        // TODO: return network-level warnings, if Zebra supports them in the future
2757        let warnings = "".to_string();
2758
2759        let response = GetNetworkInfoResponse {
2760            version,
2761            subversion,
2762            protocol_version,
2763            local_services,
2764            timeoffset,
2765            connections,
2766            networks,
2767            relay_fee,
2768            local_addresses,
2769            warnings,
2770        };
2771
2772        Ok(response)
2773    }
2774
2775    async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
2776        let address_book = self.address_book.clone();
2777        Ok(address_book
2778            .recently_live_peers(chrono::Utc::now())
2779            .into_iter()
2780            .map(PeerInfo::from)
2781            .collect())
2782    }
2783
2784    async fn validate_address(&self, raw_address: String) -> Result<ValidateAddressResponse> {
2785        let network = self.network.clone();
2786
2787        validate_address(network, raw_address)
2788    }
2789
2790    async fn z_validate_address(&self, raw_address: String) -> Result<ZValidateAddressResponse> {
2791        let network = self.network.clone();
2792
2793        z_validate_address(network, raw_address)
2794    }
2795
2796    async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse> {
2797        let latest_chain_tip = self.latest_chain_tip.clone();
2798        let network = self.network.clone();
2799
2800        let height = if let Some(height) = height {
2801            Height(height)
2802        } else {
2803            best_chain_tip_height(&latest_chain_tip)?
2804        };
2805
2806        if height < network.height_for_first_halving() {
2807            return Err(ErrorObject::borrowed(
2808                0,
2809                "Zebra does not support founders' reward subsidies, \
2810                        use a block height that is after the first halving",
2811                None,
2812            ));
2813        }
2814
2815        // Always zero for post-halving blocks
2816        let founders = Amount::zero();
2817
2818        let total_block_subsidy =
2819            block_subsidy(height, &network).map_error(server::error::LegacyCode::default())?;
2820        let miner_subsidy = miner_subsidy(height, &network, total_block_subsidy)
2821            .map_error(server::error::LegacyCode::default())?;
2822
2823        let (lockbox_streams, mut funding_streams): (Vec<_>, Vec<_>) =
2824            funding_stream_values(height, &network, total_block_subsidy)
2825                .map_error(server::error::LegacyCode::default())?
2826                .into_iter()
2827                // Separate the funding streams into deferred and non-deferred streams
2828                .partition(|(receiver, _)| matches!(receiver, FundingStreamReceiver::Deferred));
2829
2830        let is_nu6 = NetworkUpgrade::current(&network, height) == NetworkUpgrade::Nu6;
2831
2832        let [lockbox_total, funding_streams_total]: [std::result::Result<
2833            Amount<NonNegative>,
2834            amount::Error,
2835        >; 2] = [&lockbox_streams, &funding_streams]
2836            .map(|streams| streams.iter().map(|&(_, amount)| amount).sum());
2837
2838        // Use the same funding stream order as zcashd
2839        funding_streams.sort_by_key(|(receiver, _funding_stream)| {
2840            ZCASHD_FUNDING_STREAM_ORDER
2841                .iter()
2842                .position(|zcashd_receiver| zcashd_receiver == receiver)
2843        });
2844
2845        // Format the funding streams and lockbox streams
2846        let [funding_streams, lockbox_streams]: [Vec<_>; 2] = [funding_streams, lockbox_streams]
2847            .map(|streams| {
2848                streams
2849                    .into_iter()
2850                    .map(|(receiver, value)| {
2851                        let address = funding_stream_address(height, &network, receiver);
2852                        types::subsidy::FundingStream::new_internal(
2853                            is_nu6, receiver, value, address,
2854                        )
2855                    })
2856                    .collect()
2857            });
2858
2859        Ok(GetBlockSubsidyResponse {
2860            miner: miner_subsidy.into(),
2861            founders: founders.into(),
2862            funding_streams,
2863            lockbox_streams,
2864            funding_streams_total: funding_streams_total
2865                .map_error(server::error::LegacyCode::default())?
2866                .into(),
2867            lockbox_total: lockbox_total
2868                .map_error(server::error::LegacyCode::default())?
2869                .into(),
2870            total_block_subsidy: total_block_subsidy.into(),
2871        })
2872    }
2873
2874    async fn get_difficulty(&self) -> Result<f64> {
2875        chain_tip_difficulty(self.network.clone(), self.read_state.clone(), false).await
2876    }
2877
2878    async fn z_list_unified_receivers(
2879        &self,
2880        address: String,
2881    ) -> Result<ZListUnifiedReceiversResponse> {
2882        use zcash_address::unified::Container;
2883
2884        let (network, unified_address): (
2885            zcash_protocol::consensus::NetworkType,
2886            zcash_address::unified::Address,
2887        ) = zcash_address::unified::Encoding::decode(address.clone().as_str())
2888            .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2889
2890        let mut p2pkh = None;
2891        let mut p2sh = None;
2892        let mut orchard = None;
2893        let mut sapling = None;
2894
2895        for item in unified_address.items() {
2896            match item {
2897                zcash_address::unified::Receiver::Orchard(_data) => {
2898                    let addr = zcash_address::unified::Address::try_from_items(vec![item])
2899                        .expect("using data already decoded as valid");
2900                    orchard = Some(addr.encode(&network));
2901                }
2902                zcash_address::unified::Receiver::Sapling(data) => {
2903                    let addr = zebra_chain::primitives::Address::try_from_sapling(network, data)
2904                        .expect("using data already decoded as valid");
2905                    sapling = Some(addr.payment_address().unwrap_or_default());
2906                }
2907                zcash_address::unified::Receiver::P2pkh(data) => {
2908                    let addr =
2909                        zebra_chain::primitives::Address::try_from_transparent_p2pkh(network, data)
2910                            .expect("using data already decoded as valid");
2911                    p2pkh = Some(addr.payment_address().unwrap_or_default());
2912                }
2913                zcash_address::unified::Receiver::P2sh(data) => {
2914                    let addr =
2915                        zebra_chain::primitives::Address::try_from_transparent_p2sh(network, data)
2916                            .expect("using data already decoded as valid");
2917                    p2sh = Some(addr.payment_address().unwrap_or_default());
2918                }
2919                _ => (),
2920            }
2921        }
2922
2923        Ok(ZListUnifiedReceiversResponse::new(
2924            orchard, sapling, p2pkh, p2sh,
2925        ))
2926    }
2927
2928    async fn invalidate_block(&self, block_hash: String) -> Result<()> {
2929        let block_hash = block_hash
2930            .parse()
2931            .map_error(server::error::LegacyCode::InvalidParameter)?;
2932
2933        self.state
2934            .clone()
2935            .oneshot(zebra_state::Request::InvalidateBlock(block_hash))
2936            .await
2937            .map(|rsp| assert_eq!(rsp, zebra_state::Response::Invalidated(block_hash)))
2938            .map_misc_error()
2939    }
2940
2941    async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>> {
2942        let block_hash = block_hash
2943            .parse()
2944            .map_error(server::error::LegacyCode::InvalidParameter)?;
2945
2946        self.state
2947            .clone()
2948            .oneshot(zebra_state::Request::ReconsiderBlock(block_hash))
2949            .await
2950            .map(|rsp| match rsp {
2951                zebra_state::Response::Reconsidered(block_hashes) => block_hashes,
2952                _ => unreachable!("unmatched response to a reconsider block request"),
2953            })
2954            .map_misc_error()
2955    }
2956
2957    async fn generate(&self, num_blocks: u32) -> Result<Vec<Hash>> {
2958        let mut rpc = self.clone();
2959        let network = self.network.clone();
2960
2961        if !network.disable_pow() {
2962            return Err(ErrorObject::borrowed(
2963                0,
2964                "generate is only supported on networks where PoW is disabled",
2965                None,
2966            ));
2967        }
2968
2969        let mut block_hashes = Vec::new();
2970        for _ in 0..num_blocks {
2971            // Use random coinbase data in order to ensure the coinbase
2972            // transaction is unique. This is useful for tests that exercise
2973            // forks, since otherwise the coinbase txs of blocks with the same
2974            // height across different forks would be identical.
2975            let mut extra_coinbase_data = [0u8; 32];
2976            OsRng.fill_bytes(&mut extra_coinbase_data);
2977            rpc.gbt
2978                .set_extra_coinbase_data(extra_coinbase_data.to_vec());
2979
2980            let block_template = rpc
2981                .get_block_template(None)
2982                .await
2983                .map_error(server::error::LegacyCode::default())?;
2984
2985            let GetBlockTemplateResponse::TemplateMode(block_template) = block_template else {
2986                return Err(ErrorObject::borrowed(
2987                    0,
2988                    "error generating block template",
2989                    None,
2990                ));
2991            };
2992
2993            let proposal_block = proposal_block_from_template(
2994                &block_template,
2995                BlockTemplateTimeSource::CurTime,
2996                &network,
2997            )
2998            .map_error(server::error::LegacyCode::default())?;
2999
3000            let hex_proposal_block = HexData(
3001                proposal_block
3002                    .zcash_serialize_to_vec()
3003                    .map_error(server::error::LegacyCode::default())?,
3004            );
3005
3006            let r = rpc
3007                .submit_block(hex_proposal_block, None)
3008                .await
3009                .map_error(server::error::LegacyCode::default())?;
3010            match r {
3011                SubmitBlockResponse::Accepted => { /* pass */ }
3012                SubmitBlockResponse::ErrorResponse(response) => {
3013                    return Err(ErrorObject::owned(
3014                        server::error::LegacyCode::Misc.into(),
3015                        format!("block was rejected: {:?}", response),
3016                        None::<()>,
3017                    ));
3018                }
3019            }
3020
3021            block_hashes.push(GetBlockHashResponse(proposal_block.hash()));
3022        }
3023
3024        Ok(block_hashes)
3025    }
3026
3027    async fn add_node(
3028        &self,
3029        addr: zebra_network::PeerSocketAddr,
3030        command: AddNodeCommand,
3031    ) -> Result<()> {
3032        if self.network.is_regtest() {
3033            match command {
3034                AddNodeCommand::Add => {
3035                    tracing::info!(?addr, "adding peer address to the address book");
3036                    if self.address_book.clone().add_peer(addr) {
3037                        Ok(())
3038                    } else {
3039                        return Err(ErrorObject::owned(
3040                            server::error::LegacyCode::ClientNodeAlreadyAdded.into(),
3041                            format!("peer address was already present in the address book: {addr}"),
3042                            None::<()>,
3043                        ));
3044                    }
3045                }
3046            }
3047        } else {
3048            return Err(ErrorObject::owned(
3049                ErrorCode::InvalidParams.code(),
3050                "addnode command is only supported on regtest",
3051                None::<()>,
3052            ));
3053        }
3054    }
3055}
3056
3057// TODO: Move the code below to separate modules.
3058
3059/// Returns the best chain tip height of `latest_chain_tip`,
3060/// or an RPC error if there are no blocks in the state.
3061pub fn best_chain_tip_height<Tip>(latest_chain_tip: &Tip) -> Result<Height>
3062where
3063    Tip: ChainTip + Clone + Send + Sync + 'static,
3064{
3065    latest_chain_tip
3066        .best_tip_height()
3067        .ok_or_misc_error("No blocks in state")
3068}
3069
3070/// Response to a `getinfo` RPC request.
3071///
3072/// See the notes for the [`Rpc::get_info` method].
3073#[allow(clippy::too_many_arguments)]
3074#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3075pub struct GetInfoResponse {
3076    /// The node version
3077    #[getter(rename = "raw_version")]
3078    version: u64,
3079
3080    /// The node version build number
3081    build: String,
3082
3083    /// The server sub-version identifier, used as the network protocol user-agent
3084    subversion: String,
3085
3086    /// The protocol version
3087    #[serde(rename = "protocolversion")]
3088    protocol_version: u32,
3089
3090    /// The current number of blocks processed in the server
3091    blocks: u32,
3092
3093    /// The total (inbound and outbound) number of connections the node has
3094    connections: usize,
3095
3096    /// The proxy (if any) used by the server. Currently always `None` in Zebra.
3097    #[serde(skip_serializing_if = "Option::is_none")]
3098    proxy: Option<String>,
3099
3100    /// The current network difficulty
3101    difficulty: f64,
3102
3103    /// True if the server is running in testnet mode, false otherwise
3104    testnet: bool,
3105
3106    /// The minimum transaction fee in ZEC/kB
3107    #[serde(rename = "paytxfee")]
3108    pay_tx_fee: f64,
3109
3110    /// The minimum relay fee for non-free transactions in ZEC/kB
3111    #[serde(rename = "relayfee")]
3112    relay_fee: f64,
3113
3114    /// The last error or warning message, or "no errors" if there are no errors
3115    errors: String,
3116
3117    /// The time of the last error or warning message, or "no errors timestamp" if there are no errors
3118    #[serde(rename = "errorstimestamp")]
3119    errors_timestamp: String,
3120}
3121
3122#[deprecated(note = "Use `GetInfoResponse` instead")]
3123pub use self::GetInfoResponse as GetInfo;
3124
3125impl Default for GetInfoResponse {
3126    fn default() -> Self {
3127        GetInfoResponse {
3128            version: 0,
3129            build: "some build version".to_string(),
3130            subversion: "some subversion".to_string(),
3131            protocol_version: 0,
3132            blocks: 0,
3133            connections: 0,
3134            proxy: None,
3135            difficulty: 0.0,
3136            testnet: false,
3137            pay_tx_fee: 0.0,
3138            relay_fee: 0.0,
3139            errors: "no errors".to_string(),
3140            errors_timestamp: "no errors timestamp".to_string(),
3141        }
3142    }
3143}
3144
3145impl GetInfoResponse {
3146    /// Constructs [`GetInfo`] from its constituent parts.
3147    #[allow(clippy::too_many_arguments)]
3148    #[deprecated(note = "Use `GetInfoResponse::new` instead")]
3149    pub fn from_parts(
3150        version: u64,
3151        build: String,
3152        subversion: String,
3153        protocol_version: u32,
3154        blocks: u32,
3155        connections: usize,
3156        proxy: Option<String>,
3157        difficulty: f64,
3158        testnet: bool,
3159        pay_tx_fee: f64,
3160        relay_fee: f64,
3161        errors: String,
3162        errors_timestamp: String,
3163    ) -> Self {
3164        Self {
3165            version,
3166            build,
3167            subversion,
3168            protocol_version,
3169            blocks,
3170            connections,
3171            proxy,
3172            difficulty,
3173            testnet,
3174            pay_tx_fee,
3175            relay_fee,
3176            errors,
3177            errors_timestamp,
3178        }
3179    }
3180
3181    /// Returns the contents of ['GetInfo'].
3182    pub fn into_parts(
3183        self,
3184    ) -> (
3185        u64,
3186        String,
3187        String,
3188        u32,
3189        u32,
3190        usize,
3191        Option<String>,
3192        f64,
3193        bool,
3194        f64,
3195        f64,
3196        String,
3197        String,
3198    ) {
3199        (
3200            self.version,
3201            self.build,
3202            self.subversion,
3203            self.protocol_version,
3204            self.blocks,
3205            self.connections,
3206            self.proxy,
3207            self.difficulty,
3208            self.testnet,
3209            self.pay_tx_fee,
3210            self.relay_fee,
3211            self.errors,
3212            self.errors_timestamp,
3213        )
3214    }
3215
3216    /// Create the node version number.
3217    fn version_from_string(build_string: &str) -> Option<u64> {
3218        let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
3219        let build_number = semver_version
3220            .build
3221            .as_str()
3222            .split('.')
3223            .next()
3224            .and_then(|num_str| num_str.parse::<u64>().ok())
3225            .unwrap_or_default();
3226
3227        // https://github.com/zcash/zcash/blob/v6.1.0/src/clientversion.h#L55-L59
3228        let version_number = 1_000_000 * semver_version.major
3229            + 10_000 * semver_version.minor
3230            + 100 * semver_version.patch
3231            + build_number;
3232
3233        Some(version_number)
3234    }
3235}
3236
3237/// Type alias for the array of `GetBlockchainInfoBalance` objects
3238pub type BlockchainValuePoolBalances = [GetBlockchainInfoBalance; 5];
3239
3240/// Response to a `getblockchaininfo` RPC request.
3241///
3242/// See the notes for the [`Rpc::get_blockchain_info` method].
3243#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters)]
3244pub struct GetBlockchainInfoResponse {
3245    /// Current network name as defined in BIP70 (main, test, regtest)
3246    chain: String,
3247
3248    /// The current number of blocks processed in the server, numeric
3249    #[getter(copy)]
3250    blocks: Height,
3251
3252    /// The current number of headers we have validated in the best chain, that is,
3253    /// the height of the best chain.
3254    #[getter(copy)]
3255    headers: Height,
3256
3257    /// The estimated network solution rate in Sol/s.
3258    difficulty: f64,
3259
3260    /// The verification progress relative to the estimated network chain tip.
3261    #[serde(rename = "verificationprogress")]
3262    verification_progress: f64,
3263
3264    /// The total amount of work in the best chain, hex-encoded.
3265    #[serde(rename = "chainwork")]
3266    chain_work: u64,
3267
3268    /// Whether this node is pruned, currently always false in Zebra.
3269    pruned: bool,
3270
3271    /// The estimated size of the block and undo files on disk
3272    size_on_disk: u64,
3273
3274    /// The current number of note commitments in the commitment tree
3275    commitments: u64,
3276
3277    /// The hash of the currently best block, in big-endian order, hex-encoded
3278    #[serde(rename = "bestblockhash", with = "hex")]
3279    #[getter(copy)]
3280    best_block_hash: block::Hash,
3281
3282    /// If syncing, the estimated height of the chain, else the current best height, numeric.
3283    ///
3284    /// In Zebra, this is always the height estimate, so it might be a little inaccurate.
3285    #[serde(rename = "estimatedheight")]
3286    #[getter(copy)]
3287    estimated_height: Height,
3288
3289    /// Chain supply balance
3290    #[serde(rename = "chainSupply")]
3291    chain_supply: GetBlockchainInfoBalance,
3292
3293    /// Value pool balances
3294    #[serde(rename = "valuePools")]
3295    value_pools: BlockchainValuePoolBalances,
3296
3297    /// Status of network upgrades
3298    upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3299
3300    /// Branch IDs of the current and upcoming consensus rules
3301    #[getter(copy)]
3302    consensus: TipConsensusBranch,
3303}
3304
3305impl Default for GetBlockchainInfoResponse {
3306    fn default() -> Self {
3307        Self {
3308            chain: "main".to_string(),
3309            blocks: Height(1),
3310            best_block_hash: block::Hash([0; 32]),
3311            estimated_height: Height(1),
3312            chain_supply: GetBlockchainInfoBalance::chain_supply(Default::default()),
3313            value_pools: GetBlockchainInfoBalance::zero_pools(),
3314            upgrades: IndexMap::new(),
3315            consensus: TipConsensusBranch {
3316                chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
3317                next_block: ConsensusBranchIdHex(ConsensusBranchId::default()),
3318            },
3319            headers: Height(1),
3320            difficulty: 0.0,
3321            verification_progress: 0.0,
3322            chain_work: 0,
3323            pruned: false,
3324            size_on_disk: 0,
3325            commitments: 0,
3326        }
3327    }
3328}
3329
3330impl GetBlockchainInfoResponse {
3331    /// Creates a new [`GetBlockchainInfoResponse`] instance.
3332    // We don't use derive(new) because the method already existed but the arguments
3333    // have a different order. No reason to unnecessarily break existing code.
3334    #[allow(clippy::too_many_arguments)]
3335    pub fn new(
3336        chain: String,
3337        blocks: Height,
3338        best_block_hash: block::Hash,
3339        estimated_height: Height,
3340        chain_supply: GetBlockchainInfoBalance,
3341        value_pools: BlockchainValuePoolBalances,
3342        upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3343        consensus: TipConsensusBranch,
3344        headers: Height,
3345        difficulty: f64,
3346        verification_progress: f64,
3347        chain_work: u64,
3348        pruned: bool,
3349        size_on_disk: u64,
3350        commitments: u64,
3351    ) -> Self {
3352        Self {
3353            chain,
3354            blocks,
3355            best_block_hash,
3356            estimated_height,
3357            chain_supply,
3358            value_pools,
3359            upgrades,
3360            consensus,
3361            headers,
3362            difficulty,
3363            verification_progress,
3364            chain_work,
3365            pruned,
3366            size_on_disk,
3367            commitments,
3368        }
3369    }
3370}
3371
3372/// A request for [`RpcServer::get_address_balance`].
3373#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
3374#[serde(from = "DGetAddressBalanceRequest")]
3375pub struct GetAddressBalanceRequest {
3376    /// A list of transparent address strings.
3377    addresses: Vec<String>,
3378}
3379
3380impl From<DGetAddressBalanceRequest> for GetAddressBalanceRequest {
3381    fn from(address_strings: DGetAddressBalanceRequest) -> Self {
3382        match address_strings {
3383            DGetAddressBalanceRequest::Addresses { addresses } => {
3384                GetAddressBalanceRequest { addresses }
3385            }
3386            DGetAddressBalanceRequest::Address(address) => GetAddressBalanceRequest {
3387                addresses: vec![address],
3388            },
3389        }
3390    }
3391}
3392
3393/// An intermediate type used to deserialize [`AddressStrings`].
3394#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize)]
3395#[serde(untagged)]
3396enum DGetAddressBalanceRequest {
3397    /// A list of address strings.
3398    Addresses { addresses: Vec<String> },
3399    /// A single address string.
3400    Address(String),
3401}
3402
3403/// A request to get the transparent balance of a set of addresses.
3404#[deprecated(note = "Use `GetAddressBalanceRequest` instead.")]
3405pub type AddressStrings = GetAddressBalanceRequest;
3406
3407trait ValidateAddresses {
3408    /// Given a list of addresses as strings:
3409    /// - check if provided list have all valid transparent addresses.
3410    /// - return valid addresses as a set of `Address`.
3411    fn valid_addresses(&self) -> Result<HashSet<Address>> {
3412        // Reference for the legacy error code:
3413        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/misc.cpp#L783-L784>
3414        let valid_addresses: HashSet<Address> = self
3415            .addresses()
3416            .iter()
3417            .map(|address| {
3418                address
3419                    .parse()
3420                    .map_error(server::error::LegacyCode::InvalidAddressOrKey)
3421            })
3422            .collect::<Result<_>>()?;
3423
3424        Ok(valid_addresses)
3425    }
3426
3427    /// Returns string-encoded Zcash addresses in the type implementing this trait.
3428    fn addresses(&self) -> &[String];
3429}
3430
3431impl ValidateAddresses for GetAddressBalanceRequest {
3432    fn addresses(&self) -> &[String] {
3433        &self.addresses
3434    }
3435}
3436
3437impl GetAddressBalanceRequest {
3438    /// Creates a new `AddressStrings` given a vector.
3439    pub fn new(addresses: Vec<String>) -> GetAddressBalanceRequest {
3440        GetAddressBalanceRequest { addresses }
3441    }
3442
3443    /// Creates a new [`AddressStrings`] from a given vector, returns an error if any addresses are incorrect.
3444    #[deprecated(
3445        note = "Use `AddressStrings::new` instead. Validity will be checked by the server."
3446    )]
3447    pub fn new_valid(addresses: Vec<String>) -> Result<GetAddressBalanceRequest> {
3448        let req = Self { addresses };
3449        req.valid_addresses()?;
3450        Ok(req)
3451    }
3452}
3453
3454/// The transparent balance of a set of addresses.
3455#[derive(
3456    Clone,
3457    Copy,
3458    Debug,
3459    Default,
3460    Eq,
3461    PartialEq,
3462    Hash,
3463    serde::Serialize,
3464    serde::Deserialize,
3465    Getters,
3466    new,
3467)]
3468pub struct GetAddressBalanceResponse {
3469    /// The total transparent balance.
3470    balance: u64,
3471    /// The total received balance, including change.
3472    pub received: u64,
3473}
3474
3475#[deprecated(note = "Use `GetAddressBalanceResponse` instead.")]
3476pub use self::GetAddressBalanceResponse as AddressBalance;
3477
3478/// Parameters of [`RpcServer::get_address_utxos`] RPC method.
3479#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
3480#[serde(from = "DGetAddressUtxosRequest")]
3481pub struct GetAddressUtxosRequest {
3482    /// A list of addresses to get transactions from.
3483    addresses: Vec<String>,
3484    /// The height to start looking for transactions.
3485    #[serde(default)]
3486    #[serde(rename = "chainInfo")]
3487    chain_info: bool,
3488}
3489
3490impl From<DGetAddressUtxosRequest> for GetAddressUtxosRequest {
3491    fn from(request: DGetAddressUtxosRequest) -> Self {
3492        match request {
3493            DGetAddressUtxosRequest::Single(addr) => GetAddressUtxosRequest {
3494                addresses: vec![addr],
3495                chain_info: false,
3496            },
3497            DGetAddressUtxosRequest::Object {
3498                addresses,
3499                chain_info,
3500            } => GetAddressUtxosRequest {
3501                addresses,
3502                chain_info,
3503            },
3504        }
3505    }
3506}
3507
3508/// An intermediate type used to deserialize [`GetAddressUtxosRequest`].
3509#[derive(Debug, serde::Deserialize)]
3510#[serde(untagged)]
3511enum DGetAddressUtxosRequest {
3512    /// A single address string.
3513    Single(String),
3514    /// A full request object with address list and chainInfo flag.
3515    Object {
3516        /// A list of addresses to get transactions from.
3517        addresses: Vec<String>,
3518        /// The height to start looking for transactions.
3519        #[serde(default)]
3520        #[serde(rename = "chainInfo")]
3521        chain_info: bool,
3522    },
3523}
3524
3525impl ValidateAddresses for GetAddressUtxosRequest {
3526    fn addresses(&self) -> &[String] {
3527        &self.addresses
3528    }
3529}
3530
3531/// A hex-encoded [`ConsensusBranchId`] string.
3532#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
3533pub struct ConsensusBranchIdHex(#[serde(with = "hex")] ConsensusBranchId);
3534
3535impl ConsensusBranchIdHex {
3536    /// Returns a new instance of ['ConsensusBranchIdHex'].
3537    pub fn new(consensus_branch_id: u32) -> Self {
3538        ConsensusBranchIdHex(consensus_branch_id.into())
3539    }
3540
3541    /// Returns the value of the ['ConsensusBranchId'].
3542    pub fn inner(&self) -> u32 {
3543        self.0.into()
3544    }
3545}
3546
3547/// Information about [`NetworkUpgrade`] activation.
3548#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3549pub struct NetworkUpgradeInfo {
3550    /// Name of upgrade, string.
3551    ///
3552    /// Ignored by lightwalletd, but useful for debugging.
3553    name: NetworkUpgrade,
3554
3555    /// Block height of activation, numeric.
3556    #[serde(rename = "activationheight")]
3557    activation_height: Height,
3558
3559    /// Status of upgrade, string.
3560    status: NetworkUpgradeStatus,
3561}
3562
3563impl NetworkUpgradeInfo {
3564    /// Constructs [`NetworkUpgradeInfo`] from its constituent parts.
3565    pub fn from_parts(
3566        name: NetworkUpgrade,
3567        activation_height: Height,
3568        status: NetworkUpgradeStatus,
3569    ) -> Self {
3570        Self {
3571            name,
3572            activation_height,
3573            status,
3574        }
3575    }
3576
3577    /// Returns the contents of ['NetworkUpgradeInfo'].
3578    pub fn into_parts(self) -> (NetworkUpgrade, Height, NetworkUpgradeStatus) {
3579        (self.name, self.activation_height, self.status)
3580    }
3581}
3582
3583/// The activation status of a [`NetworkUpgrade`].
3584#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3585pub enum NetworkUpgradeStatus {
3586    /// The network upgrade is currently active.
3587    ///
3588    /// Includes all network upgrades that have previously activated,
3589    /// even if they are not the most recent network upgrade.
3590    #[serde(rename = "active")]
3591    Active,
3592
3593    /// The network upgrade does not have an activation height.
3594    #[serde(rename = "disabled")]
3595    Disabled,
3596
3597    /// The network upgrade has an activation height, but we haven't reached it yet.
3598    #[serde(rename = "pending")]
3599    Pending,
3600}
3601
3602/// The [`ConsensusBranchId`]s for the tip and the next block.
3603///
3604/// These branch IDs are different when the next block is a network upgrade activation block.
3605#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3606pub struct TipConsensusBranch {
3607    /// Branch ID used to validate the current chain tip, big-endian, hex-encoded.
3608    #[serde(rename = "chaintip")]
3609    chain_tip: ConsensusBranchIdHex,
3610
3611    /// Branch ID used to validate the next block, big-endian, hex-encoded.
3612    #[serde(rename = "nextblock")]
3613    next_block: ConsensusBranchIdHex,
3614}
3615
3616impl TipConsensusBranch {
3617    /// Constructs [`TipConsensusBranch`] from its constituent parts.
3618    pub fn from_parts(chain_tip: u32, next_block: u32) -> Self {
3619        Self {
3620            chain_tip: ConsensusBranchIdHex::new(chain_tip),
3621            next_block: ConsensusBranchIdHex::new(next_block),
3622        }
3623    }
3624
3625    /// Returns the contents of ['TipConsensusBranch'].
3626    pub fn into_parts(self) -> (u32, u32) {
3627        (self.chain_tip.inner(), self.next_block.inner())
3628    }
3629}
3630
3631/// Response to a `sendrawtransaction` RPC request.
3632///
3633/// Contains the hex-encoded hash of the sent transaction.
3634///
3635/// See the notes for the [`Rpc::send_raw_transaction` method].
3636#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3637pub struct SendRawTransactionResponse(#[serde(with = "hex")] transaction::Hash);
3638
3639#[deprecated(note = "Use `SendRawTransactionResponse` instead")]
3640pub use self::SendRawTransactionResponse as SentTransactionHash;
3641
3642impl Default for SendRawTransactionResponse {
3643    fn default() -> Self {
3644        Self(transaction::Hash::from([0; 32]))
3645    }
3646}
3647
3648impl SendRawTransactionResponse {
3649    /// Constructs a new [`SentTransactionHash`].
3650    pub fn new(hash: transaction::Hash) -> Self {
3651        SendRawTransactionResponse(hash)
3652    }
3653
3654    /// Returns the contents of ['SentTransactionHash'].
3655    #[deprecated(note = "Use `SentTransactionHash::hash` instead")]
3656    pub fn inner(&self) -> transaction::Hash {
3657        self.hash()
3658    }
3659
3660    /// Returns the contents of ['SentTransactionHash'].
3661    pub fn hash(&self) -> transaction::Hash {
3662        self.0
3663    }
3664}
3665
3666/// Response to a `getblock` RPC request.
3667///
3668/// See the notes for the [`RpcServer::get_block`] method.
3669#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3670#[serde(untagged)]
3671pub enum GetBlockResponse {
3672    /// The request block, hex-encoded.
3673    Raw(#[serde(with = "hex")] SerializedBlock),
3674    /// The block object.
3675    Object(Box<BlockObject>),
3676}
3677
3678#[deprecated(note = "Use `GetBlockResponse` instead")]
3679pub use self::GetBlockResponse as GetBlock;
3680
3681impl Default for GetBlockResponse {
3682    fn default() -> Self {
3683        GetBlockResponse::Object(Box::new(BlockObject {
3684            hash: block::Hash([0; 32]),
3685            confirmations: 0,
3686            height: None,
3687            time: None,
3688            tx: Vec::new(),
3689            trees: GetBlockTrees::default(),
3690            size: None,
3691            version: None,
3692            merkle_root: None,
3693            block_commitments: None,
3694            final_sapling_root: None,
3695            final_orchard_root: None,
3696            nonce: None,
3697            bits: None,
3698            difficulty: None,
3699            chain_supply: None,
3700            value_pools: None,
3701            previous_block_hash: None,
3702            next_block_hash: None,
3703            solution: None,
3704        }))
3705    }
3706}
3707
3708/// A Block object returned by the `getblock` RPC request.
3709#[allow(clippy::too_many_arguments)]
3710#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3711pub struct BlockObject {
3712    /// The hash of the requested block.
3713    #[getter(copy)]
3714    #[serde(with = "hex")]
3715    hash: block::Hash,
3716
3717    /// The number of confirmations of this block in the best chain,
3718    /// or -1 if it is not in the best chain.
3719    confirmations: i64,
3720
3721    /// The block size. TODO: fill it
3722    #[serde(skip_serializing_if = "Option::is_none")]
3723    #[getter(copy)]
3724    size: Option<i64>,
3725
3726    /// The height of the requested block.
3727    #[serde(skip_serializing_if = "Option::is_none")]
3728    #[getter(copy)]
3729    height: Option<Height>,
3730
3731    /// The version field of the requested block.
3732    #[serde(skip_serializing_if = "Option::is_none")]
3733    #[getter(copy)]
3734    version: Option<u32>,
3735
3736    /// The merkle root of the requested block.
3737    #[serde(with = "opthex", rename = "merkleroot")]
3738    #[serde(skip_serializing_if = "Option::is_none")]
3739    #[getter(copy)]
3740    merkle_root: Option<block::merkle::Root>,
3741
3742    /// The blockcommitments field of the requested block. Its interpretation changes
3743    /// depending on the network and height.
3744    #[serde(with = "opthex", rename = "blockcommitments")]
3745    #[serde(skip_serializing_if = "Option::is_none")]
3746    #[getter(copy)]
3747    block_commitments: Option<[u8; 32]>,
3748
3749    // `authdataroot` would be here. Undocumented. TODO: decide if we want to support it
3750    //
3751    /// The root of the Sapling commitment tree after applying this block.
3752    #[serde(with = "opthex", rename = "finalsaplingroot")]
3753    #[serde(skip_serializing_if = "Option::is_none")]
3754    #[getter(copy)]
3755    final_sapling_root: Option<[u8; 32]>,
3756
3757    /// The root of the Orchard commitment tree after applying this block.
3758    #[serde(with = "opthex", rename = "finalorchardroot")]
3759    #[serde(skip_serializing_if = "Option::is_none")]
3760    #[getter(copy)]
3761    final_orchard_root: Option<[u8; 32]>,
3762
3763    // `chainhistoryroot` would be here. Undocumented. TODO: decide if we want to support it
3764    //
3765    /// List of transactions in block order, hex-encoded if verbosity=1 or
3766    /// as objects if verbosity=2.
3767    tx: Vec<GetBlockTransaction>,
3768
3769    /// The height of the requested block.
3770    #[serde(skip_serializing_if = "Option::is_none")]
3771    #[getter(copy)]
3772    time: Option<i64>,
3773
3774    /// The nonce of the requested block header.
3775    #[serde(with = "opthex")]
3776    #[serde(skip_serializing_if = "Option::is_none")]
3777    #[getter(copy)]
3778    nonce: Option<[u8; 32]>,
3779
3780    /// The Equihash solution in the requested block header.
3781    /// Note: presence of this field in getblock is not documented in zcashd.
3782    #[serde(with = "opthex")]
3783    #[serde(skip_serializing_if = "Option::is_none")]
3784    #[getter(copy)]
3785    solution: Option<Solution>,
3786
3787    /// The difficulty threshold of the requested block header displayed in compact form.
3788    #[serde(with = "opthex")]
3789    #[serde(skip_serializing_if = "Option::is_none")]
3790    #[getter(copy)]
3791    bits: Option<CompactDifficulty>,
3792
3793    /// Floating point number that represents the difficulty limit for this block as a multiple
3794    /// of the minimum difficulty for the network.
3795    #[serde(skip_serializing_if = "Option::is_none")]
3796    #[getter(copy)]
3797    difficulty: Option<f64>,
3798
3799    // `chainwork` would be here, but we don't plan on supporting it
3800    // `anchor` would be here. Not planned to be supported.
3801    //
3802    /// Chain supply balance
3803    #[serde(rename = "chainSupply")]
3804    #[serde(skip_serializing_if = "Option::is_none")]
3805    chain_supply: Option<GetBlockchainInfoBalance>,
3806
3807    /// Value pool balances
3808    #[serde(rename = "valuePools")]
3809    #[serde(skip_serializing_if = "Option::is_none")]
3810    value_pools: Option<BlockchainValuePoolBalances>,
3811
3812    /// Information about the note commitment trees.
3813    #[getter(copy)]
3814    trees: GetBlockTrees,
3815
3816    /// The previous block hash of the requested block header.
3817    #[serde(rename = "previousblockhash", skip_serializing_if = "Option::is_none")]
3818    #[serde(with = "opthex")]
3819    #[getter(copy)]
3820    previous_block_hash: Option<block::Hash>,
3821
3822    /// The next block hash after the requested block header.
3823    #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
3824    #[serde(with = "opthex")]
3825    #[getter(copy)]
3826    next_block_hash: Option<block::Hash>,
3827}
3828
3829#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3830#[serde(untagged)]
3831/// The transaction list in a `getblock` call. Can be a list of transaction
3832/// IDs or the full transaction details depending on verbosity.
3833pub enum GetBlockTransaction {
3834    /// The transaction hash, hex-encoded.
3835    Hash(#[serde(with = "hex")] transaction::Hash),
3836    /// The block object.
3837    Object(Box<TransactionObject>),
3838}
3839
3840/// Response to a `getblockheader` RPC request.
3841///
3842/// See the notes for the [`RpcServer::get_block_header`] method.
3843#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3844#[serde(untagged)]
3845pub enum GetBlockHeaderResponse {
3846    /// The request block header, hex-encoded.
3847    Raw(hex_data::HexData),
3848
3849    /// The block header object.
3850    Object(Box<BlockHeaderObject>),
3851}
3852
3853#[deprecated(note = "Use `GetBlockHeaderResponse` instead")]
3854pub use self::GetBlockHeaderResponse as GetBlockHeader;
3855
3856#[allow(clippy::too_many_arguments)]
3857#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3858/// Verbose response to a `getblockheader` RPC request.
3859///
3860/// See the notes for the [`RpcServer::get_block_header`] method.
3861pub struct BlockHeaderObject {
3862    /// The hash of the requested block.
3863    #[serde(with = "hex")]
3864    #[getter(copy)]
3865    hash: block::Hash,
3866
3867    /// The number of confirmations of this block in the best chain,
3868    /// or -1 if it is not in the best chain.
3869    confirmations: i64,
3870
3871    /// The height of the requested block.
3872    #[getter(copy)]
3873    height: Height,
3874
3875    /// The version field of the requested block.
3876    version: u32,
3877
3878    /// The merkle root of the requesteed block.
3879    #[serde(with = "hex", rename = "merkleroot")]
3880    #[getter(copy)]
3881    merkle_root: block::merkle::Root,
3882
3883    /// The blockcommitments field of the requested block. Its interpretation changes
3884    /// depending on the network and height.
3885    #[serde(with = "hex", rename = "blockcommitments")]
3886    #[getter(copy)]
3887    block_commitments: [u8; 32],
3888
3889    /// The root of the Sapling commitment tree after applying this block.
3890    #[serde(with = "hex", rename = "finalsaplingroot")]
3891    #[getter(copy)]
3892    final_sapling_root: [u8; 32],
3893
3894    /// The number of Sapling notes in the Sapling note commitment tree
3895    /// after applying this block. Used by the `getblock` RPC method.
3896    #[serde(skip)]
3897    sapling_tree_size: u64,
3898
3899    /// The block time of the requested block header in non-leap seconds since Jan 1 1970 GMT.
3900    time: i64,
3901
3902    /// The nonce of the requested block header.
3903    #[serde(with = "hex")]
3904    #[getter(copy)]
3905    nonce: [u8; 32],
3906
3907    /// The Equihash solution in the requested block header.
3908    #[serde(with = "hex")]
3909    #[getter(copy)]
3910    solution: Solution,
3911
3912    /// The difficulty threshold of the requested block header displayed in compact form.
3913    #[serde(with = "hex")]
3914    #[getter(copy)]
3915    bits: CompactDifficulty,
3916
3917    /// Floating point number that represents the difficulty limit for this block as a multiple
3918    /// of the minimum difficulty for the network.
3919    difficulty: f64,
3920
3921    /// The previous block hash of the requested block header.
3922    #[serde(rename = "previousblockhash")]
3923    #[serde(with = "hex")]
3924    #[getter(copy)]
3925    previous_block_hash: block::Hash,
3926
3927    /// The next block hash after the requested block header.
3928    #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
3929    #[getter(copy)]
3930    #[serde(with = "opthex")]
3931    next_block_hash: Option<block::Hash>,
3932}
3933
3934#[deprecated(note = "Use `BlockHeaderObject` instead")]
3935pub use BlockHeaderObject as GetBlockHeaderObject;
3936
3937impl Default for GetBlockHeaderResponse {
3938    fn default() -> Self {
3939        GetBlockHeaderResponse::Object(Box::default())
3940    }
3941}
3942
3943impl Default for BlockHeaderObject {
3944    fn default() -> Self {
3945        let difficulty: ExpandedDifficulty = zebra_chain::work::difficulty::U256::one().into();
3946
3947        BlockHeaderObject {
3948            hash: block::Hash([0; 32]),
3949            confirmations: 0,
3950            height: Height::MIN,
3951            version: 4,
3952            merkle_root: block::merkle::Root([0; 32]),
3953            block_commitments: Default::default(),
3954            final_sapling_root: Default::default(),
3955            sapling_tree_size: Default::default(),
3956            time: 0,
3957            nonce: [0; 32],
3958            solution: Solution::for_proposal(),
3959            bits: difficulty.to_compact(),
3960            difficulty: 1.0,
3961            previous_block_hash: block::Hash([0; 32]),
3962            next_block_hash: Some(block::Hash([0; 32])),
3963        }
3964    }
3965}
3966
3967/// Response to a `getbestblockhash` and `getblockhash` RPC request.
3968///
3969/// Contains the hex-encoded hash of the requested block.
3970///
3971/// Also see the notes for the [`RpcServer::get_best_block_hash`] and `get_block_hash` methods.
3972#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
3973#[serde(transparent)]
3974pub struct GetBlockHashResponse(#[serde(with = "hex")] pub(crate) block::Hash);
3975
3976impl GetBlockHashResponse {
3977    /// Constructs a new [`GetBlockHashResponse`] from a block hash.
3978    pub fn new(hash: block::Hash) -> Self {
3979        GetBlockHashResponse(hash)
3980    }
3981
3982    /// Returns the contents of [`GetBlockHashResponse`].
3983    pub fn hash(&self) -> block::Hash {
3984        self.0
3985    }
3986}
3987
3988#[deprecated(note = "Use `GetBlockHashResponse` instead")]
3989pub use self::GetBlockHashResponse as GetBlockHash;
3990
3991/// A block hash used by this crate that encodes as hex by default.
3992pub type Hash = GetBlockHashResponse;
3993
3994/// Response to a `getbestblockheightandhash` RPC request.
3995#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
3996pub struct GetBlockHeightAndHashResponse {
3997    /// The best chain tip block height
3998    #[getter(copy)]
3999    height: block::Height,
4000    /// The best chain tip block hash
4001    #[getter(copy)]
4002    hash: block::Hash,
4003}
4004
4005#[deprecated(note = "Use `GetBlockHeightAndHashResponse` instead.")]
4006pub use GetBlockHeightAndHashResponse as GetBestBlockHeightAndHash;
4007
4008impl Default for GetBlockHeightAndHashResponse {
4009    fn default() -> Self {
4010        Self {
4011            height: block::Height::MIN,
4012            hash: block::Hash([0; 32]),
4013        }
4014    }
4015}
4016
4017impl Default for GetBlockHashResponse {
4018    fn default() -> Self {
4019        GetBlockHashResponse(block::Hash([0; 32]))
4020    }
4021}
4022
4023/// Response to a `getrawtransaction` RPC request.
4024///
4025/// See the notes for the [`Rpc::get_raw_transaction` method].
4026#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4027#[serde(untagged)]
4028pub enum GetRawTransactionResponse {
4029    /// The raw transaction, encoded as hex bytes.
4030    Raw(#[serde(with = "hex")] SerializedTransaction),
4031    /// The transaction object.
4032    Object(Box<TransactionObject>),
4033}
4034
4035#[deprecated(note = "Use `GetRawTransactionResponse` instead")]
4036pub use self::GetRawTransactionResponse as GetRawTransaction;
4037
4038impl Default for GetRawTransactionResponse {
4039    fn default() -> Self {
4040        Self::Object(Box::default())
4041    }
4042}
4043
4044/// Response to a `getaddressutxos` RPC request.
4045#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4046#[serde(untagged)]
4047pub enum GetAddressUtxosResponse {
4048    /// Response when `chainInfo` is false or not provided.
4049    Utxos(Vec<Utxo>),
4050    /// Response when `chainInfo` is true.
4051    UtxosAndChainInfo(GetAddressUtxosResponseObject),
4052}
4053
4054/// Response to a `getaddressutxos` RPC request, when `chainInfo` is true.
4055#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4056pub struct GetAddressUtxosResponseObject {
4057    utxos: Vec<Utxo>,
4058    #[serde(with = "hex")]
4059    #[getter(copy)]
4060    hash: block::Hash,
4061    #[getter(copy)]
4062    height: block::Height,
4063}
4064
4065/// A UTXO returned by the `getaddressutxos` RPC request.
4066///
4067/// See the notes for the [`Rpc::get_address_utxos` method].
4068#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4069pub struct Utxo {
4070    /// The transparent address, base58check encoded
4071    address: transparent::Address,
4072
4073    /// The output txid, in big-endian order, hex-encoded
4074    #[serde(with = "hex")]
4075    #[getter(copy)]
4076    txid: transaction::Hash,
4077
4078    /// The transparent output index, numeric
4079    #[serde(rename = "outputIndex")]
4080    #[getter(copy)]
4081    output_index: OutputIndex,
4082
4083    /// The transparent output script, hex encoded
4084    #[serde(with = "hex")]
4085    script: transparent::Script,
4086
4087    /// The amount of zatoshis in the transparent output
4088    satoshis: u64,
4089
4090    /// The block height, numeric.
4091    ///
4092    /// We put this field last, to match the zcashd order.
4093    #[getter(copy)]
4094    height: Height,
4095}
4096
4097#[deprecated(note = "Use `Utxo` instead")]
4098pub use self::Utxo as GetAddressUtxos;
4099
4100impl Default for Utxo {
4101    fn default() -> Self {
4102        Self {
4103            address: transparent::Address::from_pub_key_hash(
4104                zebra_chain::parameters::NetworkKind::default(),
4105                [0u8; 20],
4106            ),
4107            txid: transaction::Hash::from([0; 32]),
4108            output_index: OutputIndex::from_u64(0),
4109            script: transparent::Script::new(&[0u8; 10]),
4110            satoshis: u64::default(),
4111            height: Height(0),
4112        }
4113    }
4114}
4115
4116impl Utxo {
4117    /// Constructs a new instance of [`GetAddressUtxos`].
4118    #[deprecated(note = "Use `Utxo::new` instead")]
4119    pub fn from_parts(
4120        address: transparent::Address,
4121        txid: transaction::Hash,
4122        output_index: OutputIndex,
4123        script: transparent::Script,
4124        satoshis: u64,
4125        height: Height,
4126    ) -> Self {
4127        Utxo {
4128            address,
4129            txid,
4130            output_index,
4131            script,
4132            satoshis,
4133            height,
4134        }
4135    }
4136
4137    /// Returns the contents of [`GetAddressUtxos`].
4138    pub fn into_parts(
4139        &self,
4140    ) -> (
4141        transparent::Address,
4142        transaction::Hash,
4143        OutputIndex,
4144        transparent::Script,
4145        u64,
4146        Height,
4147    ) {
4148        (
4149            self.address.clone(),
4150            self.txid,
4151            self.output_index,
4152            self.script.clone(),
4153            self.satoshis,
4154            self.height,
4155        )
4156    }
4157}
4158
4159/// Parameters of [`RpcServer::get_address_tx_ids`] RPC method.
4160///
4161/// See [`RpcServer::get_address_tx_ids`] for more details.
4162#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
4163#[serde(from = "DGetAddressTxIdsRequest")]
4164pub struct GetAddressTxIdsRequest {
4165    /// A list of addresses. The RPC method will get transactions IDs that sent or received
4166    /// funds to or from these addresses.
4167    addresses: Vec<String>,
4168    // The height to start looking for transactions.
4169    start: Option<u32>,
4170    // The height to end looking for transactions.
4171    end: Option<u32>,
4172}
4173
4174impl GetAddressTxIdsRequest {
4175    /// Constructs [`GetAddressTxIdsRequest`] from its constituent parts.
4176    #[deprecated(note = "Use `GetAddressTxIdsRequest::new` instead.")]
4177    pub fn from_parts(addresses: Vec<String>, start: u32, end: u32) -> Self {
4178        GetAddressTxIdsRequest {
4179            addresses,
4180            start: Some(start),
4181            end: Some(end),
4182        }
4183    }
4184
4185    /// Returns the contents of [`GetAddressTxIdsRequest`].
4186    pub fn into_parts(&self) -> (Vec<String>, u32, u32) {
4187        (
4188            self.addresses.clone(),
4189            self.start.unwrap_or(0),
4190            self.end.unwrap_or(0),
4191        )
4192    }
4193}
4194
4195impl From<DGetAddressTxIdsRequest> for GetAddressTxIdsRequest {
4196    fn from(request: DGetAddressTxIdsRequest) -> Self {
4197        match request {
4198            DGetAddressTxIdsRequest::Single(addr) => GetAddressTxIdsRequest {
4199                addresses: vec![addr],
4200                start: None,
4201                end: None,
4202            },
4203            DGetAddressTxIdsRequest::Object {
4204                addresses,
4205                start,
4206                end,
4207            } => GetAddressTxIdsRequest {
4208                addresses,
4209                start,
4210                end,
4211            },
4212        }
4213    }
4214}
4215
4216/// An intermediate type used to deserialize [`GetAddressTxIdsRequest`].
4217#[derive(Debug, serde::Deserialize)]
4218#[serde(untagged)]
4219enum DGetAddressTxIdsRequest {
4220    /// A single address string.
4221    Single(String),
4222    /// A full request object with address list and optional height range.
4223    Object {
4224        /// A list of addresses to get transactions from.
4225        addresses: Vec<String>,
4226        /// The height to start looking for transactions.
4227        start: Option<u32>,
4228        /// The height to end looking for transactions.
4229        end: Option<u32>,
4230    },
4231}
4232
4233impl ValidateAddresses for GetAddressTxIdsRequest {
4234    fn addresses(&self) -> &[String] {
4235        &self.addresses
4236    }
4237}
4238
4239/// Information about the sapling and orchard note commitment trees if any.
4240#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4241pub struct GetBlockTrees {
4242    #[serde(skip_serializing_if = "SaplingTrees::is_empty")]
4243    sapling: SaplingTrees,
4244    #[serde(skip_serializing_if = "OrchardTrees::is_empty")]
4245    orchard: OrchardTrees,
4246}
4247
4248impl Default for GetBlockTrees {
4249    fn default() -> Self {
4250        GetBlockTrees {
4251            sapling: SaplingTrees { size: 0 },
4252            orchard: OrchardTrees { size: 0 },
4253        }
4254    }
4255}
4256
4257impl GetBlockTrees {
4258    /// Constructs a new instance of ['GetBlockTrees'].
4259    pub fn new(sapling: u64, orchard: u64) -> Self {
4260        GetBlockTrees {
4261            sapling: SaplingTrees { size: sapling },
4262            orchard: OrchardTrees { size: orchard },
4263        }
4264    }
4265
4266    /// Returns sapling data held by ['GetBlockTrees'].
4267    pub fn sapling(self) -> u64 {
4268        self.sapling.size
4269    }
4270
4271    /// Returns orchard data held by ['GetBlockTrees'].
4272    pub fn orchard(self) -> u64 {
4273        self.orchard.size
4274    }
4275}
4276
4277/// Sapling note commitment tree information.
4278#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4279pub struct SaplingTrees {
4280    size: u64,
4281}
4282
4283impl SaplingTrees {
4284    fn is_empty(&self) -> bool {
4285        self.size == 0
4286    }
4287}
4288
4289/// Orchard note commitment tree information.
4290#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4291pub struct OrchardTrees {
4292    size: u64,
4293}
4294
4295impl OrchardTrees {
4296    fn is_empty(&self) -> bool {
4297        self.size == 0
4298    }
4299}
4300
4301/// Build a valid height range from the given optional start and end numbers.
4302///
4303/// # Parameters
4304///
4305/// - `start`: Optional starting height. If not provided, defaults to 0.
4306/// - `end`: Optional ending height. A value of 0 or absence of a value indicates to use `chain_height`.
4307/// - `chain_height`: The maximum permissible height.
4308///
4309/// # Returns
4310///
4311/// A `RangeInclusive<Height>` from the clamped start to the clamped end.
4312///
4313/// # Errors
4314///
4315/// Returns an error if the computed start is greater than the computed end.
4316fn build_height_range(
4317    start: Option<u32>,
4318    end: Option<u32>,
4319    chain_height: Height,
4320) -> Result<RangeInclusive<Height>> {
4321    // Convert optional values to Height, using 0 (as Height(0)) when missing.
4322    // If start is above chain_height, clamp it to chain_height.
4323    let start = Height(start.unwrap_or(0)).min(chain_height);
4324
4325    // For `end`, treat a zero value or missing value as `chain_height`:
4326    let end = match end {
4327        Some(0) | None => chain_height,
4328        Some(val) => Height(val).min(chain_height),
4329    };
4330
4331    if start > end {
4332        return Err(ErrorObject::owned(
4333            ErrorCode::InvalidParams.code(),
4334            format!("start {start:?} must be less than or equal to end {end:?}"),
4335            None::<()>,
4336        ));
4337    }
4338
4339    Ok(start..=end)
4340}
4341
4342/// Given a potentially negative index, find the corresponding `Height`.
4343///
4344/// This function is used to parse the integer index argument of `get_block_hash`.
4345/// This is based on zcashd's implementation:
4346/// <https://github.com/zcash/zcash/blob/c267c3ee26510a974554f227d40a89e3ceb5bb4d/src/rpc/blockchain.cpp#L589-L618>
4347//
4348// TODO: also use this function in `get_block` and `z_get_treestate`
4349#[allow(dead_code)]
4350pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height> {
4351    if index >= 0 {
4352        let height = index.try_into().expect("Positive i32 always fits in u32");
4353        if height > tip_height.0 {
4354            return Err(ErrorObject::borrowed(
4355                ErrorCode::InvalidParams.code(),
4356                "Provided index is greater than the current tip",
4357                None,
4358            ));
4359        }
4360        Ok(Height(height))
4361    } else {
4362        // `index + 1` can't overflow, because `index` is always negative here.
4363        let height = i32::try_from(tip_height.0)
4364            .expect("tip height fits in i32, because Height::MAX fits in i32")
4365            .checked_add(index + 1);
4366
4367        let sanitized_height = match height {
4368            None => {
4369                return Err(ErrorObject::borrowed(
4370                    ErrorCode::InvalidParams.code(),
4371                    "Provided index is not valid",
4372                    None,
4373                ));
4374            }
4375            Some(h) => {
4376                if h < 0 {
4377                    return Err(ErrorObject::borrowed(
4378                        ErrorCode::InvalidParams.code(),
4379                        "Provided negative index ends up with a negative height",
4380                        None,
4381                    ));
4382                }
4383                let h: u32 = h.try_into().expect("Positive i32 always fits in u32");
4384                if h > tip_height.0 {
4385                    return Err(ErrorObject::borrowed(
4386                        ErrorCode::InvalidParams.code(),
4387                        "Provided index is greater than the current tip",
4388                        None,
4389                    ));
4390                }
4391
4392                h
4393            }
4394        };
4395
4396        Ok(Height(sanitized_height))
4397    }
4398}
4399
4400/// A helper module to serialize and deserialize `Option<T: ToHex>` as a hex string.
4401pub mod opthex {
4402    use hex::{FromHex, ToHex};
4403    use serde::{de, Deserialize, Deserializer, Serializer};
4404
4405    #[allow(missing_docs)]
4406    pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
4407    where
4408        S: Serializer,
4409        T: ToHex,
4410    {
4411        match data {
4412            Some(data) => {
4413                let s = data.encode_hex::<String>();
4414                serializer.serialize_str(&s)
4415            }
4416            None => serializer.serialize_none(),
4417        }
4418    }
4419
4420    #[allow(missing_docs)]
4421    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
4422    where
4423        D: Deserializer<'de>,
4424        T: FromHex,
4425    {
4426        let opt = Option::<String>::deserialize(deserializer)?;
4427        match opt {
4428            Some(s) => T::from_hex(&s)
4429                .map(Some)
4430                .map_err(|_e| de::Error::custom("failed to convert hex string")),
4431            None => Ok(None),
4432        }
4433    }
4434}
4435
4436/// A helper module to serialize and deserialize `[u8; N]` as a hex string.
4437pub mod arrayhex {
4438    use serde::{Deserializer, Serializer};
4439    use std::fmt;
4440
4441    #[allow(missing_docs)]
4442    pub fn serialize<S, const N: usize>(data: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
4443    where
4444        S: Serializer,
4445    {
4446        let hex_string = hex::encode(data);
4447        serializer.serialize_str(&hex_string)
4448    }
4449
4450    #[allow(missing_docs)]
4451    pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
4452    where
4453        D: Deserializer<'de>,
4454    {
4455        struct HexArrayVisitor<const N: usize>;
4456
4457        impl<const N: usize> serde::de::Visitor<'_> for HexArrayVisitor<N> {
4458            type Value = [u8; N];
4459
4460            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
4461                write!(formatter, "a hex string representing exactly {N} bytes")
4462            }
4463
4464            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
4465            where
4466                E: serde::de::Error,
4467            {
4468                let vec = hex::decode(v).map_err(E::custom)?;
4469                vec.clone().try_into().map_err(|_| {
4470                    E::invalid_length(vec.len(), &format!("expected {N} bytes").as_str())
4471                })
4472            }
4473        }
4474
4475        deserializer.deserialize_str(HexArrayVisitor::<N>)
4476    }
4477}
4478
4479/// Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
4480pub async fn chain_tip_difficulty<State>(
4481    network: Network,
4482    mut state: State,
4483    should_use_default: bool,
4484) -> Result<f64>
4485where
4486    State: Service<
4487            zebra_state::ReadRequest,
4488            Response = zebra_state::ReadResponse,
4489            Error = zebra_state::BoxError,
4490        > + Clone
4491        + Send
4492        + Sync
4493        + 'static,
4494    State::Future: Send,
4495{
4496    let request = ReadRequest::ChainInfo;
4497
4498    // # TODO
4499    // - add a separate request like BestChainNextMedianTimePast, but skipping the
4500    //   consistency check, because any block's difficulty is ok for display
4501    // - return 1.0 for a "not enough blocks in the state" error, like `zcashd`:
4502    // <https://github.com/zcash/zcash/blob/7b28054e8b46eb46a9589d0bdc8e29f9fa1dc82d/src/rpc/blockchain.cpp#L40-L41>
4503    let response = state
4504        .ready()
4505        .and_then(|service| service.call(request))
4506        .await;
4507
4508    let response = match (should_use_default, response) {
4509        (_, Ok(res)) => res,
4510        (true, Err(_)) => {
4511            return Ok((U256::from(network.target_difficulty_limit()) >> 128).as_u128() as f64);
4512        }
4513        (false, Err(error)) => return Err(ErrorObject::owned(0, error.to_string(), None::<()>)),
4514    };
4515
4516    let chain_info = match response {
4517        ReadResponse::ChainInfo(info) => info,
4518        _ => unreachable!("unmatched response to a chain info request"),
4519    };
4520
4521    // This RPC is typically used for display purposes, so it is not consensus-critical.
4522    // But it uses the difficulty consensus rules for its calculations.
4523    //
4524    // Consensus:
4525    // https://zips.z.cash/protocol/protocol.pdf#nbits
4526    //
4527    // The zcashd implementation performs to_expanded() on f64,
4528    // and then does an inverse division:
4529    // https://github.com/zcash/zcash/blob/d6e2fada844373a8554ee085418e68de4b593a6c/src/rpc/blockchain.cpp#L46-L73
4530    //
4531    // But in Zebra we divide the high 128 bits of each expanded difficulty. This gives
4532    // a similar result, because the lower 128 bits are insignificant after conversion
4533    // to `f64` with a 53-bit mantissa.
4534    //
4535    // `pow_limit >> 128 / difficulty >> 128` is the same as the work calculation
4536    // `(2^256 / pow_limit) / (2^256 / difficulty)`, but it's a bit more accurate.
4537    //
4538    // To simplify the calculation, we don't scale for leading zeroes. (Bitcoin's
4539    // difficulty currently uses 68 bits, so even it would still have full precision
4540    // using this calculation.)
4541
4542    // Get expanded difficulties (256 bits), these are the inverse of the work
4543    let pow_limit: U256 = network.target_difficulty_limit().into();
4544    let Some(difficulty) = chain_info.expected_difficulty.to_expanded() else {
4545        return Ok(0.0);
4546    };
4547
4548    // Shift out the lower 128 bits (256 bits, but the top 128 are all zeroes)
4549    let pow_limit = pow_limit >> 128;
4550    let difficulty = U256::from(difficulty) >> 128;
4551
4552    // Convert to u128 then f64.
4553    // We could also convert U256 to String, then parse as f64, but that's slower.
4554    let pow_limit = pow_limit.as_u128() as f64;
4555    let difficulty = difficulty.as_u128() as f64;
4556
4557    // Invert the division to give approximately: `work(difficulty) / work(pow_limit)`
4558    Ok(pow_limit / difficulty)
4559}
4560
4561/// Commands for the `addnode` RPC method.
4562#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4563pub enum AddNodeCommand {
4564    /// Add a node to the address book.
4565    #[serde(rename = "add")]
4566    Add,
4567}