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