zebra_rpc/
methods.rs

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