Skip to main content

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