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
|
|
|
|
2022-03-24 02:45:37 -07:00
|
|
|
use std::{collections::HashSet, io, sync::Arc};
|
|
|
|
|
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-04-11 22:06:29 -07:00
|
|
|
use tokio::{sync::broadcast::Sender, task::JoinHandle};
|
2022-03-09 17:12:41 -08:00
|
|
|
use tower::{buffer::Buffer, 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},
|
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-02-28 19:32:32 -08:00
|
|
|
use zebra_network::constants::USER_AGENT;
|
2022-03-03 23:00:24 -08:00
|
|
|
use zebra_node_services::{mempool, BoxError};
|
2022-05-11 14:43:17 -07:00
|
|
|
use zebra_state::{OutputIndex, OutputLocation, TransactionLocation};
|
2022-02-28 19:32:32 -08:00
|
|
|
|
2022-04-11 22:06:29 -07:00
|
|
|
use crate::queue::Queue;
|
|
|
|
|
2022-10-19 05:01:13 -07:00
|
|
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
2022-10-25 19:52:29 -07:00
|
|
|
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-03-30 16:34:52 -07:00
|
|
|
/// The RPC error code used by `zcashd` for missing blocks.
|
|
|
|
///
|
|
|
|
/// `lightwalletd` expects error code `-8` when a block is not found:
|
|
|
|
/// <https://github.com/adityapk00/lightwalletd/blob/c1bab818a683e4de69cd952317000f9bb2932274/common/common.go#L251-L254>
|
|
|
|
pub const MISSING_BLOCK_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-8);
|
|
|
|
|
2022-02-22 03:26:29 -08:00
|
|
|
#[rpc(server)]
|
|
|
|
/// RPC method signatures.
|
|
|
|
pub trait Rpc {
|
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)
|
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
|
|
|
#[rpc(name = "getinfo")]
|
|
|
|
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)
|
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)
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
|
|
|
/// - `address_strings`: (map) A JSON map with a single entry
|
|
|
|
/// - `addresses`: (array of strings) A list of base-58 encoded addresses.
|
|
|
|
///
|
|
|
|
/// # 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)
|
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
|
|
|
///
|
2022-03-15 02:13:24 -07:00
|
|
|
/// - `raw_transaction_hex`: (string, required) 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
|
|
|
|
2022-03-15 02:13:24 -07:00
|
|
|
/// Returns the requested block by 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)
|
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
|
|
|
///
|
2022-03-15 02:13:24 -07:00
|
|
|
/// - `height`: (string, required) The height number for the block to be returned.
|
2022-05-27 02:41:11 -07:00
|
|
|
/// - `verbosity`: (numeric, optional, default=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),
|
|
|
|
/// so we only return that for now.
|
2022-03-09 17:12:41 -08:00
|
|
|
///
|
2022-03-15 02:13:24 -07:00
|
|
|
/// `lightwalletd` only requests blocks by height, so we don't support
|
2022-03-30 16:34:52 -07:00
|
|
|
/// getting blocks by hash. (But we parse the height as a JSON string, not an integer).
|
2022-05-27 02:41:11 -07:00
|
|
|
/// `lightwalletd` also does not use verbosity=2, so we don't support it.
|
2022-03-09 17:12:41 -08:00
|
|
|
#[rpc(name = "getblock")]
|
|
|
|
fn get_block(&self, height: String, verbosity: 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)
|
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)
|
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)
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
|
|
|
/// - `hash | height`: (string, required) The block hash or height.
|
|
|
|
///
|
|
|
|
/// # 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>>;
|
|
|
|
|
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)
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
|
|
|
/// - `txid`: (string, required) The transaction ID of the transaction to be returned.
|
|
|
|
/// - `verbose`: (numeric, optional, default=0) If 0, return a string of hex-encoded data, otherwise return a JSON object.
|
|
|
|
///
|
|
|
|
/// # 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,
|
|
|
|
verbose: u8,
|
|
|
|
) -> 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)
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
2022-05-04 18:08:27 -07:00
|
|
|
/// A [`GetAddressTxIdsRequest`] struct with the following named fields:
|
2022-04-13 01:48:13 -07:00
|
|
|
/// - `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).
|
|
|
|
///
|
|
|
|
/// # 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)
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
|
|
|
/// - `addresses`: (json array of string, required) The addresses to get outputs from.
|
|
|
|
///
|
|
|
|
/// # 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.
|
2022-03-15 15:29:15 -07:00
|
|
|
pub struct RpcImpl<Mempool, State, Tip>
|
2022-03-03 23:00:24 -08:00
|
|
|
where
|
2022-03-09 17:12:41 -08:00
|
|
|
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
|
|
|
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-15 15:29:15 -07:00
|
|
|
Tip: ChainTip,
|
2022-03-03 23:00:24 -08:00
|
|
|
{
|
2022-09-06 06:32:33 -07:00
|
|
|
// Configuration
|
|
|
|
//
|
2022-02-28 19:32:32 -08:00
|
|
|
/// Zebra's application version.
|
2022-03-03 23:00:24 -08:00
|
|
|
app_version: 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,
|
|
|
|
|
|
|
|
// Services
|
|
|
|
//
|
2022-03-03 23:00:24 -08:00
|
|
|
/// A handle to the mempool service.
|
|
|
|
mempool: Buffer<Mempool, mempool::Request>,
|
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-04-11 22:06:29 -07:00
|
|
|
/// A sender component of a channel used to send transactions to the queue.
|
|
|
|
queue_sender: Sender<Option<UnminedTx>>,
|
2022-02-28 19:32:32 -08:00
|
|
|
}
|
2022-03-03 23:00:24 -08:00
|
|
|
|
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-04-11 22:06:29 -07:00
|
|
|
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
|
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,
|
|
|
|
Tip: ChainTip + Clone + Send + Sync + 'static,
|
2022-03-03 23:00:24 -08:00
|
|
|
{
|
|
|
|
/// Create a new instance of the RPC handler.
|
2022-03-15 15:29:15 -07:00
|
|
|
pub fn new<Version>(
|
|
|
|
app_version: Version,
|
2022-09-06 06:32:33 -07:00
|
|
|
network: Network,
|
|
|
|
debug_force_finished_sync: bool,
|
2022-03-09 17:12:41 -08:00
|
|
|
mempool: Buffer<Mempool, mempool::Request>,
|
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
|
|
|
|
Version: ToString,
|
2022-04-11 22:06:29 -07:00
|
|
|
<Mempool as Service<mempool::Request>>::Future: Send,
|
|
|
|
<State as Service<zebra_state::ReadRequest>>::Future: Send,
|
2022-03-15 15:29:15 -07:00
|
|
|
{
|
2022-04-11 22:06:29 -07:00
|
|
|
let runner = Queue::start();
|
|
|
|
|
2022-04-28 03:19:02 -07:00
|
|
|
let mut app_version = app_version.to_string();
|
|
|
|
|
|
|
|
// Match zcashd's version format, if the version string has anything in it
|
|
|
|
if !app_version.is_empty() && !app_version.starts_with('v') {
|
|
|
|
app_version.insert(0, 'v');
|
|
|
|
}
|
|
|
|
|
2022-04-11 22:06:29 -07:00
|
|
|
let rpc_impl = RpcImpl {
|
2022-04-28 03:19:02 -07:00
|
|
|
app_version,
|
2022-09-06 06:32:33 -07:00
|
|
|
network,
|
|
|
|
debug_force_finished_sync,
|
2022-04-11 22:06:29 -07:00
|
|
|
mempool: mempool.clone(),
|
|
|
|
state: state.clone(),
|
|
|
|
latest_chain_tip: latest_chain_tip.clone(),
|
|
|
|
queue_sender: runner.sender(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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
|
|
|
|
Mempool:
|
|
|
|
tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
|
|
|
|
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-03-15 15:29:15 -07:00
|
|
|
Tip: ChainTip + 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 {
|
2022-02-28 19:32:32 -08:00
|
|
|
build: self.app_version.clone(),
|
|
|
|
subversion: USER_AGENT.into(),
|
2022-02-22 03:26:29 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
for (activation_height, network_upgrade) in NetworkUpgrade::activation_list(network) {
|
|
|
|
// 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-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-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());
|
|
|
|
let _ = queue_sender.send(Some(unmined_transaction));
|
|
|
|
|
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-05-27 02:41:11 -07:00
|
|
|
fn get_block(&self, height: String, verbosity: u8) -> BoxFuture<Result<GetBlock>> {
|
2022-03-09 17:12:41 -08:00
|
|
|
let mut state = self.state.clone();
|
|
|
|
|
|
|
|
async move {
|
2022-10-02 16:34:44 -07:00
|
|
|
let height: Height = height.parse().map_err(|error: SerializationError| Error {
|
2022-03-09 17:12:41 -08:00
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: error.to_string(),
|
|
|
|
data: None,
|
|
|
|
})?;
|
|
|
|
|
2022-10-02 16:34:44 -07:00
|
|
|
if verbosity == 0 {
|
|
|
|
let request = zebra_state::ReadRequest::Block(height.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,
|
|
|
|
})?;
|
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 {
|
|
|
|
let request = zebra_state::ReadRequest::TransactionIdsForBlock(height.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,
|
|
|
|
})?;
|
|
|
|
|
|
|
|
match response {
|
|
|
|
zebra_state::ReadResponse::TransactionIdsForBlock(Some(tx_ids)) => {
|
|
|
|
let tx_ids = tx_ids.iter().map(|tx_id| tx_id.encode_hex()).collect();
|
|
|
|
Ok(GetBlock::Object { tx: tx_ids })
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
}),
|
2022-10-02 16:34:44 -07:00
|
|
|
_ => unreachable!("unmatched response to a transaction_ids_for_block request"),
|
|
|
|
}
|
|
|
|
} 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-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
|
|
|
|
|
|
|
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>> {
|
|
|
|
let mut mempool = self.mempool.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let request = mempool::Request::TransactionIds;
|
|
|
|
|
|
|
|
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::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.
|
|
|
|
// (zcashd's sort order appears arbitrary.)
|
|
|
|
tx_ids.sort();
|
|
|
|
|
|
|
|
Ok(tx_ids)
|
2022-03-12 08:51:49 -08:00
|
|
|
}
|
|
|
|
_ => unreachable!("unmatched response to a transactionids request"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
}
|
2022-03-24 02:45:37 -07:00
|
|
|
|
|
|
|
fn get_raw_transaction(
|
|
|
|
&self,
|
|
|
|
txid_hex: String,
|
|
|
|
verbose: u8,
|
|
|
|
) -> BoxFuture<Result<GetRawTransaction>> {
|
|
|
|
let mut state = self.state.clone();
|
|
|
|
let mut mempool = self.mempool.clone();
|
|
|
|
|
|
|
|
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();
|
|
|
|
return GetRawTransaction::from_transaction(tx, None, verbose != 0)
|
|
|
|
.map_err(|error| Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: error.to_string(),
|
|
|
|
data: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => 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 {
|
|
|
|
zebra_state::ReadResponse::Transaction(Some((tx, height))) => Ok(
|
|
|
|
GetRawTransaction::from_transaction(tx, Some(height), verbose != 0).map_err(
|
|
|
|
|error| Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: error.to_string(),
|
|
|
|
data: None,
|
|
|
|
},
|
|
|
|
)?,
|
|
|
|
),
|
|
|
|
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-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 {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
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-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
|
|
|
|
|
|
|
let chain_height = self.latest_chain_tip.best_tip_height().ok_or(Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: "No blocks in state".to_string(),
|
|
|
|
data: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
async move {
|
|
|
|
// height range checks
|
|
|
|
check_height_range(start, end, chain_height?)?;
|
|
|
|
|
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
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
|
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| {
|
|
|
|
Error::invalid_params(&format!("invalid address {address:?}: {error}"))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.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
|
|
|
///
|
|
|
|
/// 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 {
|
2022-10-02 16:34:44 -07:00
|
|
|
/// List of transaction IDs in block order, hex-encoded.
|
2022-05-27 02:41:11 -07:00
|
|
|
tx: Vec<String>,
|
|
|
|
},
|
|
|
|
}
|
2022-03-10 21:13:08 -08:00
|
|
|
|
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)]
|
2022-10-20 23:01:29 -07:00
|
|
|
pub struct GetBlockHash(#[serde(with = "hex")] block::Hash);
|
2022-03-24 02:45:37 -07:00
|
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
/// The height of the block that contains the transaction, or -1 if
|
|
|
|
/// not applicable.
|
|
|
|
height: i32,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
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>,
|
|
|
|
verbose: bool,
|
|
|
|
) -> std::result::Result<Self, io::Error> {
|
|
|
|
if verbose {
|
|
|
|
Ok(GetRawTransaction::Object {
|
|
|
|
hex: tx.into(),
|
|
|
|
height: match height {
|
|
|
|
Some(height) => height
|
|
|
|
.0
|
|
|
|
.try_into()
|
|
|
|
.expect("valid block heights are limited to i32::MAX"),
|
|
|
|
None => -1,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Ok(GetRawTransaction::Raw(tx.into()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-13 01:48:13 -07:00
|
|
|
|
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(())
|
|
|
|
}
|