2022-02-22 03:26:29 -08:00
//! Zebra supported RPC methods.
//!
//! Based on the [`zcashd` RPC methods](https://zcash.github.io/rpc/)
//! as used by `lightwalletd.`
//!
//! Some parts of the `zcashd` RPC documentation are outdated.
2022-03-25 05:25:31 -07:00
//! So this implementation follows the `zcashd` server and `lightwalletd` client implementations.
2022-02-22 03:26:29 -08:00
2024-03-14 08:04:19 -07:00
use std ::{ collections ::HashSet , default ::Default , fmt ::Debug , sync ::Arc } ;
2022-03-24 02:45:37 -07:00
2022-03-25 05:25:31 -07:00
use chrono ::Utc ;
2022-03-09 17:12:41 -08:00
use futures ::{ FutureExt , TryFutureExt } ;
2022-03-12 08:51:49 -08:00
use hex ::{ FromHex , ToHex } ;
2022-03-25 05:25:31 -07:00
use indexmap ::IndexMap ;
2022-03-03 23:00:24 -08:00
use jsonrpc_core ::{ self , BoxFuture , Error , ErrorCode , Result } ;
2022-02-22 03:26:29 -08:00
use jsonrpc_derive ::rpc ;
2022-11-10 06:51:53 -08:00
use tokio ::{ sync ::broadcast , task ::JoinHandle } ;
2024-01-11 06:41:01 -08:00
use tower ::{ Service , ServiceExt } ;
2022-04-11 22:06:29 -07:00
use tracing ::Instrument ;
2022-02-22 03:26:29 -08:00
2022-03-08 01:14:21 -08:00
use zebra_chain ::{
2022-03-25 05:25:31 -07:00
block ::{ self , Height , SerializedBlock } ,
2022-03-15 15:29:15 -07:00
chain_tip ::ChainTip ,
2022-05-12 00:00:12 -07:00
orchard ,
2022-03-25 05:25:31 -07:00
parameters ::{ ConsensusBranchId , Network , NetworkUpgrade } ,
2022-05-12 00:00:12 -07:00
sapling ,
2022-03-09 17:12:41 -08:00
serialization ::{ SerializationError , ZcashDeserialize } ,
2023-09-04 14:05:39 -07:00
subtree ::NoteCommitmentSubtreeIndex ,
2022-04-11 22:06:29 -07:00
transaction ::{ self , SerializedTransaction , Transaction , UnminedTx } ,
2022-05-02 21:10:21 -07:00
transparent ::{ self , Address } ,
2022-03-08 01:14:21 -08:00
} ;
2022-10-28 11:34:52 -07:00
use zebra_node_services ::mempool ;
2023-03-26 16:53:44 -07:00
use zebra_state ::{ HashOrHeight , MinedTx , OutputIndex , OutputLocation , TransactionLocation } ;
2022-02-28 19:32:32 -08:00
2023-09-04 14:05:39 -07:00
use crate ::{
constants ::{ INVALID_PARAMETERS_ERROR_CODE , MISSING_BLOCK_ERROR_CODE } ,
methods ::trees ::{ GetSubtrees , SubtreeRpcData } ,
queue ::Queue ,
} ;
// We don't use a types/ module here, because it is redundant.
pub mod trees ;
2022-04-11 22:06:29 -07:00
2022-10-19 05:01:13 -07:00
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
2022-10-31 17:43:45 -07:00
pub mod get_block_template_rpcs ;
2022-10-19 05:01:13 -07:00
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
2022-10-25 19:52:29 -07:00
pub use get_block_template_rpcs ::{ GetBlockTemplateRpc , GetBlockTemplateRpcImpl } ;
2022-10-19 05:01:13 -07:00
2022-02-28 19:32:32 -08:00
#[ cfg(test) ]
mod tests ;
2022-02-22 03:26:29 -08:00
#[ rpc(server) ]
/// RPC method signatures.
pub trait Rpc {
2024-03-14 08:04:19 -07:00
#[ rpc(name = " getinfo " ) ]
2022-03-15 02:13:24 -07:00
/// Returns software information from the RPC server, as a [`GetInfo`] JSON struct.
2022-02-22 03:26:29 -08:00
///
2022-03-15 02:13:24 -07:00
/// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: control
2022-02-28 19:32:32 -08:00
///
2022-03-15 02:13:24 -07:00
/// # Notes
2022-02-28 19:32:32 -08:00
///
2022-03-15 02:13:24 -07:00
/// [The zcashd reference](https://zcash.github.io/rpc/getinfo.html) might not show some fields
/// in Zebra's [`GetInfo`]. Zebra uses the field names and formats from the
/// [zcashd code](https://github.com/zcash/zcash/blob/v4.6.0-1/src/rpc/misc.cpp#L86-L87).
2022-02-28 19:32:32 -08:00
///
2022-03-15 02:13:24 -07:00
/// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95)
2022-02-22 03:26:29 -08:00
fn get_info ( & self ) -> Result < GetInfo > ;
2022-03-15 02:13:24 -07:00
/// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct.
///
/// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: blockchain
2022-02-22 03:26:29 -08:00
///
2022-03-25 05:25:31 -07:00
/// # Notes
///
/// Some fields from the zcashd reference are missing from Zebra's [`GetBlockChainInfo`]. It only contains the fields
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89)
2022-02-22 03:26:29 -08:00
#[ rpc(name = " getblockchaininfo " ) ]
fn get_blockchain_info ( & self ) -> Result < GetBlockChainInfo > ;
2022-03-03 23:00:24 -08:00
2022-04-20 11:27:00 -07:00
/// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance.
///
/// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: address
2022-04-20 11:27:00 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry
/// - `addresses`: (array of strings) A list of base-58 encoded addresses.
2022-04-20 11:27:00 -07:00
///
/// # Notes
///
/// zcashd also accepts a single string parameter instead of an array of strings, but Zebra
/// doesn't because lightwalletd always calls this RPC with an array of addresses.
///
/// zcashd also returns the total amount of Zatoshis received by the addresses, but Zebra
/// doesn't because lightwalletd doesn't use that information.
///
/// The RPC documentation says that the returned object has a string `balance` field, but
/// zcashd actually [returns an
/// integer](https://github.com/zcash/lightwalletd/blob/bdaac63f3ee0dbef62bde04f6817a9f90d483b00/common/common.go#L128-L130).
#[ rpc(name = " getaddressbalance " ) ]
fn get_address_balance (
& self ,
address_strings : AddressStrings ,
) -> BoxFuture < Result < AddressBalance > > ;
2022-03-15 02:13:24 -07:00
/// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid.
/// Returns the [`SentTransactionHash`] for the transaction, as a JSON string.
2022-03-03 23:00:24 -08:00
///
2022-03-15 02:13:24 -07:00
/// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: transaction
2022-03-03 23:00:24 -08:00
///
2022-03-15 02:13:24 -07:00
/// # Parameters
2022-03-03 23:00:24 -08:00
///
2024-03-14 08:04:19 -07:00
/// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes.
2022-03-03 23:00:24 -08:00
///
2022-03-15 02:13:24 -07:00
/// # Notes
///
/// zcashd accepts an optional `allowhighfees` parameter. Zebra doesn't support this parameter,
/// because lightwalletd doesn't use it.
2022-03-03 23:00:24 -08:00
#[ rpc(name = " sendrawtransaction " ) ]
fn send_raw_transaction (
& self ,
raw_transaction_hex : String ,
) -> BoxFuture < Result < SentTransactionHash > > ;
2022-03-09 17:12:41 -08:00
2023-02-06 17:25:34 -08:00
/// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
2022-03-30 16:34:52 -07:00
/// If the block is not in Zebra's state, returns
/// [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
2022-03-15 02:13:24 -07:00
///
/// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: blockchain
2022-03-09 17:12:41 -08:00
///
2022-03-15 02:13:24 -07:00
/// # Parameters
2022-03-09 17:12:41 -08:00
///
2024-03-14 08:04:19 -07:00
/// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned.
/// - `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.
2022-03-09 17:12:41 -08:00
///
2022-03-15 02:13:24 -07:00
/// # Notes
2022-03-09 17:12:41 -08:00
///
2022-05-27 02:41:11 -07:00
/// With verbosity=1, [`lightwalletd` only reads the `tx` field of the
/// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152),
2023-02-06 17:25:34 -08:00
/// and other clients only read the `hash` and `confirmations` fields,
/// so we only return a few fields for now.
2022-03-09 17:12:41 -08:00
///
2023-02-06 17:25:34 -08:00
/// `lightwalletd` and mining clients also do not use verbosity=2, so we don't support it.
2022-03-09 17:12:41 -08:00
#[ rpc(name = " getblock " ) ]
2023-02-06 17:25:34 -08:00
fn get_block (
& self ,
hash_or_height : String ,
verbosity : Option < u8 > ,
) -> BoxFuture < Result < GetBlock > > ;
2022-03-10 21:13:08 -08:00
2022-10-20 23:01:29 -07:00
/// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
2022-03-10 21:13:08 -08:00
///
2022-03-15 02:13:24 -07:00
/// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: blockchain
2022-03-10 21:13:08 -08:00
#[ rpc(name = " getbestblockhash " ) ]
2022-10-20 23:01:29 -07:00
fn get_best_block_hash ( & self ) -> Result < GetBlockHash > ;
2022-03-12 08:51:49 -08:00
/// Returns all transaction ids in the memory pool, as a JSON array.
///
2022-03-15 02:13:24 -07:00
/// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: blockchain
2022-03-12 08:51:49 -08:00
#[ rpc(name = " getrawmempool " ) ]
fn get_raw_mempool ( & self ) -> BoxFuture < Result < Vec < String > > > ;
2022-03-24 02:45:37 -07:00
2022-05-12 00:00:12 -07:00
/// Returns information about the given block's Sapling & Orchard tree state.
///
/// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: blockchain
2022-05-12 00:00:12 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height.
2022-05-12 00:00:12 -07:00
///
/// # Notes
///
/// The zcashd doc reference above says that the parameter "`height` can be
/// negative where -1 is the last known valid block". On the other hand,
/// `lightwalletd` only uses positive heights, so Zebra does not support
/// negative heights.
#[ rpc(name = " z_gettreestate " ) ]
fn z_get_treestate ( & self , hash_or_height : String ) -> BoxFuture < Result < GetTreestate > > ;
2023-09-04 14:05:39 -07:00
/// Returns information about a range of Sapling or Orchard subtrees.
///
2024-03-14 08:04:19 -07:00
/// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link
/// method: post
/// tags: blockchain
2023-09-04 14:05:39 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard".
/// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return.
/// - `limit`: (number, optional) The maximum number of subtree values to return.
2023-09-04 14:05:39 -07:00
///
/// # Notes
///
/// While Zebra is doing its initial subtree index rebuild, subtrees will become available
/// starting at the chain tip. This RPC will return an empty list if the `start_index` subtree
/// exists, but has not been rebuilt yet. This matches `zcashd`'s behaviour when subtrees aren't
/// available yet. (But `zcashd` does its rebuild before syncing any blocks.)
#[ rpc(name = " z_getsubtreesbyindex " ) ]
fn z_get_subtrees_by_index (
& self ,
pool : String ,
start_index : NoteCommitmentSubtreeIndex ,
limit : Option < NoteCommitmentSubtreeIndex > ,
) -> BoxFuture < Result < GetSubtrees > > ;
2022-03-24 02:45:37 -07:00
/// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure.
///
/// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: transaction
2022-03-24 02:45:37 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned.
/// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object.
2022-03-24 02:45:37 -07:00
///
/// # Notes
///
/// We don't currently support the `blockhash` parameter since lightwalletd does not
/// use it.
///
/// In verbose mode, we only expose the `hex` and `height` fields since
/// lightwalletd uses only those:
/// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L119>
#[ rpc(name = " getrawtransaction " ) ]
fn get_raw_transaction (
& self ,
txid_hex : String ,
2023-12-10 13:44:43 -08:00
verbose : Option < u8 > ,
2022-03-24 02:45:37 -07:00
) -> BoxFuture < Result < GetRawTransaction > > ;
2022-04-13 01:48:13 -07:00
/// Returns the transaction ids made by the provided transparent addresses.
///
/// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: address
2022-04-13 01:48:13 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields:
/// - `addresses`: (json array of string, required) The addresses to get transactions from.
/// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive).
/// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive).
2022-04-13 01:48:13 -07:00
///
/// # Notes
///
/// Only the multi-argument format is used by lightwalletd and this is what we currently support:
2022-05-30 13:12:11 -07:00
/// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L97-L102>
2022-04-13 01:48:13 -07:00
#[ rpc(name = " getaddresstxids " ) ]
2022-05-04 18:08:27 -07:00
fn get_address_tx_ids ( & self , request : GetAddressTxIdsRequest )
-> BoxFuture < Result < Vec < String > > > ;
2022-04-24 20:00:52 -07:00
/// Returns all unspent outputs for a list of addresses.
///
/// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html)
2024-03-14 08:04:19 -07:00
/// method: post
/// tags: address
2022-04-24 20:00:52 -07:00
///
/// # Parameters
///
2024-03-14 08:04:19 -07:00
/// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from.
2022-04-24 20:00:52 -07:00
///
/// # Notes
///
/// lightwalletd always uses the multi-address request, without chaininfo:
2022-05-30 13:12:11 -07:00
/// <https://github.com/zcash/lightwalletd/blob/master/frontend/service.go#L402>
2022-04-24 20:00:52 -07:00
#[ rpc(name = " getaddressutxos " ) ]
fn get_address_utxos (
& self ,
address_strings : AddressStrings ,
) -> BoxFuture < Result < Vec < GetAddressUtxos > > > ;
2022-02-22 03:26:29 -08:00
}
/// RPC method implementations.
2024-01-11 06:41:01 -08:00
#[ derive(Clone) ]
2022-03-15 15:29:15 -07:00
pub struct RpcImpl < Mempool , State , Tip >
2022-03-03 23:00:24 -08:00
where
2022-10-28 11:34:52 -07:00
Mempool : Service <
2024-01-11 06:41:01 -08:00
mempool ::Request ,
Response = mempool ::Response ,
Error = zebra_node_services ::BoxError ,
> + Clone
+ Send
+ Sync
+ 'static ,
Mempool ::Future : Send ,
2022-03-09 17:12:41 -08:00
State : Service <
2024-01-11 06:41:01 -08:00
zebra_state ::ReadRequest ,
Response = zebra_state ::ReadResponse ,
Error = zebra_state ::BoxError ,
> + Clone
+ Send
+ Sync
+ 'static ,
State ::Future : Send ,
Tip : ChainTip + Clone + Send + Sync + 'static ,
2022-03-03 23:00:24 -08:00
{
2022-09-06 06:32:33 -07:00
// Configuration
//
2023-06-19 19:42:06 -07:00
/// Zebra's application version, with build metadata.
build_version : String ,
/// Zebra's RPC user agent.
user_agent : String ,
2022-03-15 15:29:15 -07:00
2022-09-06 06:32:33 -07:00
/// The configured network for this RPC service.
network : Network ,
/// Test-only option that makes Zebra say it is at the chain tip,
/// no matter what the estimated height or local clock is.
debug_force_finished_sync : bool ,
2023-02-22 16:10:11 -08:00
/// Test-only option that makes RPC responses more like `zcashd`.
#[ allow(dead_code) ]
debug_like_zcashd : bool ,
2022-09-06 06:32:33 -07:00
// Services
//
2022-03-03 23:00:24 -08:00
/// A handle to the mempool service.
2024-01-11 06:41:01 -08:00
mempool : Mempool ,
2022-03-15 15:29:15 -07:00
2022-03-09 17:12:41 -08:00
/// A handle to the state service.
2022-03-11 05:58:22 -08:00
state : State ,
2022-03-15 15:29:15 -07:00
/// Allows efficient access to the best tip of the blockchain.
latest_chain_tip : Tip ,
2022-09-06 06:32:33 -07:00
// Tasks
//
2022-11-10 06:51:53 -08:00
/// A sender component of a channel used to send transactions to the mempool queue.
queue_sender : broadcast ::Sender < UnminedTx > ,
2022-02-28 19:32:32 -08:00
}
2022-03-03 23:00:24 -08:00
2024-01-11 06:41:01 -08:00
impl < Mempool , State , Tip > Debug for RpcImpl < Mempool , State , Tip >
where
Mempool : Service <
mempool ::Request ,
Response = mempool ::Response ,
Error = zebra_node_services ::BoxError ,
> + Clone
+ Send
+ Sync
+ 'static ,
Mempool ::Future : Send ,
State : Service <
zebra_state ::ReadRequest ,
Response = zebra_state ::ReadResponse ,
Error = zebra_state ::BoxError ,
> + Clone
+ Send
+ Sync
+ 'static ,
State ::Future : Send ,
Tip : ChainTip + Clone + Send + Sync + 'static ,
{
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
// Skip fields without Debug impls, and skip channels
f . debug_struct ( " RpcImpl " )
. field ( " build_version " , & self . build_version )
. field ( " user_agent " , & self . user_agent )
. field ( " network " , & self . network )
. field ( " debug_force_finished_sync " , & self . debug_force_finished_sync )
. field ( " debug_like_zcashd " , & self . debug_like_zcashd )
. finish ( )
}
}
2022-03-15 15:29:15 -07:00
impl < Mempool , State , Tip > RpcImpl < Mempool , State , Tip >
2022-03-03 23:00:24 -08:00
where
2022-10-28 11:34:52 -07:00
Mempool : Service <
mempool ::Request ,
Response = mempool ::Response ,
Error = zebra_node_services ::BoxError ,
2024-01-11 06:41:01 -08:00
> + Clone
+ Send
+ Sync
+ 'static ,
Mempool ::Future : Send ,
2022-03-09 17:12:41 -08:00
State : Service <
2022-04-11 22:06:29 -07:00
zebra_state ::ReadRequest ,
Response = zebra_state ::ReadResponse ,
Error = zebra_state ::BoxError ,
> + Clone
+ Send
+ Sync
+ 'static ,
2024-01-11 06:41:01 -08:00
State ::Future : Send ,
2022-04-11 22:06:29 -07:00
Tip : ChainTip + Clone + Send + Sync + 'static ,
2022-03-03 23:00:24 -08:00
{
/// Create a new instance of the RPC handler.
2023-06-19 19:42:06 -07:00
//
// TODO:
// - put some of the configs or services in their own struct?
#[ allow(clippy::too_many_arguments) ]
pub fn new < VersionString , UserAgentString > (
build_version : VersionString ,
user_agent : UserAgentString ,
2022-09-06 06:32:33 -07:00
network : Network ,
debug_force_finished_sync : bool ,
2023-02-22 16:10:11 -08:00
debug_like_zcashd : bool ,
2024-01-11 06:41:01 -08:00
mempool : Mempool ,
2022-03-11 05:58:22 -08:00
state : State ,
2022-03-15 15:29:15 -07:00
latest_chain_tip : Tip ,
2022-04-11 22:06:29 -07:00
) -> ( Self , JoinHandle < ( ) > )
2022-03-15 15:29:15 -07:00
where
2023-06-19 19:42:06 -07:00
VersionString : ToString + Clone + Send + 'static ,
UserAgentString : ToString + Clone + Send + 'static ,
2022-03-15 15:29:15 -07:00
{
2022-11-10 06:51:53 -08:00
let ( runner , queue_sender ) = Queue ::start ( ) ;
2022-04-11 22:06:29 -07:00
2023-06-19 19:42:06 -07:00
let mut build_version = build_version . to_string ( ) ;
let user_agent = user_agent . to_string ( ) ;
2022-04-28 03:19:02 -07:00
// Match zcashd's version format, if the version string has anything in it
2023-06-19 19:42:06 -07:00
if ! build_version . is_empty ( ) & & ! build_version . starts_with ( 'v' ) {
build_version . insert ( 0 , 'v' ) ;
2022-04-28 03:19:02 -07:00
}
2022-04-11 22:06:29 -07:00
let rpc_impl = RpcImpl {
2023-06-19 19:42:06 -07:00
build_version ,
user_agent ,
2022-09-06 06:32:33 -07:00
network ,
debug_force_finished_sync ,
2023-02-22 16:10:11 -08:00
debug_like_zcashd ,
2022-04-11 22:06:29 -07:00
mempool : mempool . clone ( ) ,
state : state . clone ( ) ,
latest_chain_tip : latest_chain_tip . clone ( ) ,
2022-11-10 06:51:53 -08:00
queue_sender ,
2022-04-11 22:06:29 -07:00
} ;
// run the process queue
let rpc_tx_queue_task_handle = tokio ::spawn (
runner
. run ( mempool , state , latest_chain_tip , network )
. in_current_span ( ) ,
) ;
( rpc_impl , rpc_tx_queue_task_handle )
2022-03-03 23:00:24 -08:00
}
}
2022-03-15 15:29:15 -07:00
impl < Mempool , State , Tip > Rpc for RpcImpl < Mempool , State , Tip >
2022-03-03 23:00:24 -08:00
where
2024-01-11 06:41:01 -08:00
Mempool : Service <
2022-10-28 11:34:52 -07:00
mempool ::Request ,
Response = mempool ::Response ,
Error = zebra_node_services ::BoxError ,
2024-01-11 06:41:01 -08:00
> + Clone
+ Send
+ Sync
+ 'static ,
2022-03-03 23:00:24 -08:00
Mempool ::Future : Send ,
2022-03-09 17:12:41 -08:00
State : Service <
2022-03-17 15:59:46 -07:00
zebra_state ::ReadRequest ,
Response = zebra_state ::ReadResponse ,
2022-03-09 17:12:41 -08:00
Error = zebra_state ::BoxError ,
2022-03-11 05:58:22 -08:00
> + Clone
+ Send
+ Sync
+ 'static ,
2022-03-09 17:12:41 -08:00
State ::Future : Send ,
2022-11-04 11:19:48 -07:00
Tip : ChainTip + Clone + Send + Sync + 'static ,
2022-03-03 23:00:24 -08:00
{
2022-02-22 03:26:29 -08:00
fn get_info ( & self ) -> Result < GetInfo > {
let response = GetInfo {
2023-06-19 19:42:06 -07:00
build : self . build_version . clone ( ) ,
subversion : self . user_agent . clone ( ) ,
2022-02-22 03:26:29 -08:00
} ;
Ok ( response )
}
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2022-02-22 03:26:29 -08:00
fn get_blockchain_info ( & self ) -> Result < GetBlockChainInfo > {
2022-03-25 05:25:31 -07:00
let network = self . network ;
// `chain` field
let chain = self . network . bip70_network_name ( ) ;
// `blocks` and `best_block_hash` fields
let ( tip_height , tip_hash ) = self
. latest_chain_tip
. best_tip_height_and_hash ( )
. ok_or_else ( | | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " No Chain tip available yet " . to_string ( ) ,
data : None ,
} ) ? ;
// `estimated_height` field
let current_block_time =
self . latest_chain_tip
. best_tip_block_time ( )
. ok_or_else ( | | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " No Chain tip available yet " . to_string ( ) ,
data : None ,
} ) ? ;
let zebra_estimated_height = self
. latest_chain_tip
. estimate_network_chain_tip_height ( network , Utc ::now ( ) )
. ok_or_else ( | | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " No Chain tip available yet " . to_string ( ) ,
data : None ,
} ) ? ;
2022-09-06 06:32:33 -07:00
let mut estimated_height =
2022-03-25 05:25:31 -07:00
if current_block_time > Utc ::now ( ) | | zebra_estimated_height < tip_height {
tip_height
} else {
zebra_estimated_height
} ;
2022-09-06 06:32:33 -07:00
// If we're testing the mempool, force the estimated height to be the actual tip height.
if self . debug_force_finished_sync {
estimated_height = tip_height ;
}
2022-03-25 05:25:31 -07:00
// `upgrades` object
//
// Get the network upgrades in height order, like `zcashd`.
let mut upgrades = IndexMap ::new ( ) ;
2024-03-12 14:41:44 -07:00
for ( activation_height , network_upgrade ) in network . activation_list ( ) {
2022-03-25 05:25:31 -07:00
// Zebra defines network upgrades based on incompatible consensus rule changes,
// but zcashd defines them based on ZIPs.
//
// All the network upgrades with a consensus branch ID are the same in Zebra and zcashd.
if let Some ( branch_id ) = network_upgrade . branch_id ( ) {
// zcashd's RPC seems to ignore Disabled network upgrades, so Zebra does too.
let status = if tip_height > = activation_height {
NetworkUpgradeStatus ::Active
} else {
NetworkUpgradeStatus ::Pending
} ;
let upgrade = NetworkUpgradeInfo {
name : network_upgrade ,
activation_height ,
status ,
} ;
upgrades . insert ( ConsensusBranchIdHex ( branch_id ) , upgrade ) ;
}
}
// `consensus` object
let next_block_height =
( tip_height + 1 ) . expect ( " valid chain tips are a lot less than Height::MAX " ) ;
let consensus = TipConsensusBranch {
chain_tip : ConsensusBranchIdHex (
NetworkUpgrade ::current ( network , tip_height )
. branch_id ( )
. unwrap_or ( ConsensusBranchId ::RPC_MISSING_ID ) ,
) ,
next_block : ConsensusBranchIdHex (
NetworkUpgrade ::current ( network , next_block_height )
. branch_id ( )
. unwrap_or ( ConsensusBranchId ::RPC_MISSING_ID ) ,
) ,
} ;
2022-02-22 03:26:29 -08:00
let response = GetBlockChainInfo {
2022-03-25 05:25:31 -07:00
chain ,
2022-05-02 21:10:21 -07:00
blocks : tip_height ,
best_block_hash : tip_hash ,
estimated_height ,
2022-03-25 05:25:31 -07:00
upgrades ,
consensus ,
2022-02-22 03:26:29 -08:00
} ;
Ok ( response )
}
2022-03-03 23:00:24 -08:00
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-04-20 11:27:00 -07:00
fn get_address_balance (
& self ,
address_strings : AddressStrings ,
) -> BoxFuture < Result < AddressBalance > > {
let state = self . state . clone ( ) ;
async move {
2022-04-24 20:00:52 -07:00
let valid_addresses = address_strings . valid_addresses ( ) ? ;
2022-04-20 11:27:00 -07:00
2022-04-24 20:00:52 -07:00
let request = zebra_state ::ReadRequest ::AddressBalance ( valid_addresses ) ;
2022-04-20 11:27:00 -07:00
let response = state . oneshot ( request ) . await . map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
match response {
2022-04-28 03:19:02 -07:00
zebra_state ::ReadResponse ::AddressBalance ( balance ) = > Ok ( AddressBalance {
balance : u64 ::from ( balance ) ,
} ) ,
2022-04-20 11:27:00 -07:00
_ = > unreachable! ( " Unexpected response from state service: {response:?} " ) ,
}
}
. boxed ( )
}
2022-12-13 13:25:04 -08:00
// TODO: use HexData or GetRawTransaction::Bytes to handle the transaction data argument
// use a generic error constructor (#5548)
2022-03-03 23:00:24 -08:00
fn send_raw_transaction (
& self ,
raw_transaction_hex : String ,
) -> BoxFuture < Result < SentTransactionHash > > {
let mempool = self . mempool . clone ( ) ;
2022-04-11 22:06:29 -07:00
let queue_sender = self . queue_sender . clone ( ) ;
2022-03-03 23:00:24 -08:00
async move {
let raw_transaction_bytes = Vec ::from_hex ( raw_transaction_hex ) . map_err ( | _ | {
Error ::invalid_params ( " raw transaction is not specified as a hex string " )
} ) ? ;
let raw_transaction = Transaction ::zcash_deserialize ( & * raw_transaction_bytes )
. map_err ( | _ | Error ::invalid_params ( " raw transaction is structurally invalid " ) ) ? ;
let transaction_hash = raw_transaction . hash ( ) ;
2022-04-11 22:06:29 -07:00
// send transaction to the rpc queue, ignore any error.
let unmined_transaction = UnminedTx ::from ( raw_transaction . clone ( ) ) ;
2022-11-10 06:51:53 -08:00
let _ = queue_sender . send ( unmined_transaction ) ;
2022-04-11 22:06:29 -07:00
2022-03-03 23:00:24 -08:00
let transaction_parameter = mempool ::Gossip ::Tx ( raw_transaction . into ( ) ) ;
let request = mempool ::Request ::Queue ( vec! [ transaction_parameter ] ) ;
let response = mempool . oneshot ( request ) . await . map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let queue_results = match response {
mempool ::Response ::Queued ( results ) = > results ,
_ = > unreachable! ( " incorrect response variant from mempool service " ) ,
} ;
assert_eq! (
queue_results . len ( ) ,
1 ,
" mempool service returned more results than expected "
) ;
2022-09-06 06:32:33 -07:00
tracing ::debug! ( " sent transaction to mempool: {:?} " , & queue_results [ 0 ] ) ;
2022-03-03 23:00:24 -08:00
match & queue_results [ 0 ] {
2022-03-08 01:14:21 -08:00
Ok ( ( ) ) = > Ok ( SentTransactionHash ( transaction_hash ) ) ,
2022-03-03 23:00:24 -08:00
Err ( error ) = > Err ( Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ,
}
}
. boxed ( )
}
2022-03-09 17:12:41 -08:00
2022-12-13 13:25:04 -08:00
// TODO:
// - use a generic error constructor (#5548)
// - use `height_from_signed_int()` to handle negative heights
// (this might be better in the state request, because it needs the state height)
// - create a function that handles block hashes or heights, and use it in `z_get_treestate()`
2023-02-02 18:27:29 -08:00
fn get_block (
& self ,
hash_or_height : String ,
verbosity : Option < u8 > ,
) -> BoxFuture < Result < GetBlock > > {
// From <https://zcash.github.io/rpc/getblock.html>
const DEFAULT_GETBLOCK_VERBOSITY : u8 = 1 ;
2022-03-09 17:12:41 -08:00
let mut state = self . state . clone ( ) ;
2023-02-02 18:27:29 -08:00
let verbosity = verbosity . unwrap_or ( DEFAULT_GETBLOCK_VERBOSITY ) ;
2022-03-09 17:12:41 -08:00
async move {
2022-12-13 21:29:33 -08:00
let hash_or_height : HashOrHeight =
hash_or_height
. parse ( )
. map_err ( | error : SerializationError | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
2022-03-09 17:12:41 -08:00
2022-10-02 16:34:44 -07:00
if verbosity = = 0 {
2023-02-06 17:25:34 -08:00
// # Performance
//
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
// so it needs to load block data very efficiently.
2022-12-13 21:29:33 -08:00
let request = zebra_state ::ReadRequest ::Block ( hash_or_height ) ;
2022-10-02 16:34:44 -07:00
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
2022-03-09 17:12:41 -08:00
2022-10-02 16:34:44 -07:00
match response {
zebra_state ::ReadResponse ::Block ( Some ( block ) ) = > {
Ok ( GetBlock ::Raw ( block . into ( ) ) )
}
zebra_state ::ReadResponse ::Block ( None ) = > Err ( Error {
code : MISSING_BLOCK_ERROR_CODE ,
message : " Block not found " . to_string ( ) ,
data : None ,
2022-05-27 02:41:11 -07:00
} ) ,
2022-10-02 16:34:44 -07:00
_ = > unreachable! ( " unmatched response to a block request " ) ,
}
} else if verbosity = = 1 {
2023-02-06 17:25:34 -08:00
// # Performance
//
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
// so it needs to load all its fields very efficiently.
//
2023-02-14 00:52:58 -08:00
// Currently, we get the block hash and transaction IDs from indexes,
// which is much more efficient than loading all the block data,
// then hashing the block header and all the transactions.
2023-02-06 17:25:34 -08:00
2023-02-14 00:52:58 -08:00
// Get the block hash from the height -> hash index, if needed
//
// # Concurrency
//
// For consistency, this lookup must be performed first, then all the other
// lookups must be based on the hash.
//
// All possible responses are valid, even if the best chain changes. Clients
// must be able to handle chain forks, including a hash for a block that is
// later discovered to be on a side chain.
let hash = match hash_or_height {
HashOrHeight ::Hash ( hash ) = > hash ,
HashOrHeight ::Height ( height ) = > {
let request = zebra_state ::ReadRequest ::BestChainBlockHash ( height ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
match response {
zebra_state ::ReadResponse ::BlockHash ( Some ( hash ) ) = > hash ,
zebra_state ::ReadResponse ::BlockHash ( None ) = > {
return Err ( Error {
code : MISSING_BLOCK_ERROR_CODE ,
message : " block height not in best chain " . to_string ( ) ,
data : None ,
} )
}
_ = > unreachable! ( " unmatched response to a block hash request " ) ,
}
}
} ;
2023-02-06 17:25:34 -08:00
2023-02-14 00:52:58 -08:00
// TODO: do the txids and confirmations state queries in parallel?
2023-02-06 17:25:34 -08:00
2023-02-14 00:52:58 -08:00
// Get transaction IDs from the transaction index by block hash
2023-02-06 17:25:34 -08:00
//
// # Concurrency
//
2023-02-14 00:52:58 -08:00
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
//
2023-02-06 17:25:34 -08:00
// A block's transaction IDs are never modified, so all possible responses are
// valid. Clients that query block heights must be able to handle chain forks,
// including getting transaction IDs from any chain fork.
2023-02-14 00:52:58 -08:00
let request = zebra_state ::ReadRequest ::TransactionIdsForBlock ( hash . into ( ) ) ;
2022-10-02 16:34:44 -07:00
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
2023-02-06 17:25:34 -08:00
let tx = match response {
2022-10-02 16:34:44 -07:00
zebra_state ::ReadResponse ::TransactionIdsForBlock ( Some ( tx_ids ) ) = > {
2023-02-06 17:25:34 -08:00
tx_ids . iter ( ) . map ( | tx_id | tx_id . encode_hex ( ) ) . collect ( )
2022-10-02 16:34:44 -07:00
}
zebra_state ::ReadResponse ::TransactionIdsForBlock ( None ) = > Err ( Error {
code : MISSING_BLOCK_ERROR_CODE ,
message : " Block not found " . to_string ( ) ,
2022-05-27 02:41:11 -07:00
data : None ,
2023-02-06 17:25:34 -08:00
} ) ? ,
2022-10-02 16:34:44 -07:00
_ = > unreachable! ( " unmatched response to a transaction_ids_for_block request " ) ,
2023-02-06 17:25:34 -08:00
} ;
// Get block confirmations from the block height index
//
// # Concurrency
//
2023-02-14 00:52:58 -08:00
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
//
2023-02-06 17:25:34 -08:00
// All possible responses are valid, even if a block is added to the chain, or
// the best chain changes. Clients must be able to handle chain forks, including
// different confirmation values before or after added blocks, and switching
// between -1 and multiple different confirmation values.
// From <https://zcash.github.io/rpc/getblock.html>
const NOT_IN_BEST_CHAIN_CONFIRMATIONS : i64 = - 1 ;
2023-02-14 00:52:58 -08:00
let request = zebra_state ::ReadRequest ::Depth ( hash ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let confirmations = match response {
// Confirmations are one more than the depth.
// Depth is limited by height, so it will never overflow an i64.
zebra_state ::ReadResponse ::Depth ( Some ( depth ) ) = > i64 ::from ( depth ) + 1 ,
zebra_state ::ReadResponse ::Depth ( None ) = > NOT_IN_BEST_CHAIN_CONFIRMATIONS ,
_ = > unreachable! ( " unmatched response to a depth request " ) ,
2023-02-06 17:25:34 -08:00
} ;
2023-02-14 00:52:58 -08:00
// TODO: look up the height if we only have a hash,
// this needs a new state request for the height -> hash index
let height = hash_or_height . height ( ) ;
2023-08-15 11:48:50 -07:00
// Sapling trees
//
// # Concurrency
//
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
let request = zebra_state ::ReadRequest ::SaplingTree ( hash . into ( ) ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let sapling_note_commitment_tree_count = match response {
zebra_state ::ReadResponse ::SaplingTree ( Some ( nct ) ) = > nct . count ( ) ,
zebra_state ::ReadResponse ::SaplingTree ( None ) = > 0 ,
_ = > unreachable! ( " unmatched response to a SaplingTree request " ) ,
} ;
// Orchard trees
//
// # Concurrency
//
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
let request = zebra_state ::ReadRequest ::OrchardTree ( hash . into ( ) ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let orchard_note_commitment_tree_count = match response {
zebra_state ::ReadResponse ::OrchardTree ( Some ( nct ) ) = > nct . count ( ) ,
zebra_state ::ReadResponse ::OrchardTree ( None ) = > 0 ,
_ = > unreachable! ( " unmatched response to a OrchardTree request " ) ,
} ;
let sapling = SaplingTrees {
size : sapling_note_commitment_tree_count ,
} ;
let orchard = OrchardTrees {
size : orchard_note_commitment_tree_count ,
} ;
let trees = GetBlockTrees { sapling , orchard } ;
2023-02-06 17:25:34 -08:00
Ok ( GetBlock ::Object {
2023-02-14 00:52:58 -08:00
hash : GetBlockHash ( hash ) ,
2023-02-06 17:25:34 -08:00
confirmations ,
height ,
tx ,
2023-08-15 11:48:50 -07:00
trees ,
2023-02-06 17:25:34 -08:00
} )
2022-10-02 16:34:44 -07:00
} else {
Err ( Error {
code : ErrorCode ::InvalidParams ,
message : " Invalid verbosity value " . to_string ( ) ,
2022-03-09 17:12:41 -08:00
data : None ,
2022-10-02 16:34:44 -07:00
} )
2022-03-09 17:12:41 -08:00
}
}
. boxed ( )
}
2022-03-10 21:13:08 -08:00
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-10-20 23:01:29 -07:00
fn get_best_block_hash ( & self ) -> Result < GetBlockHash > {
2022-03-15 21:01:59 -07:00
self . latest_chain_tip
. best_tip_hash ( )
2022-10-20 23:01:29 -07:00
. map ( GetBlockHash )
2022-03-15 21:01:59 -07:00
. ok_or ( Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " No blocks in state " . to_string ( ) ,
data : None ,
} )
2022-03-10 21:13:08 -08:00
}
2022-03-12 08:51:49 -08:00
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-03-12 08:51:49 -08:00
fn get_raw_mempool ( & self ) -> BoxFuture < Result < Vec < String > > > {
2023-01-22 20:48:18 -08:00
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
use zebra_chain ::block ::MAX_BLOCK_BYTES ;
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
2023-02-22 16:10:11 -08:00
// Determines whether the output of this RPC is sorted like zcashd
let should_use_zcashd_order = self . debug_like_zcashd ;
2023-01-22 20:48:18 -08:00
2022-03-12 08:51:49 -08:00
let mut mempool = self . mempool . clone ( ) ;
async move {
2023-01-22 20:48:18 -08:00
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
2023-02-22 16:10:11 -08:00
let request = if should_use_zcashd_order {
2023-01-22 20:48:18 -08:00
mempool ::Request ::FullTransactions
} else {
mempool ::Request ::TransactionIds
} ;
#[ cfg(not(feature = " getblocktemplate-rpcs " )) ]
2022-03-12 08:51:49 -08:00
let request = mempool ::Request ::TransactionIds ;
2022-12-13 13:25:04 -08:00
// `zcashd` doesn't check if it is synced to the tip here, so we don't either.
2022-03-12 08:51:49 -08:00
let response = mempool
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
match response {
2023-01-22 20:48:18 -08:00
#[ cfg(feature = " getblocktemplate-rpcs " ) ]
2023-04-03 16:22:07 -07:00
mempool ::Response ::FullTransactions {
mut transactions ,
last_seen_tip_hash : _ ,
} = > {
2023-01-22 20:48:18 -08:00
// Sort transactions in descending order by fee/size, using hash in serialized byte order as a tie-breaker
transactions . sort_by_cached_key ( | tx | {
// zcashd uses modified fee here but Zebra doesn't currently
// support prioritizing transactions
std ::cmp ::Reverse ( (
i64 ::from ( tx . miner_fee ) as u128 * MAX_BLOCK_BYTES as u128
/ tx . transaction . size as u128 ,
// transaction hashes are compared in their serialized byte-order.
tx . transaction . id . mined_id ( ) ,
) )
} ) ;
let tx_ids : Vec < String > = transactions
. iter ( )
. map ( | unmined_tx | unmined_tx . transaction . id . mined_id ( ) . encode_hex ( ) )
. collect ( ) ;
Ok ( tx_ids )
}
2022-03-12 08:51:49 -08:00
mempool ::Response ::TransactionIds ( unmined_transaction_ids ) = > {
2022-04-28 03:19:02 -07:00
let mut tx_ids : Vec < String > = unmined_transaction_ids
2022-03-12 08:51:49 -08:00
. iter ( )
. map ( | id | id . mined_id ( ) . encode_hex ( ) )
2022-04-28 03:19:02 -07:00
. collect ( ) ;
// Sort returned transaction IDs in numeric/string order.
tx_ids . sort ( ) ;
Ok ( tx_ids )
2022-03-12 08:51:49 -08:00
}
2023-01-22 20:48:18 -08:00
2022-03-12 08:51:49 -08:00
_ = > unreachable! ( " unmatched response to a transactionids request " ) ,
}
}
. boxed ( )
}
2022-03-24 02:45:37 -07:00
2022-12-13 13:25:04 -08:00
// TODO: use HexData or SentTransactionHash to handle the transaction ID
// use a generic error constructor (#5548)
2022-03-24 02:45:37 -07:00
fn get_raw_transaction (
& self ,
txid_hex : String ,
2023-12-10 13:44:43 -08:00
verbose : Option < u8 > ,
2022-03-24 02:45:37 -07:00
) -> BoxFuture < Result < GetRawTransaction > > {
let mut state = self . state . clone ( ) ;
let mut mempool = self . mempool . clone ( ) ;
2023-12-10 13:44:43 -08:00
let verbose = verbose . unwrap_or ( 0 ) ;
2023-03-26 16:53:44 -07:00
let verbose = verbose ! = 0 ;
2022-03-24 02:45:37 -07:00
async move {
let txid = transaction ::Hash ::from_hex ( txid_hex ) . map_err ( | _ | {
Error ::invalid_params ( " transaction ID is not specified as a hex string " )
} ) ? ;
// Check the mempool first.
//
// # Correctness
//
// Transactions are removed from the mempool after they are mined into blocks,
// so the transaction could be just in the mempool, just in the state, or in both.
// (And the mempool and state transactions could have different authorising data.)
// But it doesn't matter which transaction we choose, because the effects are the same.
let mut txid_set = HashSet ::new ( ) ;
txid_set . insert ( txid ) ;
let request = mempool ::Request ::TransactionsByMinedId ( txid_set ) ;
let response = mempool
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
match response {
mempool ::Response ::Transactions ( unmined_transactions ) = > {
if ! unmined_transactions . is_empty ( ) {
let tx = unmined_transactions [ 0 ] . transaction . clone ( ) ;
2023-03-26 16:53:44 -07:00
return Ok ( GetRawTransaction ::from_transaction ( tx , None , 0 , verbose ) ) ;
2022-03-24 02:45:37 -07:00
}
}
_ = > unreachable! ( " unmatched response to a transactionids request " ) ,
} ;
// Now check the state
let request = zebra_state ::ReadRequest ::Transaction ( txid ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
match response {
2023-03-26 16:53:44 -07:00
zebra_state ::ReadResponse ::Transaction ( Some ( MinedTx {
tx ,
height ,
confirmations ,
} ) ) = > Ok ( GetRawTransaction ::from_transaction (
tx ,
Some ( height ) ,
confirmations ,
verbose ,
) ) ,
2022-03-24 02:45:37 -07:00
zebra_state ::ReadResponse ::Transaction ( None ) = > Err ( Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " Transaction not found " . to_string ( ) ,
data : None ,
} ) ,
_ = > unreachable! ( " unmatched response to a transaction request " ) ,
}
}
. boxed ( )
}
2022-04-13 01:48:13 -07:00
2022-12-13 13:25:04 -08:00
// TODO:
// - use a generic error constructor (#5548)
// - use `height_from_signed_int()` to handle negative heights
// (this might be better in the state request, because it needs the state height)
// - create a function that handles block hashes or heights, and use it in `get_block()`
2022-05-12 00:00:12 -07:00
fn z_get_treestate ( & self , hash_or_height : String ) -> BoxFuture < Result < GetTreestate > > {
let mut state = self . state . clone ( ) ;
async move {
// Convert the [`hash_or_height`] string into an actual hash or height.
let hash_or_height = hash_or_height
. parse ( )
. map_err ( | error : SerializationError | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
// Fetch the block referenced by [`hash_or_height`] from the state.
// TODO: If this RPC is called a lot, just get the block header,
// rather than the whole block.
let block_request = zebra_state ::ReadRequest ::Block ( hash_or_height ) ;
let block_response = state
. ready ( )
. and_then ( | service | service . call ( block_request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
// The block hash, height, and time are all required fields in the
// RPC response. For this reason, we throw an error early if the
// state didn't return the requested block so that we prevent
// further state queries.
let block = match block_response {
zebra_state ::ReadResponse ::Block ( Some ( block ) ) = > block ,
zebra_state ::ReadResponse ::Block ( None ) = > {
return Err ( Error {
2023-05-17 08:13:12 -07:00
code : MISSING_BLOCK_ERROR_CODE ,
2022-05-12 00:00:12 -07:00
message : " the requested block was not found " . to_string ( ) ,
data : None ,
} )
}
_ = > unreachable! ( " unmatched response to a block request " ) ,
} ;
// Fetch the Sapling & Orchard treestates referenced by
// [`hash_or_height`] from the state.
let sapling_request = zebra_state ::ReadRequest ::SaplingTree ( hash_or_height ) ;
let sapling_response = state
. ready ( )
. and_then ( | service | service . call ( sapling_request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let orchard_request = zebra_state ::ReadRequest ::OrchardTree ( hash_or_height ) ;
let orchard_response = state
. ready ( )
. and_then ( | service | service . call ( orchard_request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
// We've got all the data we need for the RPC response, so we
// assemble the response.
let hash = block . hash ( ) ;
let height = block
. coinbase_height ( )
. expect ( " verified blocks have a valid height " ) ;
let time = u32 ::try_from ( block . header . time . timestamp ( ) )
. expect ( " Timestamps of valid blocks always fit into u32. " ) ;
let sapling_tree = match sapling_response {
zebra_state ::ReadResponse ::SaplingTree ( maybe_tree ) = > {
sapling ::tree ::SerializedTree ::from ( maybe_tree )
}
_ = > unreachable! ( " unmatched response to a sapling tree request " ) ,
} ;
let orchard_tree = match orchard_response {
zebra_state ::ReadResponse ::OrchardTree ( maybe_tree ) = > {
orchard ::tree ::SerializedTree ::from ( maybe_tree )
}
_ = > unreachable! ( " unmatched response to an orchard tree request " ) ,
} ;
Ok ( GetTreestate {
hash ,
height ,
time ,
sapling : Treestate {
commitments : Commitments {
final_state : sapling_tree ,
} ,
} ,
orchard : Treestate {
commitments : Commitments {
final_state : orchard_tree ,
} ,
} ,
} )
}
. boxed ( )
}
2023-09-04 14:05:39 -07:00
fn z_get_subtrees_by_index (
& self ,
pool : String ,
start_index : NoteCommitmentSubtreeIndex ,
limit : Option < NoteCommitmentSubtreeIndex > ,
) -> BoxFuture < Result < GetSubtrees > > {
let mut state = self . state . clone ( ) ;
async move {
const POOL_LIST : & [ & str ] = & [ " sapling " , " orchard " ] ;
if pool = = " sapling " {
let request = zebra_state ::ReadRequest ::SaplingSubtrees { start_index , limit } ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let subtrees = match response {
zebra_state ::ReadResponse ::SaplingSubtrees ( subtrees ) = > subtrees ,
_ = > unreachable! ( " unmatched response to a subtrees request " ) ,
} ;
let subtrees = subtrees
. values ( )
. map ( | subtree | SubtreeRpcData {
2023-10-30 13:06:54 -07:00
root : subtree . root . encode_hex ( ) ,
end_height : subtree . end_height ,
2023-09-04 14:05:39 -07:00
} )
. collect ( ) ;
Ok ( GetSubtrees {
pool ,
start_index ,
subtrees ,
} )
} else if pool = = " orchard " {
let request = zebra_state ::ReadRequest ::OrchardSubtrees { start_index , limit } ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let subtrees = match response {
zebra_state ::ReadResponse ::OrchardSubtrees ( subtrees ) = > subtrees ,
_ = > unreachable! ( " unmatched response to a subtrees request " ) ,
} ;
let subtrees = subtrees
. values ( )
. map ( | subtree | SubtreeRpcData {
2023-10-30 13:06:54 -07:00
root : subtree . root . encode_hex ( ) ,
end_height : subtree . end_height ,
2023-09-04 14:05:39 -07:00
} )
. collect ( ) ;
Ok ( GetSubtrees {
pool ,
start_index ,
subtrees ,
} )
} else {
Err ( Error {
code : INVALID_PARAMETERS_ERROR_CODE ,
message : format ! ( " invalid pool name, must be one of: {:?} " , POOL_LIST ) ,
data : None ,
} )
}
}
. boxed ( )
}
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-04-13 01:48:13 -07:00
fn get_address_tx_ids (
& self ,
2022-05-04 18:08:27 -07:00
request : GetAddressTxIdsRequest ,
2022-04-13 01:48:13 -07:00
) -> BoxFuture < Result < Vec < String > > > {
let mut state = self . state . clone ( ) ;
2022-11-04 11:19:48 -07:00
let latest_chain_tip = self . latest_chain_tip . clone ( ) ;
2022-05-04 18:08:27 -07:00
let start = Height ( request . start ) ;
let end = Height ( request . end ) ;
2022-04-13 01:48:13 -07:00
async move {
2022-11-04 11:19:48 -07:00
let chain_height = best_chain_tip_height ( & latest_chain_tip ) ? ;
2022-04-13 01:48:13 -07:00
// height range checks
2022-11-04 11:19:48 -07:00
check_height_range ( start , end , chain_height ) ? ;
2022-04-13 01:48:13 -07:00
2022-05-04 18:08:27 -07:00
let valid_addresses = AddressStrings {
addresses : request . addresses ,
}
. valid_addresses ( ) ? ;
2022-04-13 01:48:13 -07:00
2022-04-21 13:19:26 -07:00
let request = zebra_state ::ReadRequest ::TransactionIdsByAddresses {
2022-04-24 20:00:52 -07:00
addresses : valid_addresses ,
2022-04-21 13:19:26 -07:00
height_range : start ..= end ,
} ;
2022-04-13 01:48:13 -07:00
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
2022-04-21 13:19:26 -07:00
let hashes = match response {
zebra_state ::ReadResponse ::AddressesTransactionIds ( hashes ) = > {
2022-05-11 14:43:17 -07:00
let mut last_tx_location = TransactionLocation ::from_usize ( Height ( 0 ) , 0 ) ;
hashes
. iter ( )
. map ( | ( tx_loc , tx_id ) | {
2022-09-28 09:09:56 -07:00
// Check that the returned transactions are in chain order.
2022-05-11 14:43:17 -07:00
assert! (
* tx_loc > last_tx_location ,
" Transactions were not in chain order: \n \
{ tx_loc :? } { tx_id :? } was after :\ n \
{ last_tx_location :? } " ,
) ;
last_tx_location = * tx_loc ;
tx_id . to_string ( )
} )
. collect ( )
2022-04-21 13:19:26 -07:00
}
2022-04-13 01:48:13 -07:00
_ = > unreachable! ( " unmatched response to a TransactionsByAddresses request " ) ,
2022-04-21 13:19:26 -07:00
} ;
2022-04-13 01:48:13 -07:00
2022-04-21 13:19:26 -07:00
Ok ( hashes )
2022-04-13 01:48:13 -07:00
}
. boxed ( )
}
2022-04-24 20:00:52 -07:00
2022-12-07 22:11:33 -08:00
// TODO: use a generic error constructor (#5548)
2022-04-24 20:00:52 -07:00
fn get_address_utxos (
& self ,
address_strings : AddressStrings ,
) -> BoxFuture < Result < Vec < GetAddressUtxos > > > {
let mut state = self . state . clone ( ) ;
let mut response_utxos = vec! [ ] ;
async move {
let valid_addresses = address_strings . valid_addresses ( ) ? ;
// get utxos data for addresses
let request = zebra_state ::ReadRequest ::UtxosByAddresses ( valid_addresses ) ;
let response = state
. ready ( )
. and_then ( | service | service . call ( request ) )
. await
. map_err ( | error | Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : error . to_string ( ) ,
data : None ,
} ) ? ;
let utxos = match response {
2022-09-15 21:13:26 -07:00
zebra_state ::ReadResponse ::AddressUtxos ( utxos ) = > utxos ,
2022-04-24 20:00:52 -07:00
_ = > unreachable! ( " unmatched response to a UtxosByAddresses request " ) ,
} ;
2022-05-11 14:43:17 -07:00
let mut last_output_location = OutputLocation ::from_usize ( Height ( 0 ) , 0 , 0 ) ;
2022-04-24 20:00:52 -07:00
for utxo_data in utxos . utxos ( ) {
2022-05-02 21:10:21 -07:00
let address = utxo_data . 0 ;
let txid = * utxo_data . 1 ;
let height = utxo_data . 2. height ( ) ;
let output_index = utxo_data . 2. output_index ( ) ;
let script = utxo_data . 3. lock_script . clone ( ) ;
2022-04-28 03:19:02 -07:00
let satoshis = u64 ::from ( utxo_data . 3. value ) ;
2022-04-24 20:00:52 -07:00
2022-05-11 14:43:17 -07:00
let output_location = * utxo_data . 2 ;
2022-09-28 09:09:56 -07:00
// Check that the returned UTXOs are in chain order.
2022-05-11 14:43:17 -07:00
assert! (
output_location > last_output_location ,
" UTXOs were not in chain order: \n \
{ output_location :? } { address :? } { txid :? } was after :\ n \
{ last_output_location :? } " ,
) ;
2022-04-24 20:00:52 -07:00
let entry = GetAddressUtxos {
address ,
txid ,
output_index ,
script ,
satoshis ,
2022-05-02 21:10:21 -07:00
height ,
2022-04-24 20:00:52 -07:00
} ;
response_utxos . push ( entry ) ;
2022-05-11 14:43:17 -07:00
last_output_location = output_location ;
2022-04-24 20:00:52 -07:00
}
Ok ( response_utxos )
}
. boxed ( )
}
2022-02-22 03:26:29 -08:00
}
2022-11-04 11:19:48 -07:00
/// Returns the best chain tip height of `latest_chain_tip`,
/// or an RPC error if there are no blocks in the state.
pub fn best_chain_tip_height < Tip > ( latest_chain_tip : & Tip ) -> Result < Height >
where
Tip : ChainTip + Clone + Send + Sync + 'static ,
{
latest_chain_tip . best_tip_height ( ) . ok_or ( Error {
code : ErrorCode ::ServerError ( 0 ) ,
message : " No blocks in state " . to_string ( ) ,
data : None ,
} )
}
2022-02-22 03:26:29 -08:00
/// Response to a `getinfo` RPC request.
2022-03-15 02:13:24 -07:00
///
/// See the notes for the [`Rpc::get_info` method].
2022-03-25 05:25:31 -07:00
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
2022-02-22 03:26:29 -08:00
pub struct GetInfo {
2022-05-02 21:10:21 -07:00
/// The node version build number
2022-02-22 03:26:29 -08:00
build : String ,
2022-05-02 21:10:21 -07:00
/// The server sub-version identifier, used as the network protocol user-agent
2022-02-22 03:26:29 -08:00
subversion : String ,
}
2024-03-14 08:04:19 -07:00
impl Default for GetInfo {
fn default ( ) -> Self {
GetInfo {
build : " some build version " . to_string ( ) ,
subversion : " some subversion " . to_string ( ) ,
}
}
}
2022-02-22 03:26:29 -08:00
/// Response to a `getblockchaininfo` RPC request.
2022-03-15 02:13:24 -07:00
///
/// See the notes for the [`Rpc::get_blockchain_info` method].
2022-03-25 05:25:31 -07:00
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
2022-02-22 03:26:29 -08:00
pub struct GetBlockChainInfo {
2022-05-02 21:10:21 -07:00
/// Current network name as defined in BIP70 (main, test, regtest)
2022-02-22 03:26:29 -08:00
chain : String ,
2022-05-02 21:10:21 -07:00
/// The current number of blocks processed in the server, numeric
blocks : Height ,
/// The hash of the currently best block, in big-endian order, hex-encoded
#[ serde(rename = " bestblockhash " , with = " hex " ) ]
best_block_hash : block ::Hash ,
/// If syncing, the estimated height of the chain, else the current best height, numeric.
///
/// In Zebra, this is always the height estimate, so it might be a little inaccurate.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " estimatedheight " ) ]
2022-05-02 21:10:21 -07:00
estimated_height : Height ,
/// Status of network upgrades
2022-03-25 05:25:31 -07:00
upgrades : IndexMap < ConsensusBranchIdHex , NetworkUpgradeInfo > ,
2022-05-02 21:10:21 -07:00
/// Branch IDs of the current and upcoming consensus rules
2022-03-25 05:25:31 -07:00
consensus : TipConsensusBranch ,
}
2024-03-14 08:04:19 -07:00
impl Default for GetBlockChainInfo {
fn default ( ) -> Self {
GetBlockChainInfo {
chain : " main " . to_string ( ) ,
blocks : Height ( 1 ) ,
best_block_hash : block ::Hash ( [ 0 ; 32 ] ) ,
estimated_height : Height ( 1 ) ,
upgrades : IndexMap ::new ( ) ,
consensus : TipConsensusBranch {
chain_tip : ConsensusBranchIdHex ( ConsensusBranchId ::default ( ) ) ,
next_block : ConsensusBranchIdHex ( ConsensusBranchId ::default ( ) ) ,
} ,
}
}
}
2022-05-02 21:10:21 -07:00
/// A wrapper type with a list of transparent address strings.
2022-04-20 11:27:00 -07:00
///
2022-04-24 20:00:52 -07:00
/// This is used for the input parameter of [`Rpc::get_address_balance`],
/// [`Rpc::get_address_tx_ids`] and [`Rpc::get_address_utxos`].
2022-04-20 11:27:00 -07:00
#[ derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize) ]
pub struct AddressStrings {
2022-05-02 21:10:21 -07:00
/// A list of transparent address strings.
2022-04-20 11:27:00 -07:00
addresses : Vec < String > ,
}
2022-04-24 20:00:52 -07:00
impl AddressStrings {
2022-05-02 21:10:21 -07:00
/// Creates a new `AddressStrings` given a vector.
2022-04-24 20:00:52 -07:00
#[ cfg(test) ]
pub fn new ( addresses : Vec < String > ) -> AddressStrings {
AddressStrings { addresses }
}
2022-05-02 21:10:21 -07:00
2022-04-24 20:00:52 -07:00
/// Given a list of addresses as strings:
/// - check if provided list have all valid transparent addresses.
/// - return valid addresses as a set of `Address`.
pub fn valid_addresses ( self ) -> Result < HashSet < Address > > {
let valid_addresses : HashSet < Address > = self
. addresses
. into_iter ( )
. map ( | address | {
address . parse ( ) . map_err ( | error | {
2022-10-27 06:25:18 -07:00
Error ::invalid_params ( format! ( " invalid address {address:?} : {error} " ) )
2022-04-24 20:00:52 -07:00
} )
} )
. collect ::< Result < _ > > ( ) ? ;
Ok ( valid_addresses )
}
}
2022-04-20 11:27:00 -07:00
/// The transparent balance of a set of addresses.
#[ derive(Clone, Copy, Debug, Eq, PartialEq, Hash, serde::Serialize) ]
pub struct AddressBalance {
2022-05-02 21:10:21 -07:00
/// The total transparent balance.
2022-04-28 03:19:02 -07:00
balance : u64 ,
2022-04-20 11:27:00 -07:00
}
2022-03-25 05:25:31 -07:00
/// A hex-encoded [`ConsensusBranchId`] string.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize) ]
struct ConsensusBranchIdHex ( #[ serde(with = " hex " ) ] ConsensusBranchId ) ;
/// Information about [`NetworkUpgrade`] activation.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
struct NetworkUpgradeInfo {
2022-05-02 21:10:21 -07:00
/// Name of upgrade, string.
///
/// Ignored by lightwalletd, but useful for debugging.
2022-03-25 05:25:31 -07:00
name : NetworkUpgrade ,
2022-05-02 21:10:21 -07:00
/// Block height of activation, numeric.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " activationheight " ) ]
activation_height : Height ,
2022-05-02 21:10:21 -07:00
/// Status of upgrade, string.
2022-03-25 05:25:31 -07:00
status : NetworkUpgradeStatus ,
}
/// The activation status of a [`NetworkUpgrade`].
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
enum NetworkUpgradeStatus {
2022-05-02 21:10:21 -07:00
/// The network upgrade is currently active.
///
/// Includes all network upgrades that have previously activated,
/// even if they are not the most recent network upgrade.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " active " ) ]
Active ,
2022-05-02 21:10:21 -07:00
/// The network upgrade does not have an activation height.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " disabled " ) ]
Disabled ,
2022-05-02 21:10:21 -07:00
/// The network upgrade has an activation height, but we haven't reached it yet.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " pending " ) ]
Pending ,
}
/// The [`ConsensusBranchId`]s for the tip and the next block.
///
/// These branch IDs are different when the next block is a network upgrade activation block.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
struct TipConsensusBranch {
2022-05-02 21:10:21 -07:00
/// Branch ID used to validate the current chain tip, big-endian, hex-encoded.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " chaintip " ) ]
chain_tip : ConsensusBranchIdHex ,
2022-05-02 21:10:21 -07:00
/// Branch ID used to validate the next block, big-endian, hex-encoded.
2022-03-25 05:25:31 -07:00
#[ serde(rename = " nextblock " ) ]
next_block : ConsensusBranchIdHex ,
2022-02-22 03:26:29 -08:00
}
2022-03-03 23:00:24 -08:00
/// Response to a `sendrawtransaction` RPC request.
///
2022-03-15 02:13:24 -07:00
/// Contains the hex-encoded hash of the sent transaction.
///
/// See the notes for the [`Rpc::send_raw_transaction` method].
2022-03-25 05:25:31 -07:00
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize) ]
2022-03-08 01:14:21 -08:00
pub struct SentTransactionHash ( #[ serde(with = " hex " ) ] transaction ::Hash ) ;
2022-03-09 17:12:41 -08:00
/// Response to a `getblock` RPC request.
2022-03-15 02:13:24 -07:00
///
2023-02-14 00:52:58 -08:00
/// See the notes for the [`Rpc::get_block`] method.
2022-03-25 05:25:31 -07:00
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
2022-05-27 02:41:11 -07:00
#[ serde(untagged) ]
pub enum GetBlock {
/// The request block, hex-encoded.
Raw ( #[ serde(with = " hex " ) ] SerializedBlock ) ,
/// The block object.
Object {
2023-02-06 17:25:34 -08:00
/// The hash of the requested block.
2023-02-14 00:52:58 -08:00
hash : GetBlockHash ,
2023-02-06 17:25:34 -08:00
/// The number of confirmations of this block in the best chain,
/// or -1 if it is not in the best chain.
2023-02-14 00:52:58 -08:00
confirmations : i64 ,
2023-02-06 17:25:34 -08:00
/// The height of the requested block.
#[ serde(skip_serializing_if = " Option::is_none " ) ]
height : Option < Height > ,
2022-10-02 16:34:44 -07:00
/// List of transaction IDs in block order, hex-encoded.
2023-02-06 17:25:34 -08:00
//
// TODO: use a typed Vec<transaction::Hash> here
2022-05-27 02:41:11 -07:00
tx : Vec < String > ,
2023-08-15 11:48:50 -07:00
/// Information about the note commitment trees.
trees : GetBlockTrees ,
2022-05-27 02:41:11 -07:00
} ,
}
2022-03-10 21:13:08 -08:00
2024-03-14 08:04:19 -07:00
impl Default for GetBlock {
fn default ( ) -> Self {
GetBlock ::Object {
hash : GetBlockHash ::default ( ) ,
confirmations : 0 ,
height : None ,
tx : Vec ::new ( ) ,
trees : GetBlockTrees ::default ( ) ,
}
}
}
2022-10-20 23:01:29 -07:00
/// Response to a `getbestblockhash` and `getblockhash` RPC request.
2022-03-15 02:13:24 -07:00
///
2022-10-20 23:01:29 -07:00
/// Contains the hex-encoded hash of the requested block.
2022-03-15 02:13:24 -07:00
///
2022-10-20 23:01:29 -07:00
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
2022-03-25 05:25:31 -07:00
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize) ]
2023-02-06 17:25:34 -08:00
#[ serde(transparent) ]
2023-01-16 20:03:40 -08:00
pub struct GetBlockHash ( #[ serde(with = " hex " ) ] pub block ::Hash ) ;
2022-03-24 02:45:37 -07:00
2024-03-14 08:04:19 -07:00
impl Default for GetBlockHash {
fn default ( ) -> Self {
GetBlockHash ( block ::Hash ( [ 0 ; 32 ] ) )
}
}
2022-05-12 00:00:12 -07:00
/// Response to a `z_gettreestate` RPC request.
///
/// Contains the hex-encoded Sapling & Orchard note commitment trees, and their
/// corresponding [`block::Hash`], [`Height`], and block time.
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
pub struct GetTreestate {
/// The block hash corresponding to the treestate, hex-encoded.
#[ serde(with = " hex " ) ]
hash : block ::Hash ,
/// The block height corresponding to the treestate, numeric.
height : Height ,
/// Unix time when the block corresponding to the treestate was mined,
/// numeric.
///
/// UTC seconds since the Unix 1970-01-01 epoch.
time : u32 ,
/// A treestate containing a Sapling note commitment tree, hex-encoded.
#[ serde(skip_serializing_if = " Treestate::is_empty " ) ]
sapling : Treestate < sapling ::tree ::SerializedTree > ,
/// A treestate containing an Orchard note commitment tree, hex-encoded.
#[ serde(skip_serializing_if = " Treestate::is_empty " ) ]
orchard : Treestate < orchard ::tree ::SerializedTree > ,
}
2024-03-14 08:04:19 -07:00
impl Default for GetTreestate {
fn default ( ) -> Self {
GetTreestate {
hash : block ::Hash ( [ 0 ; 32 ] ) ,
height : Height ( 0 ) ,
time : 0 ,
sapling : Treestate {
commitments : Commitments {
final_state : sapling ::tree ::SerializedTree ::default ( ) ,
} ,
} ,
orchard : Treestate {
commitments : Commitments {
final_state : orchard ::tree ::SerializedTree ::default ( ) ,
} ,
} ,
}
}
}
2022-05-12 00:00:12 -07:00
/// A treestate that is included in the [`z_gettreestate`][1] RPC response.
///
/// [1]: https://zcash.github.io/rpc/z_gettreestate.html
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
struct Treestate < Tree : AsRef < [ u8 ] > > {
/// Contains an Orchard or Sapling serialized note commitment tree,
/// hex-encoded.
commitments : Commitments < Tree > ,
}
/// A wrapper that contains either an Orchard or Sapling note commitment tree.
///
/// Note that in the original [`z_gettreestate`][1] RPC, [`Commitments`] also
/// contains the field `finalRoot`. Zebra does *not* use this field.
///
/// [1]: https://zcash.github.io/rpc/z_gettreestate.html
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
struct Commitments < Tree : AsRef < [ u8 ] > > {
/// Orchard or Sapling serialized note commitment tree, hex-encoded.
#[ serde(with = " hex " ) ]
#[ serde(rename = " finalState " ) ]
final_state : Tree ,
}
impl < Tree : AsRef < [ u8 ] > > Treestate < Tree > {
/// Returns `true` if there's no serialized commitment tree.
fn is_empty ( & self ) -> bool {
self . commitments . final_state . as_ref ( ) . is_empty ( )
}
}
2022-03-24 02:45:37 -07:00
/// Response to a `getrawtransaction` RPC request.
///
/// See the notes for the [`Rpc::get_raw_transaction` method].
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
#[ serde(untagged) ]
pub enum GetRawTransaction {
/// The raw transaction, encoded as hex bytes.
Raw ( #[ serde(with = " hex " ) ] SerializedTransaction ) ,
/// The transaction object.
Object {
/// The raw transaction, encoded as hex bytes.
#[ serde(with = " hex " ) ]
hex : SerializedTransaction ,
2023-03-26 16:53:44 -07:00
/// The height of the block in the best chain that contains the transaction, or -1 if
/// the transaction is in the mempool.
2022-03-24 02:45:37 -07:00
height : i32 ,
2023-03-26 16:53:44 -07:00
/// The confirmations of the block in the best chain that contains the transaction,
/// or 0 if the transaction is in the mempool.
confirmations : u32 ,
2022-03-24 02:45:37 -07:00
} ,
}
2022-04-24 20:00:52 -07:00
/// Response to a `getaddressutxos` RPC request.
///
/// See the notes for the [`Rpc::get_address_utxos` method].
2022-05-02 21:10:21 -07:00
#[ derive(Clone, Debug, Eq, PartialEq, serde::Serialize) ]
2022-04-24 20:00:52 -07:00
pub struct GetAddressUtxos {
2022-05-02 21:10:21 -07:00
/// The transparent address, base58check encoded
address : transparent ::Address ,
/// The output txid, in big-endian order, hex-encoded
#[ serde(with = " hex " ) ]
txid : transaction ::Hash ,
/// The transparent output index, numeric
2022-04-24 20:00:52 -07:00
#[ serde(rename = " outputIndex " ) ]
2022-05-02 21:10:21 -07:00
output_index : OutputIndex ,
/// The transparent output script, hex encoded
#[ serde(with = " hex " ) ]
script : transparent ::Script ,
/// The amount of zatoshis in the transparent output
2022-04-28 03:19:02 -07:00
satoshis : u64 ,
2022-05-02 21:10:21 -07:00
/// The block height, numeric.
///
/// We put this field last, to match the zcashd order.
height : Height ,
2022-04-24 20:00:52 -07:00
}
2022-05-04 18:08:27 -07:00
/// A struct to use as parameter of the `getaddresstxids`.
///
/// See the notes for the [`Rpc::get_address_tx_ids` method].
#[ derive(Clone, Debug, Eq, PartialEq, serde::Deserialize) ]
pub struct GetAddressTxIdsRequest {
// A list of addresses to get transactions from.
addresses : Vec < String > ,
// The height to start looking for transactions.
start : u32 ,
// The height to end looking for transactions.
end : u32 ,
}
2022-03-24 02:45:37 -07:00
impl GetRawTransaction {
2022-05-02 21:10:21 -07:00
/// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2022-03-24 02:45:37 -07:00
fn from_transaction (
tx : Arc < Transaction > ,
height : Option < block ::Height > ,
2023-03-26 16:53:44 -07:00
confirmations : u32 ,
2022-03-24 02:45:37 -07:00
verbose : bool ,
2023-03-26 16:53:44 -07:00
) -> Self {
2022-03-24 02:45:37 -07:00
if verbose {
2023-03-26 16:53:44 -07:00
GetRawTransaction ::Object {
2022-03-24 02:45:37 -07:00
hex : tx . into ( ) ,
height : match height {
Some ( height ) = > height
. 0
. try_into ( )
. expect ( " valid block heights are limited to i32::MAX " ) ,
None = > - 1 ,
} ,
2023-03-26 16:53:44 -07:00
confirmations ,
}
2022-03-24 02:45:37 -07:00
} else {
2023-03-26 16:53:44 -07:00
GetRawTransaction ::Raw ( tx . into ( ) )
2022-03-24 02:45:37 -07:00
}
}
}
2022-04-13 01:48:13 -07:00
2023-08-15 11:48:50 -07:00
/// Information about the sapling and orchard note commitment trees if any.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize) ]
pub struct GetBlockTrees {
#[ serde(skip_serializing_if = " SaplingTrees::is_empty " ) ]
sapling : SaplingTrees ,
#[ serde(skip_serializing_if = " OrchardTrees::is_empty " ) ]
orchard : OrchardTrees ,
}
2024-03-14 08:04:19 -07:00
impl Default for GetBlockTrees {
fn default ( ) -> Self {
GetBlockTrees {
sapling : SaplingTrees { size : 0 } ,
orchard : OrchardTrees { size : 0 } ,
}
}
}
2023-08-15 11:48:50 -07:00
/// Sapling note commitment tree information.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize) ]
pub struct SaplingTrees {
size : u64 ,
}
impl SaplingTrees {
fn is_empty ( & self ) -> bool {
self . size = = 0
}
}
/// Orchard note commitment tree information.
#[ derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize) ]
pub struct OrchardTrees {
size : u64 ,
}
impl OrchardTrees {
fn is_empty ( & self ) -> bool {
self . size = = 0
}
}
2022-05-02 21:10:21 -07:00
/// Check if provided height range is valid for address indexes.
2022-04-13 01:48:13 -07:00
fn check_height_range ( start : Height , end : Height , chain_height : Height ) -> Result < ( ) > {
if start = = Height ( 0 ) | | end = = Height ( 0 ) {
2022-09-28 09:09:56 -07:00
return Err ( Error ::invalid_params ( format! (
" start {start:?} and end {end:?} must both be greater than zero "
) ) ) ;
2022-04-13 01:48:13 -07:00
}
2022-09-28 09:09:56 -07:00
if start > end {
return Err ( Error ::invalid_params ( format! (
" start {start:?} must be less than or equal to end {end:?} "
) ) ) ;
2022-04-13 01:48:13 -07:00
}
if start > chain_height | | end > chain_height {
2022-09-28 09:09:56 -07:00
return Err ( Error ::invalid_params ( format! (
" start {start:?} and end {end:?} must both be less than or equal to the chain tip {chain_height:?} "
) ) ) ;
2022-04-13 01:48:13 -07:00
}
Ok ( ( ) )
}
2022-12-13 13:25:04 -08:00
/// Given a potentially negative index, find the corresponding `Height`.
///
/// This function is used to parse the integer index argument of `get_block_hash`.
/// This is based on zcashd's implementation:
/// <https://github.com/zcash/zcash/blob/c267c3ee26510a974554f227d40a89e3ceb5bb4d/src/rpc/blockchain.cpp#L589-L618>
//
// TODO: also use this function in `get_block` and `z_get_treestate`
#[ allow(dead_code) ]
pub fn height_from_signed_int ( index : i32 , tip_height : Height ) -> Result < Height > {
if index > = 0 {
let height = index . try_into ( ) . expect ( " Positive i32 always fits in u32 " ) ;
if height > tip_height . 0 {
return Err ( Error ::invalid_params (
" Provided index is greater than the current tip " ,
) ) ;
}
Ok ( Height ( height ) )
} else {
// `index + 1` can't overflow, because `index` is always negative here.
let height = i32 ::try_from ( tip_height . 0 )
. expect ( " tip height fits in i32, because Height::MAX fits in i32 " )
. checked_add ( index + 1 ) ;
let sanitized_height = match height {
None = > return Err ( Error ::invalid_params ( " Provided index is not valid " ) ) ,
Some ( h ) = > {
if h < 0 {
return Err ( Error ::invalid_params (
" Provided negative index ends up with a negative height " ,
) ) ;
}
let h : u32 = h . try_into ( ) . expect ( " Positive i32 always fits in u32 " ) ;
if h > tip_height . 0 {
return Err ( Error ::invalid_params (
" Provided index is greater than the current tip " ,
) ) ;
}
h
}
} ;
Ok ( Height ( sanitized_height ) )
}
}