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