Skip to main content

zebra_rpc/
methods.rs

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