zebra_rpc/
methods.rs

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