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.
|
|
|
|
//! So this implementation follows the `lightwalletd` client implementation.
|
|
|
|
|
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-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-03-09 17:12:41 -08:00
|
|
|
use tower::{buffer::Buffer, Service, ServiceExt};
|
2022-02-22 03:26:29 -08:00
|
|
|
|
2022-03-08 01:14:21 -08:00
|
|
|
use zebra_chain::{
|
2022-03-10 21:13:08 -08:00
|
|
|
block::{self, SerializedBlock},
|
2022-03-09 17:12:41 -08:00
|
|
|
serialization::{SerializationError, ZcashDeserialize},
|
2022-03-08 01:14:21 -08:00
|
|
|
transaction::{self, Transaction},
|
|
|
|
};
|
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-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 {
|
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-15 02:13:24 -07:00
|
|
|
/// TODO in the context of https://github.com/ZcashFoundation/zebra/issues/3143:
|
|
|
|
/// - list the arguments and fields that lightwalletd uses
|
|
|
|
/// - note any other lightwalletd changes
|
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-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.
|
|
|
|
///
|
|
|
|
/// 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-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-03-15 02:13:24 -07:00
|
|
|
/// We only expose the `data` field as lightwalletd uses the non-verbose
|
2022-03-09 17:12:41 -08:00
|
|
|
/// mode for all getblock calls: <https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L232>
|
|
|
|
///
|
2022-03-15 02:13:24 -07:00
|
|
|
/// `lightwalletd` only requests blocks by height, so we don't support
|
|
|
|
/// getting blocks by hash but we do need to send the height number as a string.
|
2022-03-09 17:12:41 -08:00
|
|
|
///
|
2022-03-15 02:13:24 -07:00
|
|
|
/// The `verbosity` parameter is ignored but required in the call.
|
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-03-15 02:13:24 -07:00
|
|
|
/// Returns the hash of the current best blockchain tip block, as a [`GetBestBlockHash`] 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")]
|
|
|
|
fn get_best_block_hash(&self) -> BoxFuture<Result<GetBestBlockHash>>;
|
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-02-22 03:26:29 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// RPC method implementations.
|
2022-03-09 17:12:41 -08:00
|
|
|
pub struct RpcImpl<Mempool, State>
|
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<
|
|
|
|
zebra_state::Request,
|
|
|
|
Response = zebra_state::Response,
|
|
|
|
Error = zebra_state::BoxError,
|
|
|
|
>,
|
2022-03-03 23:00:24 -08:00
|
|
|
{
|
2022-02-28 19:32:32 -08:00
|
|
|
/// Zebra's application version.
|
2022-03-03 23:00:24 -08:00
|
|
|
app_version: String,
|
|
|
|
/// A handle to the mempool service.
|
|
|
|
mempool: Buffer<Mempool, mempool::Request>,
|
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-02-28 19:32:32 -08:00
|
|
|
}
|
2022-03-03 23:00:24 -08:00
|
|
|
|
2022-03-09 17:12:41 -08:00
|
|
|
impl<Mempool, State> RpcImpl<Mempool, State>
|
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-11 05:58:22 -08:00
|
|
|
zebra_state::Request,
|
|
|
|
Response = zebra_state::Response,
|
|
|
|
Error = zebra_state::BoxError,
|
|
|
|
>,
|
2022-03-03 23:00:24 -08:00
|
|
|
{
|
|
|
|
/// Create a new instance of the RPC handler.
|
2022-03-09 17:12:41 -08:00
|
|
|
pub fn new(
|
|
|
|
app_version: String,
|
|
|
|
mempool: Buffer<Mempool, mempool::Request>,
|
2022-03-11 05:58:22 -08:00
|
|
|
state: State,
|
2022-03-09 17:12:41 -08:00
|
|
|
) -> Self {
|
2022-03-03 23:00:24 -08:00
|
|
|
RpcImpl {
|
|
|
|
app_version,
|
|
|
|
mempool,
|
2022-03-09 17:12:41 -08:00
|
|
|
state,
|
2022-03-03 23:00:24 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-09 17:12:41 -08:00
|
|
|
impl<Mempool, State> Rpc for RpcImpl<Mempool, State>
|
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<
|
|
|
|
zebra_state::Request,
|
|
|
|
Response = zebra_state::Response,
|
|
|
|
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-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)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_blockchain_info(&self) -> Result<GetBlockChainInfo> {
|
|
|
|
// TODO: dummy output data, fix in the context of #3143
|
|
|
|
let response = GetBlockChainInfo {
|
|
|
|
chain: "TODO: main".to_string(),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
}
|
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();
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
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"
|
|
|
|
);
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
fn get_block(&self, height: String, _verbosity: u8) -> BoxFuture<Result<GetBlock>> {
|
|
|
|
let mut state = self.state.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let height = height.parse().map_err(|error: SerializationError| Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: error.to_string(),
|
|
|
|
data: None,
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let request = zebra_state::Request::Block(zebra_state::HashOrHeight::Height(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::Response::Block(Some(block)) => Ok(GetBlock(block.into())),
|
|
|
|
zebra_state::Response::Block(None) => Err(Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: "Block not found".to_string(),
|
|
|
|
data: None,
|
|
|
|
}),
|
|
|
|
_ => unreachable!("unmatched response to a block request"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
}
|
2022-03-10 21:13:08 -08:00
|
|
|
|
|
|
|
fn get_best_block_hash(&self) -> BoxFuture<Result<GetBestBlockHash>> {
|
|
|
|
let mut state = self.state.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let request = zebra_state::Request::Tip;
|
|
|
|
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::Response::Tip(Some((_height, hash))) => Ok(GetBestBlockHash(hash)),
|
|
|
|
zebra_state::Response::Tip(None) => Err(Error {
|
|
|
|
code: ErrorCode::ServerError(0),
|
|
|
|
message: "No blocks in state".to_string(),
|
|
|
|
data: None,
|
|
|
|
}),
|
|
|
|
_ => unreachable!("unmatched response to a tip request"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
}
|
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) => {
|
|
|
|
Ok(unmined_transaction_ids
|
|
|
|
.iter()
|
|
|
|
.map(|id| id.mined_id().encode_hex())
|
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
_ => unreachable!("unmatched response to a transactionids request"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
}
|
2022-02-22 03:26:29 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
|
|
/// Response to a `getinfo` RPC request.
|
2022-03-15 02:13:24 -07:00
|
|
|
///
|
|
|
|
/// See the notes for the [`Rpc::get_info` method].
|
2022-02-22 03:26:29 -08:00
|
|
|
pub struct GetInfo {
|
|
|
|
build: String,
|
|
|
|
subversion: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
|
|
/// 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-02-22 03:26:29 -08:00
|
|
|
pub struct GetBlockChainInfo {
|
|
|
|
chain: String,
|
|
|
|
// TODO: add other fields used by lightwalletd (#3143)
|
|
|
|
}
|
2022-03-03 23:00:24 -08:00
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
|
|
/// 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-08 01:14:21 -08:00
|
|
|
pub struct SentTransactionHash(#[serde(with = "hex")] transaction::Hash);
|
2022-03-09 17:12:41 -08:00
|
|
|
|
|
|
|
#[derive(serde::Serialize)]
|
|
|
|
/// 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-09 17:12:41 -08:00
|
|
|
pub struct GetBlock(#[serde(with = "hex")] SerializedBlock);
|
2022-03-10 21:13:08 -08:00
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, serde::Serialize)]
|
|
|
|
/// Response to a `getbestblockhash` RPC request.
|
2022-03-15 02:13:24 -07:00
|
|
|
///
|
|
|
|
/// Contains the hex-encoded hash of the tip block.
|
|
|
|
///
|
|
|
|
/// Also see the notes for the [`Rpc::get_best_block_hash` method].
|
2022-03-10 21:13:08 -08:00
|
|
|
pub struct GetBestBlockHash(#[serde(with = "hex")] block::Hash);
|