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