Skip to main content

zebra_rpc/
methods.rs

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