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