zebra_rpc/
methods.rs

1//! Zebra supported RPC methods.
2//!
3//! Based on the [`zcashd` RPC methods](https://zcash.github.io/rpc/)
4//! as used by `lightwalletd.`
5//!
6//! Some parts of the `zcashd` RPC documentation are outdated.
7//! So this implementation follows the `zcashd` server and `lightwalletd` client implementations.
8
9use std::{
10    collections::{HashMap, HashSet},
11    fmt::Debug,
12    ops::RangeInclusive,
13};
14
15use chrono::Utc;
16use futures::{stream::FuturesOrdered, StreamExt, TryFutureExt};
17use hex::{FromHex, ToHex};
18use hex_data::HexData;
19use indexmap::IndexMap;
20use jsonrpsee::core::{async_trait, RpcResult as Result};
21use jsonrpsee_proc_macros::rpc;
22use jsonrpsee_types::{ErrorCode, ErrorObject};
23use tokio::{
24    sync::{broadcast, watch},
25    task::JoinHandle,
26};
27use tower::{Service, ServiceExt};
28use tracing::Instrument;
29
30use zcash_primitives::consensus::Parameters;
31use zebra_chain::{
32    block::{self, Commitment, Height, SerializedBlock},
33    chain_tip::{ChainTip, NetworkChainTipHeightEstimator},
34    parameters::{ConsensusBranchId, Network, NetworkUpgrade},
35    serialization::{ZcashDeserialize, ZcashSerialize},
36    subtree::NoteCommitmentSubtreeIndex,
37    transaction::{self, SerializedTransaction, Transaction, UnminedTx},
38    transparent::{self, Address},
39    work::{
40        difficulty::{CompactDifficulty, ExpandedDifficulty, ParameterDifficulty, U256},
41        equihash::Solution,
42    },
43};
44use zebra_consensus::ParameterCheckpoint;
45use zebra_network::address_book_peers::AddressBookPeers;
46use zebra_node_services::mempool;
47use zebra_state::{
48    HashOrHeight, OutputIndex, OutputLocation, ReadRequest, ReadResponse, TransactionLocation,
49};
50
51use crate::{
52    methods::trees::{GetSubtrees, GetTreestate, SubtreeRpcData},
53    queue::Queue,
54    server::{
55        self,
56        error::{MapError, OkOrError},
57    },
58};
59
60pub mod hex_data;
61
62// We don't use a types/ module here, because it is redundant.
63pub mod trees;
64
65pub mod types;
66
67use types::GetRawMempool;
68use types::MempoolObject;
69use types::TransactionObject;
70
71pub mod get_block_template_rpcs;
72
73pub use get_block_template_rpcs::{GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer};
74
75#[cfg(test)]
76mod tests;
77
78#[rpc(server)]
79/// RPC method signatures.
80pub trait Rpc {
81    /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct.
82    ///
83    /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html)
84    /// method: post
85    /// tags: control
86    ///
87    /// # Notes
88    ///
89    /// [The zcashd reference](https://zcash.github.io/rpc/getinfo.html) might not show some fields
90    /// in Zebra's [`GetInfo`]. Zebra uses the field names and formats from the
91    /// [zcashd code](https://github.com/zcash/zcash/blob/v4.6.0-1/src/rpc/misc.cpp#L86-L87).
92    ///
93    /// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields
94    /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95)
95    #[method(name = "getinfo")]
96    async fn get_info(&self) -> Result<GetInfo>;
97
98    /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct.
99    ///
100    /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html)
101    /// method: post
102    /// tags: blockchain
103    ///
104    /// # Notes
105    ///
106    /// Some fields from the zcashd reference are missing from Zebra's [`GetBlockChainInfo`]. It only contains the fields
107    /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89)
108    #[method(name = "getblockchaininfo")]
109    async fn get_blockchain_info(&self) -> Result<GetBlockChainInfo>;
110
111    /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance.
112    ///
113    /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html)
114    /// method: post
115    /// tags: address
116    ///
117    /// # Parameters
118    ///
119    /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry
120    ///     - `addresses`: (array of strings) A list of base-58 encoded addresses.
121    ///
122    /// # Notes
123    ///
124    /// zcashd also accepts a single string parameter instead of an array of strings, but Zebra
125    /// doesn't because lightwalletd always calls this RPC with an array of addresses.
126    ///
127    /// zcashd also returns the total amount of Zatoshis received by the addresses, but Zebra
128    /// doesn't because lightwalletd doesn't use that information.
129    ///
130    /// The RPC documentation says that the returned object has a string `balance` field, but
131    /// zcashd actually [returns an
132    /// integer](https://github.com/zcash/lightwalletd/blob/bdaac63f3ee0dbef62bde04f6817a9f90d483b00/common/common.go#L128-L130).
133    #[method(name = "getaddressbalance")]
134    async fn get_address_balance(&self, address_strings: AddressStrings) -> Result<AddressBalance>;
135
136    /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid.
137    /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string.
138    ///
139    /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html)
140    /// method: post
141    /// tags: transaction
142    ///
143    /// # Parameters
144    ///
145    /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes.
146    /// - `allow_high_fees`: (bool, optional) A legacy parameter accepted by zcashd but ignored by Zebra.
147    ///
148    /// # Notes
149    ///
150    /// zcashd accepts an optional `allowhighfees` parameter. Zebra doesn't support this parameter,
151    /// because lightwalletd doesn't use it.
152    #[method(name = "sendrawtransaction")]
153    async fn send_raw_transaction(
154        &self,
155        raw_transaction_hex: String,
156        _allow_high_fees: Option<bool>,
157    ) -> Result<SentTransactionHash>;
158
159    /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
160    /// If the block is not in Zebra's state, returns
161    /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) if a height was
162    /// passed or -5 if a hash was passed.
163    ///
164    /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
165    /// method: post
166    /// tags: blockchain
167    ///
168    /// # Parameters
169    ///
170    /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
171    /// - `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.
172    ///
173    /// # Notes
174    ///
175    /// The `size` field is only returned with verbosity=2.
176    ///
177    /// The undocumented `chainwork` field is not returned.
178    #[method(name = "getblock")]
179    async fn get_block(&self, hash_or_height: String, verbosity: Option<u8>) -> Result<GetBlock>;
180
181    /// Returns the requested block header by hash or height, as a [`GetBlockHeader`] JSON string.
182    /// If the block is not in Zebra's state,
183    /// returns [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
184    /// if a height was passed or -5 if a hash was passed.
185    ///
186    /// zcashd reference: [`getblockheader`](https://zcash.github.io/rpc/getblockheader.html)
187    /// method: post
188    /// tags: blockchain
189    ///
190    /// # Parameters
191    ///
192    /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
193    /// - `verbose`: (bool, optional, default=false, example=true) false for hex encoded data, true for a json object
194    ///
195    /// # Notes
196    ///
197    /// The undocumented `chainwork` field is not returned.
198    #[method(name = "getblockheader")]
199    async fn get_block_header(
200        &self,
201        hash_or_height: String,
202        verbose: Option<bool>,
203    ) -> Result<GetBlockHeader>;
204
205    /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
206    ///
207    /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
208    /// method: post
209    /// tags: blockchain
210    #[method(name = "getbestblockhash")]
211    fn get_best_block_hash(&self) -> Result<GetBlockHash>;
212
213    /// Returns the height and hash of the current best blockchain tip block, as a [`GetBlockHeightAndHash`] JSON struct.
214    ///
215    /// zcashd reference: none
216    /// method: post
217    /// tags: blockchain
218    #[method(name = "getbestblockheightandhash")]
219    fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHash>;
220
221    /// Returns all transaction ids in the memory pool, as a JSON array.
222    ///
223    /// # Parameters
224    ///
225    /// - `verbose`: (boolean, optional, default=false) true for a json object, false for array of transaction ids.
226    ///
227    /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html)
228    /// method: post
229    /// tags: blockchain
230    #[method(name = "getrawmempool")]
231    async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempool>;
232
233    /// Returns information about the given block's Sapling & Orchard tree state.
234    ///
235    /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html)
236    /// method: post
237    /// tags: blockchain
238    ///
239    /// # Parameters
240    ///
241    /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height.
242    ///
243    /// # Notes
244    ///
245    /// The zcashd doc reference above says that the parameter "`height` can be
246    /// negative where -1 is the last known valid block". On the other hand,
247    /// `lightwalletd` only uses positive heights, so Zebra does not support
248    /// negative heights.
249    #[method(name = "z_gettreestate")]
250    async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestate>;
251
252    /// Returns information about a range of Sapling or Orchard subtrees.
253    ///
254    /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link
255    /// method: post
256    /// tags: blockchain
257    ///
258    /// # Parameters
259    ///
260    /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard".
261    /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return.
262    /// - `limit`: (number, optional) The maximum number of subtree values to return.
263    ///
264    /// # Notes
265    ///
266    /// While Zebra is doing its initial subtree index rebuild, subtrees will become available
267    /// starting at the chain tip. This RPC will return an empty list if the `start_index` subtree
268    /// exists, but has not been rebuilt yet. This matches `zcashd`'s behaviour when subtrees aren't
269    /// available yet. (But `zcashd` does its rebuild before syncing any blocks.)
270    #[method(name = "z_getsubtreesbyindex")]
271    async fn z_get_subtrees_by_index(
272        &self,
273        pool: String,
274        start_index: NoteCommitmentSubtreeIndex,
275        limit: Option<NoteCommitmentSubtreeIndex>,
276    ) -> Result<GetSubtrees>;
277
278    /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure.
279    ///
280    /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html)
281    /// method: post
282    /// tags: transaction
283    ///
284    /// # Parameters
285    ///
286    /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned.
287    /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object.
288    ///
289    /// # Notes
290    ///
291    /// We don't currently support the `blockhash` parameter since lightwalletd does not
292    /// use it.
293    ///
294    /// In verbose mode, we only expose the `hex` and `height` fields since
295    /// lightwalletd uses only those:
296    /// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L119>
297    #[method(name = "getrawtransaction")]
298    async fn get_raw_transaction(
299        &self,
300        txid: String,
301        verbose: Option<u8>,
302    ) -> Result<GetRawTransaction>;
303
304    /// Returns the transaction ids made by the provided transparent addresses.
305    ///
306    /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html)
307    /// method: post
308    /// tags: address
309    ///
310    /// # Parameters
311    ///
312    /// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields:
313    ///     - `addresses`: (json array of string, required) The addresses to get transactions from.
314    ///     - `start`: (numeric, optional) The lower height to start looking for transactions (inclusive).
315    ///     - `end`: (numeric, optional) The top height to stop looking for transactions (inclusive).
316    ///
317    /// # Notes
318    ///
319    /// Only the multi-argument format is used by lightwalletd and this is what we currently support:
320    /// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L97-L102>
321    #[method(name = "getaddresstxids")]
322    async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
323
324    /// Returns all unspent outputs for a list of addresses.
325    ///
326    /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html)
327    /// method: post
328    /// tags: address
329    ///
330    /// # Parameters
331    ///
332    /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from.
333    ///
334    /// # Notes
335    ///
336    /// lightwalletd always uses the multi-address request, without chaininfo:
337    /// <https://github.com/zcash/lightwalletd/blob/master/frontend/service.go#L402>
338    #[method(name = "getaddressutxos")]
339    async fn get_address_utxos(
340        &self,
341        address_strings: AddressStrings,
342    ) -> Result<Vec<GetAddressUtxos>>;
343
344    /// Stop the running zebrad process.
345    ///
346    /// # Notes
347    ///
348    /// - Works for non windows targets only.
349    /// - Works only if the network of the running zebrad process is `Regtest`.
350    ///
351    /// zcashd reference: [`stop`](https://zcash.github.io/rpc/stop.html)
352    /// method: post
353    /// tags: control
354    #[method(name = "stop")]
355    fn stop(&self) -> Result<String>;
356}
357
358/// RPC method implementations.
359#[derive(Clone)]
360pub struct RpcImpl<Mempool, State, Tip, AddressBook>
361where
362    Mempool: Service<
363            mempool::Request,
364            Response = mempool::Response,
365            Error = zebra_node_services::BoxError,
366        > + Clone
367        + Send
368        + Sync
369        + 'static,
370    Mempool::Future: Send,
371    State: Service<
372            zebra_state::ReadRequest,
373            Response = zebra_state::ReadResponse,
374            Error = zebra_state::BoxError,
375        > + Clone
376        + Send
377        + Sync
378        + 'static,
379    State::Future: Send,
380    Tip: ChainTip + Clone + Send + Sync + 'static,
381    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
382{
383    // Configuration
384    //
385    /// Zebra's application version, with build metadata.
386    build_version: String,
387
388    /// Zebra's RPC user agent.
389    user_agent: String,
390
391    /// The configured network for this RPC service.
392    network: Network,
393
394    /// Test-only option that makes Zebra say it is at the chain tip,
395    /// no matter what the estimated height or local clock is.
396    debug_force_finished_sync: bool,
397
398    /// Test-only option that makes RPC responses more like `zcashd`.
399    #[allow(dead_code)]
400    debug_like_zcashd: bool,
401
402    // Services
403    //
404    /// A handle to the mempool service.
405    mempool: Mempool,
406
407    /// A handle to the state service.
408    state: State,
409
410    /// Allows efficient access to the best tip of the blockchain.
411    latest_chain_tip: Tip,
412
413    // Tasks
414    //
415    /// A sender component of a channel used to send transactions to the mempool queue.
416    queue_sender: broadcast::Sender<UnminedTx>,
417
418    /// Peer address book.
419    address_book: AddressBook,
420
421    /// The last warning or error event logged by the server.
422    last_warn_error_log_rx: LoggedLastEvent,
423}
424
425/// A type alias for the last event logged by the server.
426pub type LoggedLastEvent = watch::Receiver<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>;
427
428impl<Mempool, State, Tip, AddressBook> Debug for RpcImpl<Mempool, State, Tip, AddressBook>
429where
430    Mempool: Service<
431            mempool::Request,
432            Response = mempool::Response,
433            Error = zebra_node_services::BoxError,
434        > + Clone
435        + Send
436        + Sync
437        + 'static,
438    Mempool::Future: Send,
439    State: Service<
440            zebra_state::ReadRequest,
441            Response = zebra_state::ReadResponse,
442            Error = zebra_state::BoxError,
443        > + Clone
444        + Send
445        + Sync
446        + 'static,
447    State::Future: Send,
448    Tip: ChainTip + Clone + Send + Sync + 'static,
449    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
450{
451    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452        // Skip fields without Debug impls, and skip channels
453        f.debug_struct("RpcImpl")
454            .field("build_version", &self.build_version)
455            .field("user_agent", &self.user_agent)
456            .field("network", &self.network)
457            .field("debug_force_finished_sync", &self.debug_force_finished_sync)
458            .field("debug_like_zcashd", &self.debug_like_zcashd)
459            .finish()
460    }
461}
462
463impl<Mempool, State, Tip, AddressBook> RpcImpl<Mempool, State, Tip, AddressBook>
464where
465    Mempool: Service<
466            mempool::Request,
467            Response = mempool::Response,
468            Error = zebra_node_services::BoxError,
469        > + Clone
470        + Send
471        + Sync
472        + 'static,
473    Mempool::Future: Send,
474    State: Service<
475            zebra_state::ReadRequest,
476            Response = zebra_state::ReadResponse,
477            Error = zebra_state::BoxError,
478        > + Clone
479        + Send
480        + Sync
481        + 'static,
482    State::Future: Send,
483    Tip: ChainTip + Clone + Send + Sync + 'static,
484    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
485{
486    /// Create a new instance of the RPC handler.
487    //
488    // TODO:
489    // - put some of the configs or services in their own struct?
490    #[allow(clippy::too_many_arguments)]
491    pub fn new<VersionString, UserAgentString>(
492        build_version: VersionString,
493        user_agent: UserAgentString,
494        network: Network,
495        debug_force_finished_sync: bool,
496        debug_like_zcashd: bool,
497        mempool: Mempool,
498        state: State,
499        latest_chain_tip: Tip,
500        address_book: AddressBook,
501        last_warn_error_log_rx: LoggedLastEvent,
502    ) -> (Self, JoinHandle<()>)
503    where
504        VersionString: ToString + Clone + Send + 'static,
505        UserAgentString: ToString + Clone + Send + 'static,
506    {
507        let (runner, queue_sender) = Queue::start();
508
509        let mut build_version = build_version.to_string();
510        let user_agent = user_agent.to_string();
511
512        // Match zcashd's version format, if the version string has anything in it
513        if !build_version.is_empty() && !build_version.starts_with('v') {
514            build_version.insert(0, 'v');
515        }
516
517        let rpc_impl = RpcImpl {
518            build_version,
519            user_agent,
520            network: network.clone(),
521            debug_force_finished_sync,
522            debug_like_zcashd,
523            mempool: mempool.clone(),
524            state: state.clone(),
525            latest_chain_tip: latest_chain_tip.clone(),
526            queue_sender,
527            address_book,
528            last_warn_error_log_rx,
529        };
530
531        // run the process queue
532        let rpc_tx_queue_task_handle = tokio::spawn(
533            runner
534                .run(mempool, state, latest_chain_tip, network)
535                .in_current_span(),
536        );
537
538        (rpc_impl, rpc_tx_queue_task_handle)
539    }
540}
541
542#[async_trait]
543impl<Mempool, State, Tip, AddressBook> RpcServer for RpcImpl<Mempool, State, Tip, AddressBook>
544where
545    Mempool: Service<
546            mempool::Request,
547            Response = mempool::Response,
548            Error = zebra_node_services::BoxError,
549        > + Clone
550        + Send
551        + Sync
552        + 'static,
553    Mempool::Future: Send,
554    State: Service<
555            zebra_state::ReadRequest,
556            Response = zebra_state::ReadResponse,
557            Error = zebra_state::BoxError,
558        > + Clone
559        + Send
560        + Sync
561        + 'static,
562    State::Future: Send,
563    Tip: ChainTip + Clone + Send + Sync + 'static,
564    AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
565{
566    async fn get_info(&self) -> Result<GetInfo> {
567        let version = GetInfo::version(&self.build_version).expect("invalid version string");
568
569        let connections = self.address_book.recently_live_peers(Utc::now()).len();
570
571        let last_error_recorded = self.last_warn_error_log_rx.borrow().clone();
572        let (last_error_log, _level, last_error_log_time) = last_error_recorded.unwrap_or((
573            GetInfo::default().errors,
574            tracing::Level::INFO,
575            Utc::now(),
576        ));
577
578        let tip_height = self
579            .latest_chain_tip
580            .best_tip_height()
581            .unwrap_or(Height::MIN);
582        let testnet = self.network.is_a_test_network();
583
584        // This field is behind the `ENABLE_WALLET` feature flag in zcashd:
585        // https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L113
586        // However it is not documented as optional:
587        // https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L70
588        // For compatibility, we keep the field in the response, but always return 0.
589        let pay_tx_fee = 0.0;
590
591        let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
592            / (zebra_chain::amount::COIN as f64);
593        let difficulty = chain_tip_difficulty(self.network.clone(), self.state.clone(), true)
594            .await
595            .expect("should always be Ok when `should_use_default` is true");
596
597        let response = GetInfo {
598            version,
599            build: self.build_version.clone(),
600            subversion: self.user_agent.clone(),
601            protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
602            blocks: tip_height.0,
603            connections,
604            proxy: None,
605            difficulty,
606            testnet,
607            pay_tx_fee,
608            relay_fee,
609            errors: last_error_log,
610            errors_timestamp: last_error_log_time.to_string(),
611        };
612
613        Ok(response)
614    }
615
616    #[allow(clippy::unwrap_in_result)]
617    async fn get_blockchain_info(&self) -> Result<GetBlockChainInfo> {
618        let debug_force_finished_sync = self.debug_force_finished_sync;
619        let network = &self.network;
620
621        let (usage_info_rsp, tip_pool_values_rsp, chain_tip_difficulty) = {
622            use zebra_state::ReadRequest::*;
623            let state_call = |request| self.state.clone().oneshot(request);
624            tokio::join!(
625                state_call(UsageInfo),
626                state_call(TipPoolValues),
627                chain_tip_difficulty(network.clone(), self.state.clone(), true)
628            )
629        };
630
631        let (size_on_disk, (tip_height, tip_hash), value_balance, difficulty) = {
632            use zebra_state::ReadResponse::*;
633
634            let UsageInfo(size_on_disk) = usage_info_rsp.map_misc_error()? else {
635                unreachable!("unmatched response to a TipPoolValues request")
636            };
637
638            let (tip, value_balance) = match tip_pool_values_rsp {
639                Ok(TipPoolValues {
640                    tip_height,
641                    tip_hash,
642                    value_balance,
643                }) => ((tip_height, tip_hash), value_balance),
644                Ok(_) => unreachable!("unmatched response to a TipPoolValues request"),
645                Err(_) => ((Height::MIN, network.genesis_hash()), Default::default()),
646            };
647
648            let difficulty = chain_tip_difficulty
649                .expect("should always be Ok when `should_use_default` is true");
650
651            (size_on_disk, tip, value_balance, difficulty)
652        };
653
654        let now = Utc::now();
655        let (estimated_height, verification_progress) = self
656            .latest_chain_tip
657            .best_tip_height_and_block_time()
658            .map(|(tip_height, tip_block_time)| {
659                let height =
660                    NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, network)
661                        .estimate_height_at(now);
662
663                // If we're testing the mempool, force the estimated height to be the actual tip height, otherwise,
664                // check if the estimated height is below Zebra's latest tip height, or if the latest tip's block time is
665                // later than the current time on the local clock.
666                let height =
667                    if tip_block_time > now || height < tip_height || debug_force_finished_sync {
668                        tip_height
669                    } else {
670                        height
671                    };
672
673                (height, f64::from(tip_height.0) / f64::from(height.0))
674            })
675            // TODO: Add a `genesis_block_time()` method on `Network` to use here.
676            .unwrap_or((Height::MIN, 0.0));
677
678        // `upgrades` object
679        //
680        // Get the network upgrades in height order, like `zcashd`.
681        let mut upgrades = IndexMap::new();
682        for (activation_height, network_upgrade) in network.full_activation_list() {
683            // Zebra defines network upgrades based on incompatible consensus rule changes,
684            // but zcashd defines them based on ZIPs.
685            //
686            // All the network upgrades with a consensus branch ID are the same in Zebra and zcashd.
687            if let Some(branch_id) = network_upgrade.branch_id() {
688                // zcashd's RPC seems to ignore Disabled network upgrades, so Zebra does too.
689                let status = if tip_height >= activation_height {
690                    NetworkUpgradeStatus::Active
691                } else {
692                    NetworkUpgradeStatus::Pending
693                };
694
695                let upgrade = NetworkUpgradeInfo {
696                    name: network_upgrade,
697                    activation_height,
698                    status,
699                };
700                upgrades.insert(ConsensusBranchIdHex(branch_id), upgrade);
701            }
702        }
703
704        // `consensus` object
705        let next_block_height =
706            (tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
707        let consensus = TipConsensusBranch {
708            chain_tip: ConsensusBranchIdHex(
709                NetworkUpgrade::current(network, tip_height)
710                    .branch_id()
711                    .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
712            ),
713            next_block: ConsensusBranchIdHex(
714                NetworkUpgrade::current(network, next_block_height)
715                    .branch_id()
716                    .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
717            ),
718        };
719
720        let response = GetBlockChainInfo {
721            chain: network.bip70_network_name(),
722            blocks: tip_height,
723            best_block_hash: tip_hash,
724            estimated_height,
725            chain_supply: types::Balance::chain_supply(value_balance),
726            value_pools: types::Balance::value_pools(value_balance),
727            upgrades,
728            consensus,
729            headers: tip_height,
730            difficulty,
731            verification_progress,
732            // TODO: store work in the finalized state for each height (#7109)
733            chain_work: 0,
734            pruned: false,
735            size_on_disk,
736            // TODO: Investigate whether this needs to be implemented (it's sprout-only in zcashd)
737            commitments: 0,
738        };
739
740        Ok(response)
741    }
742
743    async fn get_address_balance(&self, address_strings: AddressStrings) -> Result<AddressBalance> {
744        let state = self.state.clone();
745
746        let valid_addresses = address_strings.valid_addresses()?;
747
748        let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
749        let response = state.oneshot(request).await.map_misc_error()?;
750
751        match response {
752            zebra_state::ReadResponse::AddressBalance(balance) => Ok(AddressBalance {
753                balance: u64::from(balance),
754            }),
755            _ => unreachable!("Unexpected response from state service: {response:?}"),
756        }
757    }
758
759    // TODO: use HexData or GetRawTransaction::Bytes to handle the transaction data argument
760    async fn send_raw_transaction(
761        &self,
762        raw_transaction_hex: String,
763        _allow_high_fees: Option<bool>,
764    ) -> Result<SentTransactionHash> {
765        let mempool = self.mempool.clone();
766        let queue_sender = self.queue_sender.clone();
767
768        // Reference for the legacy error code:
769        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L1259-L1260>
770        let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
771            .map_error(server::error::LegacyCode::Deserialization)?;
772        let raw_transaction = Transaction::zcash_deserialize(&*raw_transaction_bytes)
773            .map_error(server::error::LegacyCode::Deserialization)?;
774
775        let transaction_hash = raw_transaction.hash();
776
777        // send transaction to the rpc queue, ignore any error.
778        let unmined_transaction = UnminedTx::from(raw_transaction.clone());
779        let _ = queue_sender.send(unmined_transaction);
780
781        let transaction_parameter = mempool::Gossip::Tx(raw_transaction.into());
782        let request = mempool::Request::Queue(vec![transaction_parameter]);
783
784        let response = mempool.oneshot(request).await.map_misc_error()?;
785
786        let mut queue_results = match response {
787            mempool::Response::Queued(results) => results,
788            _ => unreachable!("incorrect response variant from mempool service"),
789        };
790
791        assert_eq!(
792            queue_results.len(),
793            1,
794            "mempool service returned more results than expected"
795        );
796
797        let queue_result = queue_results
798            .pop()
799            .expect("there should be exactly one item in Vec")
800            .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err))
801            .map_misc_error()?
802            .await
803            .map_misc_error()?;
804
805        tracing::debug!("sent transaction to mempool: {:?}", &queue_result);
806
807        queue_result
808            .map(|_| SentTransactionHash(transaction_hash))
809            // Reference for the legacy error code:
810            // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L1290-L1301>
811            // Note that this error code might not exactly match the one returned by zcashd
812            // since zcashd's error code selection logic is more granular. We'd need to
813            // propagate the error coming from the verifier to be able to return more specific
814            // error codes.
815            .map_error(server::error::LegacyCode::Verify)
816    }
817
818    // # Performance
819    //
820    // `lightwalletd` calls this RPC with verosity 1 for its initial sync of 2 million blocks, the
821    // performance of this RPC with verbosity 1 significantly affects `lightwalletd`s sync time.
822    //
823    // TODO:
824    // - use `height_from_signed_int()` to handle negative heights
825    //   (this might be better in the state request, because it needs the state height)
826    async fn get_block(&self, hash_or_height: String, verbosity: Option<u8>) -> Result<GetBlock> {
827        let mut state = self.state.clone();
828        let verbosity = verbosity.unwrap_or(1);
829        let network = self.network.clone();
830        let original_hash_or_height = hash_or_height.clone();
831
832        // If verbosity requires a call to `get_block_header`, resolve it here
833        let get_block_header_future = if matches!(verbosity, 1 | 2) {
834            Some(self.get_block_header(original_hash_or_height.clone(), Some(true)))
835        } else {
836            None
837        };
838
839        let hash_or_height =
840            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
841                // Reference for the legacy error code:
842                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
843                .map_error(server::error::LegacyCode::InvalidParameter)?;
844
845        if verbosity == 0 {
846            let request = zebra_state::ReadRequest::Block(hash_or_height);
847            let response = state
848                .ready()
849                .and_then(|service| service.call(request))
850                .await
851                .map_misc_error()?;
852
853            match response {
854                zebra_state::ReadResponse::Block(Some(block)) => Ok(GetBlock::Raw(block.into())),
855                zebra_state::ReadResponse::Block(None) => {
856                    Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
857                }
858                _ => unreachable!("unmatched response to a block request"),
859            }
860        } else if let Some(get_block_header_future) = get_block_header_future {
861            let get_block_header_result: Result<GetBlockHeader> = get_block_header_future.await;
862
863            let GetBlockHeader::Object(block_header) = get_block_header_result? else {
864                panic!("must return Object")
865            };
866
867            let GetBlockHeaderObject {
868                hash,
869                confirmations,
870                height,
871                version,
872                merkle_root,
873                block_commitments,
874                final_sapling_root,
875                sapling_tree_size,
876                time,
877                nonce,
878                solution,
879                bits,
880                difficulty,
881                previous_block_hash,
882                next_block_hash,
883            } = *block_header;
884
885            let transactions_request = match verbosity {
886                1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
887                2 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
888                _other => panic!("get_block_header_fut should be none"),
889            };
890
891            // # Concurrency
892            //
893            // We look up by block hash so the hash, transaction IDs, and confirmations
894            // are consistent.
895            let hash_or_height = hash.0.into();
896            let requests = vec![
897                // Get transaction IDs from the transaction index by block hash
898                //
899                // # Concurrency
900                //
901                // A block's transaction IDs are never modified, so all possible responses are
902                // valid. Clients that query block heights must be able to handle chain forks,
903                // including getting transaction IDs from any chain fork.
904                transactions_request,
905                // Orchard trees
906                zebra_state::ReadRequest::OrchardTree(hash_or_height),
907            ];
908
909            let mut futs = FuturesOrdered::new();
910
911            for request in requests {
912                futs.push_back(state.clone().oneshot(request));
913            }
914
915            let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
916            let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
917                zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
918                    tx_ids
919                        .ok_or_misc_error("block not found")?
920                        .iter()
921                        .map(|tx_id| GetBlockTransaction::Hash(*tx_id))
922                        .collect(),
923                    None,
924                ),
925                zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
926                    let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
927                    let block_time = block.header.time;
928                    let transactions =
929                        block
930                            .transactions
931                            .iter()
932                            .map(|tx| {
933                                GetBlockTransaction::Object(Box::new(
934                                    TransactionObject::from_transaction(
935                                        tx.clone(),
936                                        Some(height),
937                                        Some(confirmations.try_into().expect(
938                                            "should be less than max block height, i32::MAX",
939                                        )),
940                                        &network,
941                                        Some(block_time),
942                                    ),
943                                ))
944                            })
945                            .collect();
946                    (transactions, Some(size))
947                }
948                _ => unreachable!("unmatched response to a transaction_ids_for_block request"),
949            };
950
951            let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
952            let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
953                orchard_tree_response.map_misc_error()?
954            else {
955                unreachable!("unmatched response to a OrchardTree request");
956            };
957
958            let nu5_activation = NetworkUpgrade::Nu5.activation_height(&network);
959
960            // This could be `None` if there's a chain reorg between state queries.
961            let orchard_tree = orchard_tree.ok_or_misc_error("missing Orchard tree")?;
962
963            let final_orchard_root = match nu5_activation {
964                Some(activation_height) if height >= activation_height => {
965                    Some(orchard_tree.root().into())
966                }
967                _other => None,
968            };
969
970            let sapling = SaplingTrees {
971                size: sapling_tree_size,
972            };
973
974            let orchard_tree_size = orchard_tree.count();
975            let orchard = OrchardTrees {
976                size: orchard_tree_size,
977            };
978
979            let trees = GetBlockTrees { sapling, orchard };
980
981            Ok(GetBlock::Object {
982                hash,
983                confirmations,
984                height: Some(height),
985                version: Some(version),
986                merkle_root: Some(merkle_root),
987                time: Some(time),
988                nonce: Some(nonce),
989                solution: Some(solution),
990                bits: Some(bits),
991                difficulty: Some(difficulty),
992                tx,
993                trees,
994                size: size.map(|size| size as i64),
995                block_commitments: Some(block_commitments),
996                final_sapling_root: Some(final_sapling_root),
997                final_orchard_root,
998                previous_block_hash: Some(previous_block_hash),
999                next_block_hash,
1000            })
1001        } else {
1002            Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
1003        }
1004    }
1005
1006    async fn get_block_header(
1007        &self,
1008        hash_or_height: String,
1009        verbose: Option<bool>,
1010    ) -> Result<GetBlockHeader> {
1011        let state = self.state.clone();
1012        let verbose = verbose.unwrap_or(true);
1013        let network = self.network.clone();
1014
1015        let hash_or_height =
1016            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1017                // Reference for the legacy error code:
1018                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1019                .map_error(server::error::LegacyCode::InvalidParameter)?;
1020        let zebra_state::ReadResponse::BlockHeader {
1021            header,
1022            hash,
1023            height,
1024            next_block_hash,
1025        } = state
1026            .clone()
1027            .oneshot(zebra_state::ReadRequest::BlockHeader(hash_or_height))
1028            .await
1029            .map_err(|_| "block height not in best chain")
1030            .map_error(
1031                // ## Compatibility with `zcashd`.
1032                //
1033                // Since this function is reused by getblock(), we return the errors
1034                // expected by it (they differ whether a hash or a height was passed).
1035                if hash_or_height.hash().is_some() {
1036                    server::error::LegacyCode::InvalidAddressOrKey
1037                } else {
1038                    server::error::LegacyCode::InvalidParameter
1039                },
1040            )?
1041        else {
1042            panic!("unexpected response to BlockHeader request")
1043        };
1044
1045        let response = if !verbose {
1046            GetBlockHeader::Raw(HexData(header.zcash_serialize_to_vec().map_misc_error()?))
1047        } else {
1048            let zebra_state::ReadResponse::SaplingTree(sapling_tree) = state
1049                .clone()
1050                .oneshot(zebra_state::ReadRequest::SaplingTree(hash_or_height))
1051                .await
1052                .map_misc_error()?
1053            else {
1054                panic!("unexpected response to SaplingTree request")
1055            };
1056
1057            // This could be `None` if there's a chain reorg between state queries.
1058            let sapling_tree = sapling_tree.ok_or_misc_error("missing Sapling tree")?;
1059
1060            let zebra_state::ReadResponse::Depth(depth) = state
1061                .clone()
1062                .oneshot(zebra_state::ReadRequest::Depth(hash))
1063                .await
1064                .map_misc_error()?
1065            else {
1066                panic!("unexpected response to SaplingTree request")
1067            };
1068
1069            // From <https://zcash.github.io/rpc/getblock.html>
1070            // TODO: Deduplicate const definition, consider refactoring this to avoid duplicate logic
1071            const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
1072
1073            // Confirmations are one more than the depth.
1074            // Depth is limited by height, so it will never overflow an i64.
1075            let confirmations = depth
1076                .map(|depth| i64::from(depth) + 1)
1077                .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS);
1078
1079            let mut nonce = *header.nonce;
1080            nonce.reverse();
1081
1082            let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network);
1083            let sapling_tree_size = sapling_tree.count();
1084            let final_sapling_root: [u8; 32] =
1085                if sapling_activation.is_some() && height >= sapling_activation.unwrap() {
1086                    let mut root: [u8; 32] = sapling_tree.root().into();
1087                    root.reverse();
1088                    root
1089                } else {
1090                    [0; 32]
1091                };
1092
1093            let difficulty = header.difficulty_threshold.relative_to_network(&network);
1094
1095            let block_commitments = match header.commitment(&network, height).expect(
1096                "Unexpected failure while parsing the blockcommitments field in get_block_header",
1097            ) {
1098                Commitment::PreSaplingReserved(bytes) => bytes,
1099                Commitment::FinalSaplingRoot(_) => final_sapling_root,
1100                Commitment::ChainHistoryActivationReserved => [0; 32],
1101                Commitment::ChainHistoryRoot(root) => root.bytes_in_display_order(),
1102                Commitment::ChainHistoryBlockTxAuthCommitment(hash) => {
1103                    hash.bytes_in_display_order()
1104                }
1105            };
1106
1107            let block_header = GetBlockHeaderObject {
1108                hash: GetBlockHash(hash),
1109                confirmations,
1110                height,
1111                version: header.version,
1112                merkle_root: header.merkle_root,
1113                block_commitments,
1114                final_sapling_root,
1115                sapling_tree_size,
1116                time: header.time.timestamp(),
1117                nonce,
1118                solution: header.solution,
1119                bits: header.difficulty_threshold,
1120                difficulty,
1121                previous_block_hash: GetBlockHash(header.previous_block_hash),
1122                next_block_hash: next_block_hash.map(GetBlockHash),
1123            };
1124
1125            GetBlockHeader::Object(Box::new(block_header))
1126        };
1127
1128        Ok(response)
1129    }
1130
1131    fn get_best_block_hash(&self) -> Result<GetBlockHash> {
1132        self.latest_chain_tip
1133            .best_tip_hash()
1134            .map(GetBlockHash)
1135            .ok_or_misc_error("No blocks in state")
1136    }
1137
1138    fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHash> {
1139        self.latest_chain_tip
1140            .best_tip_height_and_hash()
1141            .map(|(height, hash)| GetBlockHeightAndHash { height, hash })
1142            .ok_or_misc_error("No blocks in state")
1143    }
1144
1145    async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempool> {
1146        #[allow(unused)]
1147        let verbose = verbose.unwrap_or(false);
1148
1149        use zebra_chain::block::MAX_BLOCK_BYTES;
1150
1151        // Determines whether the output of this RPC is sorted like zcashd
1152        let should_use_zcashd_order = self.debug_like_zcashd;
1153
1154        let mut mempool = self.mempool.clone();
1155
1156        let request = if should_use_zcashd_order || verbose {
1157            mempool::Request::FullTransactions
1158        } else {
1159            mempool::Request::TransactionIds
1160        };
1161
1162        // `zcashd` doesn't check if it is synced to the tip here, so we don't either.
1163        let response = mempool
1164            .ready()
1165            .and_then(|service| service.call(request))
1166            .await
1167            .map_misc_error()?;
1168
1169        match response {
1170            mempool::Response::FullTransactions {
1171                mut transactions,
1172                transaction_dependencies,
1173                last_seen_tip_hash: _,
1174            } => {
1175                if verbose {
1176                    let map = transactions
1177                        .iter()
1178                        .map(|unmined_tx| {
1179                            (
1180                                unmined_tx.transaction.id.mined_id().encode_hex(),
1181                                MempoolObject::from_verified_unmined_tx(
1182                                    unmined_tx,
1183                                    &transactions,
1184                                    &transaction_dependencies,
1185                                ),
1186                            )
1187                        })
1188                        .collect::<HashMap<_, _>>();
1189                    Ok(GetRawMempool::Verbose(map))
1190                } else {
1191                    // Sort transactions in descending order by fee/size, using
1192                    // hash in serialized byte order as a tie-breaker. Note that
1193                    // this is only done in not verbose because in verbose mode
1194                    // a dictionary is returned, where order does not matter.
1195                    transactions.sort_by_cached_key(|tx| {
1196                        // zcashd uses modified fee here but Zebra doesn't currently
1197                        // support prioritizing transactions
1198                        std::cmp::Reverse((
1199                            i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
1200                                / tx.transaction.size as u128,
1201                            // transaction hashes are compared in their serialized byte-order.
1202                            tx.transaction.id.mined_id(),
1203                        ))
1204                    });
1205                    let tx_ids: Vec<String> = transactions
1206                        .iter()
1207                        .map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
1208                        .collect();
1209
1210                    Ok(GetRawMempool::TxIds(tx_ids))
1211                }
1212            }
1213
1214            mempool::Response::TransactionIds(unmined_transaction_ids) => {
1215                let mut tx_ids: Vec<String> = unmined_transaction_ids
1216                    .iter()
1217                    .map(|id| id.mined_id().encode_hex())
1218                    .collect();
1219
1220                // Sort returned transaction IDs in numeric/string order.
1221                tx_ids.sort();
1222
1223                Ok(GetRawMempool::TxIds(tx_ids))
1224            }
1225
1226            _ => unreachable!("unmatched response to a transactionids request"),
1227        }
1228    }
1229
1230    async fn get_raw_transaction(
1231        &self,
1232        txid: String,
1233        verbose: Option<u8>,
1234    ) -> Result<GetRawTransaction> {
1235        let mut state = self.state.clone();
1236        let mut mempool = self.mempool.clone();
1237        let verbose = verbose.unwrap_or(0) != 0;
1238
1239        // Reference for the legacy error code:
1240        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L544>
1241        let txid = transaction::Hash::from_hex(txid)
1242            .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1243
1244        // Check the mempool first.
1245        match mempool
1246            .ready()
1247            .and_then(|service| {
1248                service.call(mempool::Request::TransactionsByMinedId([txid].into()))
1249            })
1250            .await
1251            .map_misc_error()?
1252        {
1253            mempool::Response::Transactions(txns) => {
1254                if let Some(tx) = txns.first() {
1255                    return Ok(if verbose {
1256                        GetRawTransaction::Object(Box::new(TransactionObject::from_transaction(
1257                            tx.transaction.clone(),
1258                            None,
1259                            None,
1260                            &self.network,
1261                            None,
1262                        )))
1263                    } else {
1264                        let hex = tx.transaction.clone().into();
1265                        GetRawTransaction::Raw(hex)
1266                    });
1267                }
1268            }
1269
1270            _ => unreachable!("unmatched response to a `TransactionsByMinedId` request"),
1271        };
1272
1273        // If the tx wasn't in the mempool, check the state.
1274        match state
1275            .ready()
1276            .and_then(|service| service.call(zebra_state::ReadRequest::Transaction(txid)))
1277            .await
1278            .map_misc_error()?
1279        {
1280            zebra_state::ReadResponse::Transaction(Some(tx)) => Ok(if verbose {
1281                GetRawTransaction::Object(Box::new(TransactionObject::from_transaction(
1282                    tx.tx.clone(),
1283                    Some(tx.height),
1284                    Some(tx.confirmations),
1285                    &self.network,
1286                    // TODO: Performance gain:
1287                    // https://github.com/ZcashFoundation/zebra/pull/9458#discussion_r2059352752
1288                    Some(tx.block_time),
1289                )))
1290            } else {
1291                let hex = tx.tx.into();
1292                GetRawTransaction::Raw(hex)
1293            }),
1294
1295            zebra_state::ReadResponse::Transaction(None) => {
1296                Err("No such mempool or main chain transaction")
1297                    .map_error(server::error::LegacyCode::InvalidAddressOrKey)
1298            }
1299
1300            _ => unreachable!("unmatched response to a `Transaction` read request"),
1301        }
1302    }
1303
1304    // TODO:
1305    // - use `height_from_signed_int()` to handle negative heights
1306    //   (this might be better in the state request, because it needs the state height)
1307    async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestate> {
1308        let mut state = self.state.clone();
1309        let network = self.network.clone();
1310
1311        let hash_or_height =
1312            HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1313                // Reference for the legacy error code:
1314                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1315                .map_error(server::error::LegacyCode::InvalidParameter)?;
1316
1317        // Fetch the block referenced by [`hash_or_height`] from the state.
1318        //
1319        // # Concurrency
1320        //
1321        // For consistency, this lookup must be performed first, then all the other lookups must
1322        // be based on the hash.
1323        //
1324        // TODO: If this RPC is called a lot, just get the block header, rather than the whole block.
1325        let block = match state
1326            .ready()
1327            .and_then(|service| service.call(zebra_state::ReadRequest::Block(hash_or_height)))
1328            .await
1329            .map_misc_error()?
1330        {
1331            zebra_state::ReadResponse::Block(Some(block)) => block,
1332            zebra_state::ReadResponse::Block(None) => {
1333                // Reference for the legacy error code:
1334                // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
1335                return Err("the requested block is not in the main chain")
1336                    .map_error(server::error::LegacyCode::InvalidParameter);
1337            }
1338            _ => unreachable!("unmatched response to a block request"),
1339        };
1340
1341        let hash = hash_or_height
1342            .hash_or_else(|_| Some(block.hash()))
1343            .expect("block hash");
1344
1345        let height = hash_or_height
1346            .height_or_else(|_| block.coinbase_height())
1347            .expect("verified blocks have a coinbase height");
1348
1349        let time = u32::try_from(block.header.time.timestamp())
1350            .expect("Timestamps of valid blocks always fit into u32.");
1351
1352        let sapling_nu = zcash_primitives::consensus::NetworkUpgrade::Sapling;
1353        let sapling = if network.is_nu_active(sapling_nu, height.into()) {
1354            match state
1355                .ready()
1356                .and_then(|service| {
1357                    service.call(zebra_state::ReadRequest::SaplingTree(hash.into()))
1358                })
1359                .await
1360                .map_misc_error()?
1361            {
1362                zebra_state::ReadResponse::SaplingTree(tree) => tree.map(|t| t.to_rpc_bytes()),
1363                _ => unreachable!("unmatched response to a Sapling tree request"),
1364            }
1365        } else {
1366            None
1367        };
1368
1369        let orchard_nu = zcash_primitives::consensus::NetworkUpgrade::Nu5;
1370        let orchard = if network.is_nu_active(orchard_nu, height.into()) {
1371            match state
1372                .ready()
1373                .and_then(|service| {
1374                    service.call(zebra_state::ReadRequest::OrchardTree(hash.into()))
1375                })
1376                .await
1377                .map_misc_error()?
1378            {
1379                zebra_state::ReadResponse::OrchardTree(tree) => tree.map(|t| t.to_rpc_bytes()),
1380                _ => unreachable!("unmatched response to an Orchard tree request"),
1381            }
1382        } else {
1383            None
1384        };
1385
1386        Ok(GetTreestate::from_parts(
1387            hash, height, time, sapling, orchard,
1388        ))
1389    }
1390
1391    async fn z_get_subtrees_by_index(
1392        &self,
1393        pool: String,
1394        start_index: NoteCommitmentSubtreeIndex,
1395        limit: Option<NoteCommitmentSubtreeIndex>,
1396    ) -> Result<GetSubtrees> {
1397        let mut state = self.state.clone();
1398
1399        const POOL_LIST: &[&str] = &["sapling", "orchard"];
1400
1401        if pool == "sapling" {
1402            let request = zebra_state::ReadRequest::SaplingSubtrees { start_index, limit };
1403            let response = state
1404                .ready()
1405                .and_then(|service| service.call(request))
1406                .await
1407                .map_misc_error()?;
1408
1409            let subtrees = match response {
1410                zebra_state::ReadResponse::SaplingSubtrees(subtrees) => subtrees,
1411                _ => unreachable!("unmatched response to a subtrees request"),
1412            };
1413
1414            let subtrees = subtrees
1415                .values()
1416                .map(|subtree| SubtreeRpcData {
1417                    root: subtree.root.encode_hex(),
1418                    end_height: subtree.end_height,
1419                })
1420                .collect();
1421
1422            Ok(GetSubtrees {
1423                pool,
1424                start_index,
1425                subtrees,
1426            })
1427        } else if pool == "orchard" {
1428            let request = zebra_state::ReadRequest::OrchardSubtrees { start_index, limit };
1429            let response = state
1430                .ready()
1431                .and_then(|service| service.call(request))
1432                .await
1433                .map_misc_error()?;
1434
1435            let subtrees = match response {
1436                zebra_state::ReadResponse::OrchardSubtrees(subtrees) => subtrees,
1437                _ => unreachable!("unmatched response to a subtrees request"),
1438            };
1439
1440            let subtrees = subtrees
1441                .values()
1442                .map(|subtree| SubtreeRpcData {
1443                    root: subtree.root.encode_hex(),
1444                    end_height: subtree.end_height,
1445                })
1446                .collect();
1447
1448            Ok(GetSubtrees {
1449                pool,
1450                start_index,
1451                subtrees,
1452            })
1453        } else {
1454            Err(ErrorObject::owned(
1455                server::error::LegacyCode::Misc.into(),
1456                format!("invalid pool name, must be one of: {:?}", POOL_LIST).as_str(),
1457                None::<()>,
1458            ))
1459        }
1460    }
1461
1462    async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
1463        let mut state = self.state.clone();
1464        let latest_chain_tip = self.latest_chain_tip.clone();
1465
1466        let height_range = build_height_range(
1467            request.start,
1468            request.end,
1469            best_chain_tip_height(&latest_chain_tip)?,
1470        )?;
1471
1472        let valid_addresses = AddressStrings {
1473            addresses: request.addresses,
1474        }
1475        .valid_addresses()?;
1476
1477        let request = zebra_state::ReadRequest::TransactionIdsByAddresses {
1478            addresses: valid_addresses,
1479            height_range,
1480        };
1481        let response = state
1482            .ready()
1483            .and_then(|service| service.call(request))
1484            .await
1485            .map_misc_error()?;
1486
1487        let hashes = match response {
1488            zebra_state::ReadResponse::AddressesTransactionIds(hashes) => {
1489                let mut last_tx_location = TransactionLocation::from_usize(Height(0), 0);
1490
1491                hashes
1492                    .iter()
1493                    .map(|(tx_loc, tx_id)| {
1494                        // Check that the returned transactions are in chain order.
1495                        assert!(
1496                            *tx_loc > last_tx_location,
1497                            "Transactions were not in chain order:\n\
1498                                 {tx_loc:?} {tx_id:?} was after:\n\
1499                                 {last_tx_location:?}",
1500                        );
1501
1502                        last_tx_location = *tx_loc;
1503
1504                        tx_id.to_string()
1505                    })
1506                    .collect()
1507            }
1508            _ => unreachable!("unmatched response to a TransactionsByAddresses request"),
1509        };
1510
1511        Ok(hashes)
1512    }
1513
1514    async fn get_address_utxos(
1515        &self,
1516        address_strings: AddressStrings,
1517    ) -> Result<Vec<GetAddressUtxos>> {
1518        let mut state = self.state.clone();
1519        let mut response_utxos = vec![];
1520
1521        let valid_addresses = address_strings.valid_addresses()?;
1522
1523        // get utxos data for addresses
1524        let request = zebra_state::ReadRequest::UtxosByAddresses(valid_addresses);
1525        let response = state
1526            .ready()
1527            .and_then(|service| service.call(request))
1528            .await
1529            .map_misc_error()?;
1530        let utxos = match response {
1531            zebra_state::ReadResponse::AddressUtxos(utxos) => utxos,
1532            _ => unreachable!("unmatched response to a UtxosByAddresses request"),
1533        };
1534
1535        let mut last_output_location = OutputLocation::from_usize(Height(0), 0, 0);
1536
1537        for utxo_data in utxos.utxos() {
1538            let address = utxo_data.0;
1539            let txid = *utxo_data.1;
1540            let height = utxo_data.2.height();
1541            let output_index = utxo_data.2.output_index();
1542            let script = utxo_data.3.lock_script.clone();
1543            let satoshis = u64::from(utxo_data.3.value);
1544
1545            let output_location = *utxo_data.2;
1546            // Check that the returned UTXOs are in chain order.
1547            assert!(
1548                output_location > last_output_location,
1549                "UTXOs were not in chain order:\n\
1550                     {output_location:?} {address:?} {txid:?} was after:\n\
1551                     {last_output_location:?}",
1552            );
1553
1554            let entry = GetAddressUtxos {
1555                address,
1556                txid,
1557                output_index,
1558                script,
1559                satoshis,
1560                height,
1561            };
1562            response_utxos.push(entry);
1563
1564            last_output_location = output_location;
1565        }
1566
1567        Ok(response_utxos)
1568    }
1569
1570    fn stop(&self) -> Result<String> {
1571        #[cfg(not(target_os = "windows"))]
1572        if self.network.is_regtest() {
1573            match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
1574                Ok(_) => Ok("Zebra server stopping".to_string()),
1575                Err(error) => Err(ErrorObject::owned(
1576                    ErrorCode::InternalError.code(),
1577                    format!("Failed to shut down: {}", error).as_str(),
1578                    None::<()>,
1579                )),
1580            }
1581        } else {
1582            Err(ErrorObject::borrowed(
1583                ErrorCode::MethodNotFound.code(),
1584                "stop is only available on regtest networks",
1585                None,
1586            ))
1587        }
1588        #[cfg(target_os = "windows")]
1589        Err(ErrorObject::borrowed(
1590            ErrorCode::MethodNotFound.code(),
1591            "stop is not available in windows targets",
1592            None,
1593        ))
1594    }
1595}
1596
1597/// Returns the best chain tip height of `latest_chain_tip`,
1598/// or an RPC error if there are no blocks in the state.
1599pub fn best_chain_tip_height<Tip>(latest_chain_tip: &Tip) -> Result<Height>
1600where
1601    Tip: ChainTip + Clone + Send + Sync + 'static,
1602{
1603    latest_chain_tip
1604        .best_tip_height()
1605        .ok_or_misc_error("No blocks in state")
1606}
1607
1608/// Response to a `getinfo` RPC request.
1609///
1610/// See the notes for the [`Rpc::get_info` method].
1611#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1612pub struct GetInfo {
1613    /// The node version
1614    version: u64,
1615
1616    /// The node version build number
1617    build: String,
1618
1619    /// The server sub-version identifier, used as the network protocol user-agent
1620    subversion: String,
1621
1622    /// The protocol version
1623    #[serde(rename = "protocolversion")]
1624    protocol_version: u32,
1625
1626    /// The current number of blocks processed in the server
1627    blocks: u32,
1628
1629    /// The total (inbound and outbound) number of connections the node has
1630    connections: usize,
1631
1632    /// The proxy (if any) used by the server. Currently always `None` in Zebra.
1633    #[serde(skip_serializing_if = "Option::is_none")]
1634    proxy: Option<String>,
1635
1636    /// The current network difficulty
1637    difficulty: f64,
1638
1639    /// True if the server is running in testnet mode, false otherwise
1640    testnet: bool,
1641
1642    /// The minimum transaction fee in ZEC/kB
1643    #[serde(rename = "paytxfee")]
1644    pay_tx_fee: f64,
1645
1646    /// The minimum relay fee for non-free transactions in ZEC/kB
1647    #[serde(rename = "relayfee")]
1648    relay_fee: f64,
1649
1650    /// The last error or warning message, or "no errors" if there are no errors
1651    errors: String,
1652
1653    /// The time of the last error or warning message, or "no errors timestamp" if there are no errors
1654    #[serde(rename = "errorstimestamp")]
1655    errors_timestamp: String,
1656}
1657
1658impl Default for GetInfo {
1659    fn default() -> Self {
1660        GetInfo {
1661            version: 0,
1662            build: "some build version".to_string(),
1663            subversion: "some subversion".to_string(),
1664            protocol_version: 0,
1665            blocks: 0,
1666            connections: 0,
1667            proxy: None,
1668            difficulty: 0.0,
1669            testnet: false,
1670            pay_tx_fee: 0.0,
1671            relay_fee: 0.0,
1672            errors: "no errors".to_string(),
1673            errors_timestamp: "no errors timestamp".to_string(),
1674        }
1675    }
1676}
1677
1678impl GetInfo {
1679    /// Constructs [`GetInfo`] from its constituent parts.
1680    #[allow(clippy::too_many_arguments)]
1681    pub fn from_parts(
1682        version: u64,
1683        build: String,
1684        subversion: String,
1685        protocol_version: u32,
1686        blocks: u32,
1687        connections: usize,
1688        proxy: Option<String>,
1689        difficulty: f64,
1690        testnet: bool,
1691        pay_tx_fee: f64,
1692        relay_fee: f64,
1693        errors: String,
1694        errors_timestamp: String,
1695    ) -> Self {
1696        Self {
1697            version,
1698            build,
1699            subversion,
1700            protocol_version,
1701            blocks,
1702            connections,
1703            proxy,
1704            difficulty,
1705            testnet,
1706            pay_tx_fee,
1707            relay_fee,
1708            errors,
1709            errors_timestamp,
1710        }
1711    }
1712
1713    /// Returns the contents of ['GetInfo'].
1714    pub fn into_parts(
1715        self,
1716    ) -> (
1717        u64,
1718        String,
1719        String,
1720        u32,
1721        u32,
1722        usize,
1723        Option<String>,
1724        f64,
1725        bool,
1726        f64,
1727        f64,
1728        String,
1729        String,
1730    ) {
1731        (
1732            self.version,
1733            self.build,
1734            self.subversion,
1735            self.protocol_version,
1736            self.blocks,
1737            self.connections,
1738            self.proxy,
1739            self.difficulty,
1740            self.testnet,
1741            self.pay_tx_fee,
1742            self.relay_fee,
1743            self.errors,
1744            self.errors_timestamp,
1745        )
1746    }
1747
1748    /// Create the node version number.
1749    pub fn version(build_string: &str) -> Option<u64> {
1750        let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
1751        let build_number = semver_version
1752            .build
1753            .as_str()
1754            .split('.')
1755            .next()
1756            .and_then(|num_str| num_str.parse::<u64>().ok())
1757            .unwrap_or_default();
1758
1759        // https://github.com/zcash/zcash/blob/v6.1.0/src/clientversion.h#L55-L59
1760        let version_number = 1_000_000 * semver_version.major
1761            + 10_000 * semver_version.minor
1762            + 100 * semver_version.patch
1763            + build_number;
1764
1765        Some(version_number)
1766    }
1767}
1768
1769/// Response to a `getblockchaininfo` RPC request.
1770///
1771/// See the notes for the [`Rpc::get_blockchain_info` method].
1772#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1773pub struct GetBlockChainInfo {
1774    /// Current network name as defined in BIP70 (main, test, regtest)
1775    chain: String,
1776
1777    /// The current number of blocks processed in the server, numeric
1778    blocks: Height,
1779
1780    /// The current number of headers we have validated in the best chain, that is,
1781    /// the height of the best chain.
1782    headers: Height,
1783
1784    /// The estimated network solution rate in Sol/s.
1785    difficulty: f64,
1786
1787    /// The verification progress relative to the estimated network chain tip.
1788    #[serde(rename = "verificationprogress")]
1789    verification_progress: f64,
1790
1791    /// The total amount of work in the best chain, hex-encoded.
1792    #[serde(rename = "chainwork")]
1793    chain_work: u64,
1794
1795    /// Whether this node is pruned, currently always false in Zebra.
1796    pruned: bool,
1797
1798    /// The estimated size of the block and undo files on disk
1799    size_on_disk: u64,
1800
1801    /// The current number of note commitments in the commitment tree
1802    commitments: u64,
1803
1804    /// The hash of the currently best block, in big-endian order, hex-encoded
1805    #[serde(rename = "bestblockhash", with = "hex")]
1806    best_block_hash: block::Hash,
1807
1808    /// If syncing, the estimated height of the chain, else the current best height, numeric.
1809    ///
1810    /// In Zebra, this is always the height estimate, so it might be a little inaccurate.
1811    #[serde(rename = "estimatedheight")]
1812    estimated_height: Height,
1813
1814    /// Chain supply balance
1815    #[serde(rename = "chainSupply")]
1816    chain_supply: types::Balance,
1817
1818    /// Value pool balances
1819    #[serde(rename = "valuePools")]
1820    value_pools: [types::Balance; 5],
1821
1822    /// Status of network upgrades
1823    upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
1824
1825    /// Branch IDs of the current and upcoming consensus rules
1826    consensus: TipConsensusBranch,
1827}
1828
1829impl Default for GetBlockChainInfo {
1830    fn default() -> Self {
1831        GetBlockChainInfo {
1832            chain: "main".to_string(),
1833            blocks: Height(1),
1834            best_block_hash: block::Hash([0; 32]),
1835            estimated_height: Height(1),
1836            chain_supply: types::Balance::chain_supply(Default::default()),
1837            value_pools: types::Balance::zero_pools(),
1838            upgrades: IndexMap::new(),
1839            consensus: TipConsensusBranch {
1840                chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
1841                next_block: ConsensusBranchIdHex(ConsensusBranchId::default()),
1842            },
1843            headers: Height(1),
1844            difficulty: 0.0,
1845            verification_progress: 0.0,
1846            chain_work: 0,
1847            pruned: false,
1848            size_on_disk: 0,
1849            commitments: 0,
1850        }
1851    }
1852}
1853
1854impl GetBlockChainInfo {
1855    /// Creates a new [`GetBlockChainInfo`] instance.
1856    #[allow(clippy::too_many_arguments)]
1857    pub fn new(
1858        chain: String,
1859        blocks: Height,
1860        best_block_hash: block::Hash,
1861        estimated_height: Height,
1862        chain_supply: types::Balance,
1863        value_pools: [types::Balance; 5],
1864        upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
1865        consensus: TipConsensusBranch,
1866        headers: Height,
1867        difficulty: f64,
1868        verification_progress: f64,
1869        chain_work: u64,
1870        pruned: bool,
1871        size_on_disk: u64,
1872        commitments: u64,
1873    ) -> Self {
1874        Self {
1875            chain,
1876            blocks,
1877            best_block_hash,
1878            estimated_height,
1879            chain_supply,
1880            value_pools,
1881            upgrades,
1882            consensus,
1883            headers,
1884            difficulty,
1885            verification_progress,
1886            chain_work,
1887            pruned,
1888            size_on_disk,
1889            commitments,
1890        }
1891    }
1892
1893    /// Returns the current network name as defined in BIP70 (main, test, regtest).
1894    pub fn chain(&self) -> String {
1895        self.chain.clone()
1896    }
1897
1898    /// Returns the current number of blocks processed in the server.
1899    pub fn blocks(&self) -> Height {
1900        self.blocks
1901    }
1902
1903    /// Returns the hash of the current best chain tip block, in big-endian order, hex-encoded.
1904    pub fn best_block_hash(&self) -> &block::Hash {
1905        &self.best_block_hash
1906    }
1907
1908    /// Returns the estimated height of the chain.
1909    ///
1910    /// If syncing, the estimated height of the chain, else the current best height, numeric.
1911    ///
1912    /// In Zebra, this is always the height estimate, so it might be a little inaccurate.
1913    pub fn estimated_height(&self) -> Height {
1914        self.estimated_height
1915    }
1916
1917    /// Returns the value pool balances.
1918    pub fn value_pools(&self) -> &[types::Balance; 5] {
1919        &self.value_pools
1920    }
1921
1922    /// Returns the network upgrades.
1923    pub fn upgrades(&self) -> &IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo> {
1924        &self.upgrades
1925    }
1926
1927    /// Returns the Branch IDs of the current and upcoming consensus rules.
1928    pub fn consensus(&self) -> &TipConsensusBranch {
1929        &self.consensus
1930    }
1931}
1932
1933/// A wrapper type with a list of transparent address strings.
1934///
1935/// This is used for the input parameter of [`RpcServer::get_address_balance`],
1936/// [`RpcServer::get_address_tx_ids`] and [`RpcServer::get_address_utxos`].
1937#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize)]
1938pub struct AddressStrings {
1939    /// A list of transparent address strings.
1940    addresses: Vec<String>,
1941}
1942
1943impl AddressStrings {
1944    /// Creates a new `AddressStrings` given a vector.
1945    #[cfg(test)]
1946    pub fn new(addresses: Vec<String>) -> AddressStrings {
1947        AddressStrings { addresses }
1948    }
1949
1950    /// Creates a new [`AddressStrings`] from a given vector, returns an error if any addresses are incorrect.
1951    pub fn new_valid(addresses: Vec<String>) -> Result<AddressStrings> {
1952        let address_strings = Self { addresses };
1953        address_strings.clone().valid_addresses()?;
1954        Ok(address_strings)
1955    }
1956
1957    /// Given a list of addresses as strings:
1958    /// - check if provided list have all valid transparent addresses.
1959    /// - return valid addresses as a set of `Address`.
1960    pub fn valid_addresses(self) -> Result<HashSet<Address>> {
1961        // Reference for the legacy error code:
1962        // <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/misc.cpp#L783-L784>
1963        let valid_addresses: HashSet<Address> = self
1964            .addresses
1965            .into_iter()
1966            .map(|address| {
1967                address
1968                    .parse()
1969                    .map_error(server::error::LegacyCode::InvalidAddressOrKey)
1970            })
1971            .collect::<Result<_>>()?;
1972
1973        Ok(valid_addresses)
1974    }
1975
1976    /// Given a list of addresses as strings:
1977    /// - check if provided list have all valid transparent addresses.
1978    /// - return valid addresses as a vec of strings.
1979    pub fn valid_address_strings(self) -> Result<Vec<String>> {
1980        self.clone().valid_addresses()?;
1981        Ok(self.addresses)
1982    }
1983}
1984
1985/// The transparent balance of a set of addresses.
1986#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize)]
1987pub struct AddressBalance {
1988    /// The total transparent balance.
1989    pub balance: u64,
1990}
1991
1992/// A hex-encoded [`ConsensusBranchId`] string.
1993#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
1994pub struct ConsensusBranchIdHex(#[serde(with = "hex")] ConsensusBranchId);
1995
1996impl ConsensusBranchIdHex {
1997    /// Returns a new instance of ['ConsensusBranchIdHex'].
1998    pub fn new(consensus_branch_id: u32) -> Self {
1999        ConsensusBranchIdHex(consensus_branch_id.into())
2000    }
2001
2002    /// Returns the value of the ['ConsensusBranchId'].
2003    pub fn inner(&self) -> u32 {
2004        self.0.into()
2005    }
2006}
2007
2008/// Information about [`NetworkUpgrade`] activation.
2009#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
2010pub struct NetworkUpgradeInfo {
2011    /// Name of upgrade, string.
2012    ///
2013    /// Ignored by lightwalletd, but useful for debugging.
2014    name: NetworkUpgrade,
2015
2016    /// Block height of activation, numeric.
2017    #[serde(rename = "activationheight")]
2018    activation_height: Height,
2019
2020    /// Status of upgrade, string.
2021    status: NetworkUpgradeStatus,
2022}
2023
2024impl NetworkUpgradeInfo {
2025    /// Constructs [`NetworkUpgradeInfo`] from its constituent parts.
2026    pub fn from_parts(
2027        name: NetworkUpgrade,
2028        activation_height: Height,
2029        status: NetworkUpgradeStatus,
2030    ) -> Self {
2031        Self {
2032            name,
2033            activation_height,
2034            status,
2035        }
2036    }
2037
2038    /// Returns the contents of ['NetworkUpgradeInfo'].
2039    pub fn into_parts(self) -> (NetworkUpgrade, Height, NetworkUpgradeStatus) {
2040        (self.name, self.activation_height, self.status)
2041    }
2042}
2043
2044/// The activation status of a [`NetworkUpgrade`].
2045#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
2046pub enum NetworkUpgradeStatus {
2047    /// The network upgrade is currently active.
2048    ///
2049    /// Includes all network upgrades that have previously activated,
2050    /// even if they are not the most recent network upgrade.
2051    #[serde(rename = "active")]
2052    Active,
2053
2054    /// The network upgrade does not have an activation height.
2055    #[serde(rename = "disabled")]
2056    Disabled,
2057
2058    /// The network upgrade has an activation height, but we haven't reached it yet.
2059    #[serde(rename = "pending")]
2060    Pending,
2061}
2062
2063/// The [`ConsensusBranchId`]s for the tip and the next block.
2064///
2065/// These branch IDs are different when the next block is a network upgrade activation block.
2066#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
2067pub struct TipConsensusBranch {
2068    /// Branch ID used to validate the current chain tip, big-endian, hex-encoded.
2069    #[serde(rename = "chaintip")]
2070    chain_tip: ConsensusBranchIdHex,
2071
2072    /// Branch ID used to validate the next block, big-endian, hex-encoded.
2073    #[serde(rename = "nextblock")]
2074    next_block: ConsensusBranchIdHex,
2075}
2076
2077impl TipConsensusBranch {
2078    /// Constructs [`TipConsensusBranch`] from its constituent parts.
2079    pub fn from_parts(chain_tip: u32, next_block: u32) -> Self {
2080        Self {
2081            chain_tip: ConsensusBranchIdHex::new(chain_tip),
2082            next_block: ConsensusBranchIdHex::new(next_block),
2083        }
2084    }
2085
2086    /// Returns the contents of ['TipConsensusBranch'].
2087    pub fn into_parts(self) -> (u32, u32) {
2088        (self.chain_tip.inner(), self.next_block.inner())
2089    }
2090}
2091
2092/// Response to a `sendrawtransaction` RPC request.
2093///
2094/// Contains the hex-encoded hash of the sent transaction.
2095///
2096/// See the notes for the [`Rpc::send_raw_transaction` method].
2097#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
2098pub struct SentTransactionHash(#[serde(with = "hex")] transaction::Hash);
2099
2100impl Default for SentTransactionHash {
2101    fn default() -> Self {
2102        Self(transaction::Hash::from([0; 32]))
2103    }
2104}
2105
2106impl SentTransactionHash {
2107    /// Constructs a new [`SentTransactionHash`].
2108    pub fn new(hash: transaction::Hash) -> Self {
2109        SentTransactionHash(hash)
2110    }
2111
2112    /// Returns the contents of ['SentTransactionHash'].
2113    pub fn inner(&self) -> transaction::Hash {
2114        self.0
2115    }
2116}
2117
2118/// Response to a `getblock` RPC request.
2119///
2120/// See the notes for the [`RpcServer::get_block`] method.
2121#[derive(Clone, Debug, PartialEq, serde::Serialize)]
2122#[serde(untagged)]
2123#[allow(clippy::large_enum_variant)] //TODO: create a struct for the Object and Box it
2124pub enum GetBlock {
2125    /// The request block, hex-encoded.
2126    Raw(#[serde(with = "hex")] SerializedBlock),
2127    /// The block object.
2128    Object {
2129        /// The hash of the requested block.
2130        hash: GetBlockHash,
2131
2132        /// The number of confirmations of this block in the best chain,
2133        /// or -1 if it is not in the best chain.
2134        confirmations: i64,
2135
2136        /// The block size. TODO: fill it
2137        #[serde(skip_serializing_if = "Option::is_none")]
2138        size: Option<i64>,
2139
2140        /// The height of the requested block.
2141        #[serde(skip_serializing_if = "Option::is_none")]
2142        height: Option<Height>,
2143
2144        /// The version field of the requested block.
2145        #[serde(skip_serializing_if = "Option::is_none")]
2146        version: Option<u32>,
2147
2148        /// The merkle root of the requested block.
2149        #[serde(with = "opthex", rename = "merkleroot")]
2150        #[serde(skip_serializing_if = "Option::is_none")]
2151        merkle_root: Option<block::merkle::Root>,
2152
2153        /// The blockcommitments field of the requested block. Its interpretation changes
2154        /// depending on the network and height.
2155        #[serde(with = "opthex", rename = "blockcommitments")]
2156        #[serde(skip_serializing_if = "Option::is_none")]
2157        block_commitments: Option<[u8; 32]>,
2158
2159        // `authdataroot` would be here. Undocumented. TODO: decide if we want to support it
2160        //
2161        /// The root of the Sapling commitment tree after applying this block.
2162        #[serde(with = "opthex", rename = "finalsaplingroot")]
2163        #[serde(skip_serializing_if = "Option::is_none")]
2164        final_sapling_root: Option<[u8; 32]>,
2165
2166        /// The root of the Orchard commitment tree after applying this block.
2167        #[serde(with = "opthex", rename = "finalorchardroot")]
2168        #[serde(skip_serializing_if = "Option::is_none")]
2169        final_orchard_root: Option<[u8; 32]>,
2170
2171        // `chainhistoryroot` would be here. Undocumented. TODO: decide if we want to support it
2172        //
2173        /// List of transactions in block order, hex-encoded if verbosity=1 or
2174        /// as objects if verbosity=2.
2175        tx: Vec<GetBlockTransaction>,
2176
2177        /// The height of the requested block.
2178        #[serde(skip_serializing_if = "Option::is_none")]
2179        time: Option<i64>,
2180
2181        /// The nonce of the requested block header.
2182        #[serde(with = "opthex")]
2183        #[serde(skip_serializing_if = "Option::is_none")]
2184        nonce: Option<[u8; 32]>,
2185
2186        /// The Equihash solution in the requested block header.
2187        /// Note: presence of this field in getblock is not documented in zcashd.
2188        #[serde(with = "opthex")]
2189        #[serde(skip_serializing_if = "Option::is_none")]
2190        solution: Option<Solution>,
2191
2192        /// The difficulty threshold of the requested block header displayed in compact form.
2193        #[serde(with = "opthex")]
2194        #[serde(skip_serializing_if = "Option::is_none")]
2195        bits: Option<CompactDifficulty>,
2196
2197        /// Floating point number that represents the difficulty limit for this block as a multiple
2198        /// of the minimum difficulty for the network.
2199        #[serde(skip_serializing_if = "Option::is_none")]
2200        difficulty: Option<f64>,
2201
2202        // `chainwork` would be here, but we don't plan on supporting it
2203        // `anchor` would be here. Not planned to be supported.
2204        // `chainSupply` would be here, TODO: implement
2205        // `valuePools` would be here, TODO: implement
2206        //
2207        /// Information about the note commitment trees.
2208        trees: GetBlockTrees,
2209
2210        /// The previous block hash of the requested block header.
2211        #[serde(rename = "previousblockhash", skip_serializing_if = "Option::is_none")]
2212        previous_block_hash: Option<GetBlockHash>,
2213
2214        /// The next block hash after the requested block header.
2215        #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
2216        next_block_hash: Option<GetBlockHash>,
2217    },
2218}
2219
2220impl Default for GetBlock {
2221    fn default() -> Self {
2222        GetBlock::Object {
2223            hash: GetBlockHash::default(),
2224            confirmations: 0,
2225            height: None,
2226            time: None,
2227            tx: Vec::new(),
2228            trees: GetBlockTrees::default(),
2229            size: None,
2230            version: None,
2231            merkle_root: None,
2232            block_commitments: None,
2233            final_sapling_root: None,
2234            final_orchard_root: None,
2235            nonce: None,
2236            bits: None,
2237            difficulty: None,
2238            previous_block_hash: None,
2239            next_block_hash: None,
2240            solution: None,
2241        }
2242    }
2243}
2244
2245#[derive(Clone, Debug, PartialEq, serde::Serialize)]
2246#[serde(untagged)]
2247/// The transaction list in a `getblock` call. Can be a list of transaction
2248/// IDs or the full transaction details depending on verbosity.
2249pub enum GetBlockTransaction {
2250    /// The transaction hash, hex-encoded.
2251    Hash(#[serde(with = "hex")] transaction::Hash),
2252    /// The block object.
2253    Object(Box<TransactionObject>),
2254}
2255
2256/// Response to a `getblockheader` RPC request.
2257///
2258/// See the notes for the [`RpcServer::get_block_header`] method.
2259#[derive(Clone, Debug, PartialEq, serde::Serialize)]
2260#[serde(untagged)]
2261pub enum GetBlockHeader {
2262    /// The request block header, hex-encoded.
2263    Raw(hex_data::HexData),
2264
2265    /// The block header object.
2266    Object(Box<GetBlockHeaderObject>),
2267}
2268
2269#[derive(Clone, Debug, PartialEq, serde::Serialize)]
2270/// Verbose response to a `getblockheader` RPC request.
2271///
2272/// See the notes for the [`RpcServer::get_block_header`] method.
2273pub struct GetBlockHeaderObject {
2274    /// The hash of the requested block.
2275    pub hash: GetBlockHash,
2276
2277    /// The number of confirmations of this block in the best chain,
2278    /// or -1 if it is not in the best chain.
2279    pub confirmations: i64,
2280
2281    /// The height of the requested block.
2282    pub height: Height,
2283
2284    /// The version field of the requested block.
2285    pub version: u32,
2286
2287    /// The merkle root of the requesteed block.
2288    #[serde(with = "hex", rename = "merkleroot")]
2289    pub merkle_root: block::merkle::Root,
2290
2291    /// The blockcommitments field of the requested block. Its interpretation changes
2292    /// depending on the network and height.
2293    #[serde(with = "hex", rename = "blockcommitments")]
2294    pub block_commitments: [u8; 32],
2295
2296    /// The root of the Sapling commitment tree after applying this block.
2297    #[serde(with = "hex", rename = "finalsaplingroot")]
2298    pub final_sapling_root: [u8; 32],
2299
2300    /// The number of Sapling notes in the Sapling note commitment tree
2301    /// after applying this block. Used by the `getblock` RPC method.
2302    #[serde(skip)]
2303    pub sapling_tree_size: u64,
2304
2305    /// The block time of the requested block header in non-leap seconds since Jan 1 1970 GMT.
2306    pub time: i64,
2307
2308    /// The nonce of the requested block header.
2309    #[serde(with = "hex")]
2310    pub nonce: [u8; 32],
2311
2312    /// The Equihash solution in the requested block header.
2313    #[serde(with = "hex")]
2314    pub solution: Solution,
2315
2316    /// The difficulty threshold of the requested block header displayed in compact form.
2317    #[serde(with = "hex")]
2318    pub bits: CompactDifficulty,
2319
2320    /// Floating point number that represents the difficulty limit for this block as a multiple
2321    /// of the minimum difficulty for the network.
2322    pub difficulty: f64,
2323
2324    /// The previous block hash of the requested block header.
2325    #[serde(rename = "previousblockhash")]
2326    pub previous_block_hash: GetBlockHash,
2327
2328    /// The next block hash after the requested block header.
2329    #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
2330    pub next_block_hash: Option<GetBlockHash>,
2331}
2332
2333impl Default for GetBlockHeader {
2334    fn default() -> Self {
2335        GetBlockHeader::Object(Box::default())
2336    }
2337}
2338
2339impl Default for GetBlockHeaderObject {
2340    fn default() -> Self {
2341        let difficulty: ExpandedDifficulty = zebra_chain::work::difficulty::U256::one().into();
2342
2343        GetBlockHeaderObject {
2344            hash: GetBlockHash::default(),
2345            confirmations: 0,
2346            height: Height::MIN,
2347            version: 4,
2348            merkle_root: block::merkle::Root([0; 32]),
2349            block_commitments: Default::default(),
2350            final_sapling_root: Default::default(),
2351            sapling_tree_size: Default::default(),
2352            time: 0,
2353            nonce: [0; 32],
2354            solution: Solution::for_proposal(),
2355            bits: difficulty.to_compact(),
2356            difficulty: 1.0,
2357            previous_block_hash: Default::default(),
2358            next_block_hash: Default::default(),
2359        }
2360    }
2361}
2362
2363/// Response to a `getbestblockhash` and `getblockhash` RPC request.
2364///
2365/// Contains the hex-encoded hash of the requested block.
2366///
2367/// Also see the notes for the [`RpcServer::get_best_block_hash`] and `get_block_hash` methods.
2368#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
2369#[serde(transparent)]
2370pub struct GetBlockHash(#[serde(with = "hex")] pub block::Hash);
2371
2372/// Response to a `getbestblockheightandhash` RPC request.
2373#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
2374pub struct GetBlockHeightAndHash {
2375    /// The best chain tip block height
2376    pub height: block::Height,
2377    /// The best chain tip block hash
2378    pub hash: block::Hash,
2379}
2380
2381impl Default for GetBlockHeightAndHash {
2382    fn default() -> Self {
2383        Self {
2384            height: block::Height::MIN,
2385            hash: block::Hash([0; 32]),
2386        }
2387    }
2388}
2389
2390impl Default for GetBlockHash {
2391    fn default() -> Self {
2392        GetBlockHash(block::Hash([0; 32]))
2393    }
2394}
2395
2396/// Response to a `getrawtransaction` RPC request.
2397///
2398/// See the notes for the [`Rpc::get_raw_transaction` method].
2399#[derive(Clone, Debug, PartialEq, serde::Serialize)]
2400#[serde(untagged)]
2401pub enum GetRawTransaction {
2402    /// The raw transaction, encoded as hex bytes.
2403    Raw(#[serde(with = "hex")] SerializedTransaction),
2404    /// The transaction object.
2405    Object(Box<TransactionObject>),
2406}
2407
2408impl Default for GetRawTransaction {
2409    fn default() -> Self {
2410        Self::Object(Box::default())
2411    }
2412}
2413
2414/// Response to a `getaddressutxos` RPC request.
2415///
2416/// See the notes for the [`Rpc::get_address_utxos` method].
2417#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
2418pub struct GetAddressUtxos {
2419    /// The transparent address, base58check encoded
2420    address: transparent::Address,
2421
2422    /// The output txid, in big-endian order, hex-encoded
2423    #[serde(with = "hex")]
2424    txid: transaction::Hash,
2425
2426    /// The transparent output index, numeric
2427    #[serde(rename = "outputIndex")]
2428    output_index: OutputIndex,
2429
2430    /// The transparent output script, hex encoded
2431    #[serde(with = "hex")]
2432    script: transparent::Script,
2433
2434    /// The amount of zatoshis in the transparent output
2435    satoshis: u64,
2436
2437    /// The block height, numeric.
2438    ///
2439    /// We put this field last, to match the zcashd order.
2440    height: Height,
2441}
2442
2443impl Default for GetAddressUtxos {
2444    fn default() -> Self {
2445        Self {
2446            address: transparent::Address::from_pub_key_hash(
2447                zebra_chain::parameters::NetworkKind::default(),
2448                [0u8; 20],
2449            ),
2450            txid: transaction::Hash::from([0; 32]),
2451            output_index: OutputIndex::from_u64(0),
2452            script: transparent::Script::new(&[0u8; 10]),
2453            satoshis: u64::default(),
2454            height: Height(0),
2455        }
2456    }
2457}
2458
2459impl GetAddressUtxos {
2460    /// Constructs a new instance of [`GetAddressUtxos`].
2461    pub fn from_parts(
2462        address: transparent::Address,
2463        txid: transaction::Hash,
2464        output_index: OutputIndex,
2465        script: transparent::Script,
2466        satoshis: u64,
2467        height: Height,
2468    ) -> Self {
2469        GetAddressUtxos {
2470            address,
2471            txid,
2472            output_index,
2473            script,
2474            satoshis,
2475            height,
2476        }
2477    }
2478
2479    /// Returns the contents of [`GetAddressUtxos`].
2480    pub fn into_parts(
2481        &self,
2482    ) -> (
2483        transparent::Address,
2484        transaction::Hash,
2485        OutputIndex,
2486        transparent::Script,
2487        u64,
2488        Height,
2489    ) {
2490        (
2491            self.address.clone(),
2492            self.txid,
2493            self.output_index,
2494            self.script.clone(),
2495            self.satoshis,
2496            self.height,
2497        )
2498    }
2499}
2500
2501/// A struct to use as parameter of the `getaddresstxids`.
2502///
2503/// See the notes for the [`Rpc::get_address_tx_ids` method].
2504#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
2505pub struct GetAddressTxIdsRequest {
2506    // A list of addresses to get transactions from.
2507    addresses: Vec<String>,
2508    // The height to start looking for transactions.
2509    start: Option<u32>,
2510    // The height to end looking for transactions.
2511    end: Option<u32>,
2512}
2513
2514impl GetAddressTxIdsRequest {
2515    /// Constructs [`GetAddressTxIdsRequest`] from its constituent parts.
2516    pub fn from_parts(addresses: Vec<String>, start: u32, end: u32) -> Self {
2517        GetAddressTxIdsRequest {
2518            addresses,
2519            start: Some(start),
2520            end: Some(end),
2521        }
2522    }
2523    /// Returns the contents of [`GetAddressTxIdsRequest`].
2524    pub fn into_parts(&self) -> (Vec<String>, u32, u32) {
2525        (
2526            self.addresses.clone(),
2527            self.start.unwrap_or(0),
2528            self.end.unwrap_or(0),
2529        )
2530    }
2531}
2532
2533/// Information about the sapling and orchard note commitment trees if any.
2534#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
2535pub struct GetBlockTrees {
2536    #[serde(skip_serializing_if = "SaplingTrees::is_empty")]
2537    sapling: SaplingTrees,
2538    #[serde(skip_serializing_if = "OrchardTrees::is_empty")]
2539    orchard: OrchardTrees,
2540}
2541
2542impl Default for GetBlockTrees {
2543    fn default() -> Self {
2544        GetBlockTrees {
2545            sapling: SaplingTrees { size: 0 },
2546            orchard: OrchardTrees { size: 0 },
2547        }
2548    }
2549}
2550
2551impl GetBlockTrees {
2552    /// Constructs a new instance of ['GetBlockTrees'].
2553    pub fn new(sapling: u64, orchard: u64) -> Self {
2554        GetBlockTrees {
2555            sapling: SaplingTrees { size: sapling },
2556            orchard: OrchardTrees { size: orchard },
2557        }
2558    }
2559
2560    /// Returns sapling data held by ['GetBlockTrees'].
2561    pub fn sapling(self) -> u64 {
2562        self.sapling.size
2563    }
2564
2565    /// Returns orchard data held by ['GetBlockTrees'].
2566    pub fn orchard(self) -> u64 {
2567        self.orchard.size
2568    }
2569}
2570
2571/// Sapling note commitment tree information.
2572#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
2573pub struct SaplingTrees {
2574    size: u64,
2575}
2576
2577impl SaplingTrees {
2578    fn is_empty(&self) -> bool {
2579        self.size == 0
2580    }
2581}
2582
2583/// Orchard note commitment tree information.
2584#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
2585pub struct OrchardTrees {
2586    size: u64,
2587}
2588
2589impl OrchardTrees {
2590    fn is_empty(&self) -> bool {
2591        self.size == 0
2592    }
2593}
2594
2595/// Build a valid height range from the given optional start and end numbers.
2596///
2597/// # Parameters
2598///
2599/// - `start`: Optional starting height. If not provided, defaults to 0.
2600/// - `end`: Optional ending height. A value of 0 or absence of a value indicates to use `chain_height`.
2601/// - `chain_height`: The maximum permissible height.
2602///
2603/// # Returns
2604///
2605/// A `RangeInclusive<Height>` from the clamped start to the clamped end.
2606///
2607/// # Errors
2608///
2609/// Returns an error if the computed start is greater than the computed end.
2610fn build_height_range(
2611    start: Option<u32>,
2612    end: Option<u32>,
2613    chain_height: Height,
2614) -> Result<RangeInclusive<Height>> {
2615    // Convert optional values to Height, using 0 (as Height(0)) when missing.
2616    // If start is above chain_height, clamp it to chain_height.
2617    let start = Height(start.unwrap_or(0)).min(chain_height);
2618
2619    // For `end`, treat a zero value or missing value as `chain_height`:
2620    let end = match end {
2621        Some(0) | None => chain_height,
2622        Some(val) => Height(val).min(chain_height),
2623    };
2624
2625    if start > end {
2626        return Err(ErrorObject::owned(
2627            ErrorCode::InvalidParams.code(),
2628            format!("start {start:?} must be less than or equal to end {end:?}"),
2629            None::<()>,
2630        ));
2631    }
2632
2633    Ok(start..=end)
2634}
2635
2636/// Given a potentially negative index, find the corresponding `Height`.
2637///
2638/// This function is used to parse the integer index argument of `get_block_hash`.
2639/// This is based on zcashd's implementation:
2640/// <https://github.com/zcash/zcash/blob/c267c3ee26510a974554f227d40a89e3ceb5bb4d/src/rpc/blockchain.cpp#L589-L618>
2641//
2642// TODO: also use this function in `get_block` and `z_get_treestate`
2643#[allow(dead_code)]
2644pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height> {
2645    if index >= 0 {
2646        let height = index.try_into().expect("Positive i32 always fits in u32");
2647        if height > tip_height.0 {
2648            return Err(ErrorObject::borrowed(
2649                ErrorCode::InvalidParams.code(),
2650                "Provided index is greater than the current tip",
2651                None,
2652            ));
2653        }
2654        Ok(Height(height))
2655    } else {
2656        // `index + 1` can't overflow, because `index` is always negative here.
2657        let height = i32::try_from(tip_height.0)
2658            .expect("tip height fits in i32, because Height::MAX fits in i32")
2659            .checked_add(index + 1);
2660
2661        let sanitized_height = match height {
2662            None => {
2663                return Err(ErrorObject::borrowed(
2664                    ErrorCode::InvalidParams.code(),
2665                    "Provided index is not valid",
2666                    None,
2667                ))
2668            }
2669            Some(h) => {
2670                if h < 0 {
2671                    return Err(ErrorObject::borrowed(
2672                        ErrorCode::InvalidParams.code(),
2673                        "Provided negative index ends up with a negative height",
2674                        None,
2675                    ));
2676                }
2677                let h: u32 = h.try_into().expect("Positive i32 always fits in u32");
2678                if h > tip_height.0 {
2679                    return Err(ErrorObject::borrowed(
2680                        ErrorCode::InvalidParams.code(),
2681                        "Provided index is greater than the current tip",
2682                        None,
2683                    ));
2684                }
2685
2686                h
2687            }
2688        };
2689
2690        Ok(Height(sanitized_height))
2691    }
2692}
2693
2694/// A helper module to serialize `Option<T: ToHex>` as a hex string.
2695mod opthex {
2696    use hex::ToHex;
2697    use serde::Serializer;
2698
2699    pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
2700    where
2701        S: Serializer,
2702        T: ToHex,
2703    {
2704        match data {
2705            Some(data) => {
2706                let s = data.encode_hex::<String>();
2707                serializer.serialize_str(&s)
2708            }
2709            None => serializer.serialize_none(),
2710        }
2711    }
2712}
2713/// Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
2714pub async fn chain_tip_difficulty<State>(
2715    network: Network,
2716    mut state: State,
2717    should_use_default: bool,
2718) -> Result<f64>
2719where
2720    State: Service<
2721            zebra_state::ReadRequest,
2722            Response = zebra_state::ReadResponse,
2723            Error = zebra_state::BoxError,
2724        > + Clone
2725        + Send
2726        + Sync
2727        + 'static,
2728    State::Future: Send,
2729{
2730    let request = ReadRequest::ChainInfo;
2731
2732    // # TODO
2733    // - add a separate request like BestChainNextMedianTimePast, but skipping the
2734    //   consistency check, because any block's difficulty is ok for display
2735    // - return 1.0 for a "not enough blocks in the state" error, like `zcashd`:
2736    // <https://github.com/zcash/zcash/blob/7b28054e8b46eb46a9589d0bdc8e29f9fa1dc82d/src/rpc/blockchain.cpp#L40-L41>
2737    let response = state
2738        .ready()
2739        .and_then(|service| service.call(request))
2740        .await;
2741
2742    let response = match (should_use_default, response) {
2743        (_, Ok(res)) => res,
2744        (true, Err(_)) => {
2745            return Ok((U256::from(network.target_difficulty_limit()) >> 128).as_u128() as f64)
2746        }
2747        (false, Err(error)) => return Err(ErrorObject::owned(0, error.to_string(), None::<()>)),
2748    };
2749
2750    let chain_info = match response {
2751        ReadResponse::ChainInfo(info) => info,
2752        _ => unreachable!("unmatched response to a chain info request"),
2753    };
2754
2755    // This RPC is typically used for display purposes, so it is not consensus-critical.
2756    // But it uses the difficulty consensus rules for its calculations.
2757    //
2758    // Consensus:
2759    // https://zips.z.cash/protocol/protocol.pdf#nbits
2760    //
2761    // The zcashd implementation performs to_expanded() on f64,
2762    // and then does an inverse division:
2763    // https://github.com/zcash/zcash/blob/d6e2fada844373a8554ee085418e68de4b593a6c/src/rpc/blockchain.cpp#L46-L73
2764    //
2765    // But in Zebra we divide the high 128 bits of each expanded difficulty. This gives
2766    // a similar result, because the lower 128 bits are insignificant after conversion
2767    // to `f64` with a 53-bit mantissa.
2768    //
2769    // `pow_limit >> 128 / difficulty >> 128` is the same as the work calculation
2770    // `(2^256 / pow_limit) / (2^256 / difficulty)`, but it's a bit more accurate.
2771    //
2772    // To simplify the calculation, we don't scale for leading zeroes. (Bitcoin's
2773    // difficulty currently uses 68 bits, so even it would still have full precision
2774    // using this calculation.)
2775
2776    // Get expanded difficulties (256 bits), these are the inverse of the work
2777    let pow_limit: U256 = network.target_difficulty_limit().into();
2778    let Some(difficulty) = chain_info.expected_difficulty.to_expanded() else {
2779        return Ok(0.0);
2780    };
2781
2782    // Shift out the lower 128 bits (256 bits, but the top 128 are all zeroes)
2783    let pow_limit = pow_limit >> 128;
2784    let difficulty = U256::from(difficulty) >> 128;
2785
2786    // Convert to u128 then f64.
2787    // We could also convert U256 to String, then parse as f64, but that's slower.
2788    let pow_limit = pow_limit.as_u128() as f64;
2789    let difficulty = difficulty.as_u128() as f64;
2790
2791    // Invert the division to give approximately: `work(difficulty) / work(pow_limit)`
2792    Ok(pow_limit / difficulty)
2793}