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