feat(rpc): add getblockhash rpc method (#4967)
* implement getblockhash rpc method * make fixes * fix some docs * rustfmt * add snapshot test * rename `Hash` to `BestChainBlockHash` * Suggestion for "add getblockhash rpc method" PR (#5428) * Always immediately return errors in get_height_from_int() * Explain why calculations can't overflow * fix for rust feature * fix some warnings * hide state functions behind feature * remove commented assert * renames * rename * fix some warnings * make zebra-rpc rpc features depend on zebra-state rpc features Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
8fcbeab946
commit
8d777c892f
|
@ -10,7 +10,7 @@ edition = "2021"
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
||||||
getblocktemplate-rpcs = []
|
getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }
|
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }
|
||||||
|
|
|
@ -147,11 +147,11 @@ pub trait Rpc {
|
||||||
#[rpc(name = "getblock")]
|
#[rpc(name = "getblock")]
|
||||||
fn get_block(&self, height: String, verbosity: u8) -> BoxFuture<Result<GetBlock>>;
|
fn get_block(&self, height: String, verbosity: u8) -> BoxFuture<Result<GetBlock>>;
|
||||||
|
|
||||||
/// Returns the hash of the current best blockchain tip block, as a [`GetBestBlockHash`] JSON string.
|
/// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
|
||||||
///
|
///
|
||||||
/// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
|
/// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
|
||||||
#[rpc(name = "getbestblockhash")]
|
#[rpc(name = "getbestblockhash")]
|
||||||
fn get_best_block_hash(&self) -> Result<GetBestBlockHash>;
|
fn get_best_block_hash(&self) -> Result<GetBlockHash>;
|
||||||
|
|
||||||
/// Returns all transaction ids in the memory pool, as a JSON array.
|
/// Returns all transaction ids in the memory pool, as a JSON array.
|
||||||
///
|
///
|
||||||
|
@ -610,10 +610,10 @@ where
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_best_block_hash(&self) -> Result<GetBestBlockHash> {
|
fn get_best_block_hash(&self) -> Result<GetBlockHash> {
|
||||||
self.latest_chain_tip
|
self.latest_chain_tip
|
||||||
.best_tip_hash()
|
.best_tip_hash()
|
||||||
.map(GetBestBlockHash)
|
.map(GetBlockHash)
|
||||||
.ok_or(Error {
|
.ok_or(Error {
|
||||||
code: ErrorCode::ServerError(0),
|
code: ErrorCode::ServerError(0),
|
||||||
message: "No blocks in state".to_string(),
|
message: "No blocks in state".to_string(),
|
||||||
|
@ -1141,13 +1141,13 @@ pub enum GetBlock {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response to a `getbestblockhash` RPC request.
|
/// Response to a `getbestblockhash` and `getblockhash` RPC request.
|
||||||
///
|
///
|
||||||
/// Contains the hex-encoded hash of the tip block.
|
/// Contains the hex-encoded hash of the requested block.
|
||||||
///
|
///
|
||||||
/// Also see the notes for the [`Rpc::get_best_block_hash` method].
|
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct GetBestBlockHash(#[serde(with = "hex")] block::Hash);
|
pub struct GetBlockHash(#[serde(with = "hex")] block::Hash);
|
||||||
|
|
||||||
/// Response to a `z_gettreestate` RPC request.
|
/// Response to a `z_gettreestate` RPC request.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
//! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature.
|
//! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature.
|
||||||
use zebra_chain::chain_tip::ChainTip;
|
use zebra_chain::{block::Height, chain_tip::ChainTip};
|
||||||
|
|
||||||
use jsonrpc_core::{self, Error, ErrorCode, Result};
|
use futures::{FutureExt, TryFutureExt};
|
||||||
|
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
|
||||||
use jsonrpc_derive::rpc;
|
use jsonrpc_derive::rpc;
|
||||||
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
|
use crate::methods::{GetBlockHash, MISSING_BLOCK_ERROR_CODE};
|
||||||
|
|
||||||
/// getblocktemplate RPC method signatures.
|
/// getblocktemplate RPC method signatures.
|
||||||
#[rpc(server)]
|
#[rpc(server)]
|
||||||
|
@ -17,31 +21,75 @@ pub trait GetBlockTemplateRpc {
|
||||||
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
|
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
|
||||||
#[rpc(name = "getblockcount")]
|
#[rpc(name = "getblockcount")]
|
||||||
fn get_block_count(&self) -> Result<u32>;
|
fn get_block_count(&self) -> Result<u32>;
|
||||||
|
|
||||||
|
/// Returns the hash of the block of a given height iff the index argument correspond
|
||||||
|
/// to a block in the best chain.
|
||||||
|
///
|
||||||
|
/// zcashd reference: [`getblockhash`](https://zcash-rpc.github.io/getblockhash.html)
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// - `index`: (numeric, required) The block index.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// - If `index` is positive then index = block height.
|
||||||
|
/// - If `index` is negative then -1 is the last known valid block.
|
||||||
|
#[rpc(name = "getblockhash")]
|
||||||
|
fn get_block_hash(&self, index: i32) -> BoxFuture<Result<GetBlockHash>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RPC method implementations.
|
/// RPC method implementations.
|
||||||
pub struct GetBlockTemplateRpcImpl<Tip>
|
pub struct GetBlockTemplateRpcImpl<Tip, State>
|
||||||
where
|
where
|
||||||
Tip: ChainTip,
|
Tip: ChainTip,
|
||||||
|
State: Service<
|
||||||
|
zebra_state::ReadRequest,
|
||||||
|
Response = zebra_state::ReadResponse,
|
||||||
|
Error = zebra_state::BoxError,
|
||||||
|
>,
|
||||||
{
|
{
|
||||||
// TODO: Add the other fields from the [`Rpc`] struct as-needed
|
// TODO: Add the other fields from the [`Rpc`] struct as-needed
|
||||||
/// Allows efficient access to the best tip of the blockchain.
|
/// Allows efficient access to the best tip of the blockchain.
|
||||||
latest_chain_tip: Tip,
|
latest_chain_tip: Tip,
|
||||||
|
|
||||||
|
/// A handle to the state service.
|
||||||
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Tip> GetBlockTemplateRpcImpl<Tip>
|
impl<Tip, State> GetBlockTemplateRpcImpl<Tip, State>
|
||||||
where
|
where
|
||||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||||
|
State: Service<
|
||||||
|
zebra_state::ReadRequest,
|
||||||
|
Response = zebra_state::ReadResponse,
|
||||||
|
Error = zebra_state::BoxError,
|
||||||
|
> + Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
/// Create a new instance of the RPC handler.
|
/// Create a new instance of the RPC handler.
|
||||||
pub fn new(latest_chain_tip: Tip) -> Self {
|
pub fn new(latest_chain_tip: Tip, state: State) -> Self {
|
||||||
Self { latest_chain_tip }
|
Self {
|
||||||
|
latest_chain_tip,
|
||||||
|
state,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Tip> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Tip>
|
impl<Tip, State> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Tip, State>
|
||||||
where
|
where
|
||||||
Tip: ChainTip + Send + Sync + 'static,
|
Tip: ChainTip + Send + Sync + 'static,
|
||||||
|
State: Service<
|
||||||
|
zebra_state::ReadRequest,
|
||||||
|
Response = zebra_state::ReadResponse,
|
||||||
|
Error = zebra_state::BoxError,
|
||||||
|
> + Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
<State as Service<zebra_state::ReadRequest>>::Future: Send,
|
||||||
{
|
{
|
||||||
fn get_block_count(&self) -> Result<u32> {
|
fn get_block_count(&self) -> Result<u32> {
|
||||||
self.latest_chain_tip
|
self.latest_chain_tip
|
||||||
|
@ -53,4 +101,83 @@ where
|
||||||
data: None,
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_block_hash(&self, index: i32) -> BoxFuture<Result<GetBlockHash>> {
|
||||||
|
let mut state = self.state.clone();
|
||||||
|
|
||||||
|
let maybe_tip_height = self.latest_chain_tip.best_tip_height();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let tip_height = maybe_tip_height.ok_or(Error {
|
||||||
|
code: ErrorCode::ServerError(0),
|
||||||
|
message: "No blocks in state".to_string(),
|
||||||
|
data: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let height = get_height_from_int(index, tip_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)) => Ok(GetBlockHash(hash)),
|
||||||
|
zebra_state::ReadResponse::BlockHash(None) => Err(Error {
|
||||||
|
code: MISSING_BLOCK_ERROR_CODE,
|
||||||
|
message: "Block not found".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
_ => unreachable!("unmatched response to a block request"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a potentially negative index, find the corresponding `Height`.
|
||||||
|
///
|
||||||
|
/// This function is used to parse the integer index argument of `get_block_hash`.
|
||||||
|
fn get_height_from_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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
let latest_chain_tip_gbt_clone = latest_chain_tip.clone();
|
let latest_chain_tip_gbt_clone = latest_chain_tip.clone();
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
let read_state_clone = read_state.clone();
|
||||||
|
|
||||||
// Init RPC
|
// Init RPC
|
||||||
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||||
|
@ -104,7 +106,7 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
// `getbestblockhash`
|
// `getbestblockhash`
|
||||||
let get_best_block_hash = rpc
|
let get_best_block_hash = rpc
|
||||||
.get_best_block_hash()
|
.get_best_block_hash()
|
||||||
.expect("We should have a GetBestBlockHash struct");
|
.expect("We should have a GetBlockHash struct");
|
||||||
snapshot_rpc_getbestblockhash(get_best_block_hash, &settings);
|
snapshot_rpc_getbestblockhash(get_best_block_hash, &settings);
|
||||||
|
|
||||||
// `getrawmempool`
|
// `getrawmempool`
|
||||||
|
@ -172,13 +174,23 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
{
|
{
|
||||||
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(latest_chain_tip_gbt_clone);
|
let get_block_template_rpc =
|
||||||
|
GetBlockTemplateRpcImpl::new(latest_chain_tip_gbt_clone, read_state_clone);
|
||||||
|
|
||||||
// `getblockcount`
|
// `getblockcount`
|
||||||
let get_block_count = get_block_template_rpc
|
let get_block_count = get_block_template_rpc
|
||||||
.get_block_count()
|
.get_block_count()
|
||||||
.expect("We should have a number");
|
.expect("We should have a number");
|
||||||
snapshot_rpc_getblockcount(get_block_count, &settings);
|
snapshot_rpc_getblockcount(get_block_count, &settings);
|
||||||
|
|
||||||
|
// `getblockhash`
|
||||||
|
const BLOCK_HEIGHT10: i32 = 10;
|
||||||
|
let get_block_hash = get_block_template_rpc
|
||||||
|
.get_block_hash(BLOCK_HEIGHT10)
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlockHash struct");
|
||||||
|
|
||||||
|
snapshot_rpc_getblockhash(get_block_hash, &settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +251,7 @@ fn snapshot_rpc_getblock_verbose(block: GetBlock, settings: &insta::Settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot `getbestblockhash` response, using `cargo insta` and JSON serialization.
|
/// Snapshot `getbestblockhash` response, using `cargo insta` and JSON serialization.
|
||||||
fn snapshot_rpc_getbestblockhash(tip_hash: GetBestBlockHash, settings: &insta::Settings) {
|
fn snapshot_rpc_getbestblockhash(tip_hash: GetBlockHash, settings: &insta::Settings) {
|
||||||
settings.bind(|| insta::assert_json_snapshot!("get_best_block_hash", tip_hash));
|
settings.bind(|| insta::assert_json_snapshot!("get_best_block_hash", tip_hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +286,12 @@ fn snapshot_rpc_getblockcount(block_count: u32, settings: &insta::Settings) {
|
||||||
settings.bind(|| insta::assert_json_snapshot!("get_block_count", block_count));
|
settings.bind(|| insta::assert_json_snapshot!("get_block_count", block_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Snapshot `getblockhash` response, using `cargo insta` and JSON serialization.
|
||||||
|
fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Settings) {
|
||||||
|
settings.bind(|| insta::assert_json_snapshot!("get_block_hash", block_hash));
|
||||||
|
}
|
||||||
|
|
||||||
/// Utility function to convert a `Network` to a lowercase string.
|
/// Utility function to convert a `Network` to a lowercase string.
|
||||||
fn network_string(network: Network) -> String {
|
fn network_string(network: Network) -> String {
|
||||||
let mut net_suffix = network.to_string();
|
let mut net_suffix = network.to_string();
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
assertion_line: 270
|
||||||
|
expression: block_hash
|
||||||
|
---
|
||||||
|
"00074c46a4aa8172df8ae2ad1848a2e084e1b6989b7d9e6132adc938bf835b36"
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
assertion_line: 270
|
||||||
|
expression: block_hash
|
||||||
|
---
|
||||||
|
"079f4c752729be63e6341ee9bce42fbbe37236aba22e3deb82405f3c2805c112"
|
|
@ -233,7 +233,7 @@ async fn rpc_getbestblockhash() {
|
||||||
// Get the tip hash using RPC method `get_best_block_hash`
|
// Get the tip hash using RPC method `get_best_block_hash`
|
||||||
let get_best_block_hash = rpc
|
let get_best_block_hash = rpc
|
||||||
.get_best_block_hash()
|
.get_best_block_hash()
|
||||||
.expect("We should have a GetBestBlockHash struct");
|
.expect("We should have a GetBlockHash struct");
|
||||||
let response_hash = get_best_block_hash.0;
|
let response_hash = get_best_block_hash.0;
|
||||||
|
|
||||||
// Check if response is equal to block 10 hash.
|
// Check if response is equal to block 10 hash.
|
||||||
|
@ -641,13 +641,13 @@ async fn rpc_getblockcount() {
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state.clone(),
|
||||||
latest_chain_tip.clone(),
|
latest_chain_tip.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Init RPC
|
// Init RPC
|
||||||
let get_block_template_rpc =
|
let get_block_template_rpc =
|
||||||
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
|
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state);
|
||||||
|
|
||||||
// Get the tip height using RPC method `get_block_count`
|
// Get the tip height using RPC method `get_block_count`
|
||||||
let get_block_count = get_block_template_rpc
|
let get_block_count = get_block_template_rpc
|
||||||
|
@ -681,12 +681,12 @@ async fn rpc_getblockcount_empty_state() {
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state.clone(),
|
||||||
latest_chain_tip.clone(),
|
latest_chain_tip.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_block_template_rpc =
|
let get_block_template_rpc =
|
||||||
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
|
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state);
|
||||||
|
|
||||||
// Get the tip height using RPC method `get_block_count
|
// Get the tip height using RPC method `get_block_count
|
||||||
let get_block_count = get_block_template_rpc.get_block_count();
|
let get_block_count = get_block_template_rpc.get_block_count();
|
||||||
|
@ -703,3 +703,57 @@ async fn rpc_getblockcount_empty_state() {
|
||||||
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
||||||
assert!(matches!(rpc_tx_queue_task_result, None));
|
assert!(matches!(rpc_tx_queue_task_result, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn rpc_getblockhash() {
|
||||||
|
let _init_guard = zebra_test::init();
|
||||||
|
|
||||||
|
// Create a continuous chain of mainnet blocks from genesis
|
||||||
|
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
|
||||||
|
.iter()
|
||||||
|
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||||
|
// Create a populated state service
|
||||||
|
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
|
||||||
|
zebra_state::populated_state(blocks.clone(), Mainnet).await;
|
||||||
|
|
||||||
|
// Init RPCs
|
||||||
|
let _rpc = RpcImpl::new(
|
||||||
|
"RPC test",
|
||||||
|
Mainnet,
|
||||||
|
false,
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(read_state.clone(), 1),
|
||||||
|
latest_chain_tip.clone(),
|
||||||
|
);
|
||||||
|
let get_block_template_rpc =
|
||||||
|
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip, read_state);
|
||||||
|
|
||||||
|
// Query the hashes using positive indexes
|
||||||
|
for (i, block) in blocks.iter().enumerate() {
|
||||||
|
let get_block_hash = get_block_template_rpc
|
||||||
|
.get_block_hash(i.try_into().expect("usize always fits in i32"))
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlockHash struct");
|
||||||
|
|
||||||
|
assert_eq!(get_block_hash, GetBlockHash(block.clone().hash()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the hashes using negative indexes
|
||||||
|
for i in (-10..=-1).rev() {
|
||||||
|
let get_block_hash = get_block_template_rpc
|
||||||
|
.get_block_hash(i)
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlockHash struct");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_block_hash,
|
||||||
|
GetBlockHash(blocks[(10 + (i + 1)) as usize].hash())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mempool.expect_no_requests().await;
|
||||||
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ impl RpcServer {
|
||||||
{
|
{
|
||||||
// Initialize the getblocktemplate rpc methods
|
// Initialize the getblocktemplate rpc methods
|
||||||
let get_block_template_rpc_impl =
|
let get_block_template_rpc_impl =
|
||||||
GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
|
GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), state.clone());
|
||||||
|
|
||||||
io.extend_with(get_block_template_rpc_impl.to_delegate());
|
io.extend_with(get_block_template_rpc_impl.to_delegate());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
proptest-impl = ["proptest", "proptest-derive", "zebra-test", "zebra-chain/proptest-impl"]
|
proptest-impl = ["proptest", "proptest-derive", "zebra-test", "zebra-chain/proptest-impl"]
|
||||||
|
getblocktemplate-rpcs = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
|
|
|
@ -731,6 +731,15 @@ pub enum ReadRequest {
|
||||||
///
|
///
|
||||||
/// Returns a type with found utxos and transaction information.
|
/// Returns a type with found utxos and transaction information.
|
||||||
UtxosByAddresses(HashSet<transparent::Address>),
|
UtxosByAddresses(HashSet<transparent::Address>),
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Looks up a block hash by height in the current best chain.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// * [`ReadResponse::BlockHash(Some(hash))`](ReadResponse::BlockHash) if the block is in the best chain;
|
||||||
|
/// * [`ReadResponse::BlockHash(None)`](ReadResponse::BlockHash) otherwise.
|
||||||
|
BestChainBlockHash(block::Height),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadRequest {
|
impl ReadRequest {
|
||||||
|
@ -751,6 +760,8 @@ impl ReadRequest {
|
||||||
ReadRequest::AddressBalance { .. } => "address_balance",
|
ReadRequest::AddressBalance { .. } => "address_balance",
|
||||||
ReadRequest::TransactionIdsByAddresses { .. } => "transaction_ids_by_addesses",
|
ReadRequest::TransactionIdsByAddresses { .. } => "transaction_ids_by_addesses",
|
||||||
ReadRequest::UtxosByAddresses(_) => "utxos_by_addesses",
|
ReadRequest::UtxosByAddresses(_) => "utxos_by_addesses",
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,11 @@ pub enum ReadResponse {
|
||||||
|
|
||||||
/// Response to [`ReadRequest::UtxosByAddresses`] with found utxos and transaction data.
|
/// Response to [`ReadRequest::UtxosByAddresses`] with found utxos and transaction data.
|
||||||
AddressUtxos(AddressUtxos),
|
AddressUtxos(AddressUtxos),
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Response to [`ReadRequest::BestChainBlockHash`](crate::ReadRequest::BestChainBlockHash) with the
|
||||||
|
/// specified block hash.
|
||||||
|
BlockHash(Option<block::Hash>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Conversion from read-only [`ReadResponse`]s to read-write [`Response`]s.
|
/// Conversion from read-only [`ReadResponse`]s to read-write [`Response`]s.
|
||||||
|
@ -144,6 +149,11 @@ impl TryFrom<ReadResponse> for Response {
|
||||||
| ReadResponse::AddressUtxos(_) => {
|
| ReadResponse::AddressUtxos(_) => {
|
||||||
Err("there is no corresponding Response for this ReadResponse")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadResponse::BlockHash(_) => {
|
||||||
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1510,6 +1510,42 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.map(|join_result| join_result.expect("panic in ReadRequest::UtxosByAddresses"))
|
.map(|join_result| join_result.expect("panic in ReadRequest::UtxosByAddresses"))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by get_block_hash RPC.
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::BestChainBlockHash(height) => {
|
||||||
|
metrics::counter!(
|
||||||
|
"state.requests",
|
||||||
|
1,
|
||||||
|
"service" => "read_state",
|
||||||
|
"type" => "best_chain_block_hash",
|
||||||
|
);
|
||||||
|
|
||||||
|
let timer = CodeTimer::start();
|
||||||
|
|
||||||
|
let state = self.clone();
|
||||||
|
|
||||||
|
// # Performance
|
||||||
|
//
|
||||||
|
// Allow other async tasks to make progress while concurrently reading blocks from disk.
|
||||||
|
let span = Span::current();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
span.in_scope(move || {
|
||||||
|
let hash = state.non_finalized_state_receiver.with_watch_data(
|
||||||
|
|non_finalized_state| {
|
||||||
|
read::hash(non_finalized_state.best_chain(), &state.db, height)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The work is done in the future.
|
||||||
|
timer.finish(module_path!(), line!(), "ReadRequest::BestChainBlockHash");
|
||||||
|
|
||||||
|
Ok(ReadResponse::BlockHash(hash))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|join_result| join_result.expect("panic in ReadRequest::BestChainBlockHash"))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,12 @@ pub use address::{
|
||||||
tx_id::transparent_tx_ids,
|
tx_id::transparent_tx_ids,
|
||||||
utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE},
|
utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use block::{any_utxo, block, block_header, transaction, transaction_hashes_for_block, utxo};
|
pub use block::{any_utxo, block, block_header, transaction, transaction_hashes_for_block, utxo};
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
pub use block::hash;
|
||||||
|
|
||||||
pub use find::{
|
pub use find::{
|
||||||
best_tip, block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
best_tip, block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
||||||
hash_by_height, height_by_hash, tip, tip_height,
|
hash_by_height, height_by_hash, tip, tip_height,
|
||||||
|
|
|
@ -167,3 +167,25 @@ pub fn any_utxo(
|
||||||
.any_utxo(&outpoint)
|
.any_utxo(&outpoint)
|
||||||
.or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
|
.or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Returns the [`Hash`] given [`block::Height`](zebra_chain::block::Height), if it exists in
|
||||||
|
/// the non-finalized `chain` or finalized `db`.
|
||||||
|
pub fn hash<C>(chain: Option<C>, db: &ZebraDb, height: Height) -> Option<zebra_chain::block::Hash>
|
||||||
|
where
|
||||||
|
C: AsRef<Chain>,
|
||||||
|
{
|
||||||
|
// # Correctness
|
||||||
|
//
|
||||||
|
// The StateService commits blocks to the finalized state before updating
|
||||||
|
// the latest chain, and it can commit additional blocks after we've cloned
|
||||||
|
// this `chain` variable.
|
||||||
|
//
|
||||||
|
// Since blocks are the same in the finalized and non-finalized state, we
|
||||||
|
// check the most efficient alternative first. (`chain` is always in memory,
|
||||||
|
// but `db` stores blocks on disk, with a memory cache.)
|
||||||
|
chain
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|chain| chain.as_ref().hash_by_height(height))
|
||||||
|
.or_else(|| db.hash(height))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue