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