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]
|
||||
default = []
|
||||
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
||||
getblocktemplate-rpcs = []
|
||||
getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }
|
||||
|
|
|
@ -147,11 +147,11 @@ pub trait Rpc {
|
|||
#[rpc(name = "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)
|
||||
#[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.
|
||||
///
|
||||
|
@ -610,10 +610,10 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn get_best_block_hash(&self) -> Result<GetBestBlockHash> {
|
||||
fn get_best_block_hash(&self) -> Result<GetBlockHash> {
|
||||
self.latest_chain_tip
|
||||
.best_tip_hash()
|
||||
.map(GetBestBlockHash)
|
||||
.map(GetBlockHash)
|
||||
.ok_or(Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
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)]
|
||||
pub struct GetBestBlockHash(#[serde(with = "hex")] block::Hash);
|
||||
pub struct GetBlockHash(#[serde(with = "hex")] block::Hash);
|
||||
|
||||
/// Response to a `z_gettreestate` RPC request.
|
||||
///
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
//! 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 tower::{Service, ServiceExt};
|
||||
|
||||
use crate::methods::{GetBlockHash, MISSING_BLOCK_ERROR_CODE};
|
||||
|
||||
/// getblocktemplate RPC method signatures.
|
||||
#[rpc(server)]
|
||||
|
@ -17,31 +21,75 @@ pub trait GetBlockTemplateRpc {
|
|||
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
|
||||
#[rpc(name = "getblockcount")]
|
||||
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.
|
||||
pub struct GetBlockTemplateRpcImpl<Tip>
|
||||
pub struct GetBlockTemplateRpcImpl<Tip, State>
|
||||
where
|
||||
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
|
||||
/// Allows efficient access to the best tip of the blockchain.
|
||||
latest_chain_tip: Tip,
|
||||
|
||||
/// A handle to the state service.
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<Tip> GetBlockTemplateRpcImpl<Tip>
|
||||
impl<Tip, State> GetBlockTemplateRpcImpl<Tip, State>
|
||||
where
|
||||
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.
|
||||
pub fn new(latest_chain_tip: Tip) -> Self {
|
||||
Self { latest_chain_tip }
|
||||
pub fn new(latest_chain_tip: Tip, state: State) -> Self {
|
||||
Self {
|
||||
latest_chain_tip,
|
||||
state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tip> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Tip>
|
||||
impl<Tip, State> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Tip, State>
|
||||
where
|
||||
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> {
|
||||
self.latest_chain_tip
|
||||
|
@ -53,4 +101,83 @@ where
|
|||
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")]
|
||||
let latest_chain_tip_gbt_clone = latest_chain_tip.clone();
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
let read_state_clone = read_state.clone();
|
||||
|
||||
// Init RPC
|
||||
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
|
@ -104,7 +106,7 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
|||
// `getbestblockhash`
|
||||
let get_best_block_hash = rpc
|
||||
.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);
|
||||
|
||||
// `getrawmempool`
|
||||
|
@ -172,13 +174,23 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
|||
|
||||
#[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`
|
||||
let get_block_count = get_block_template_rpc
|
||||
.get_block_count()
|
||||
.expect("We should have a number");
|
||||
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.
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
#[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.
|
||||
fn network_string(network: Network) -> 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`
|
||||
let get_best_block_hash = rpc
|
||||
.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;
|
||||
|
||||
// Check if response is equal to block 10 hash.
|
||||
|
@ -641,13 +641,13 @@ async fn rpc_getblockcount() {
|
|||
Mainnet,
|
||||
false,
|
||||
Buffer::new(mempool.clone(), 1),
|
||||
read_state,
|
||||
read_state.clone(),
|
||||
latest_chain_tip.clone(),
|
||||
);
|
||||
|
||||
// Init 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`
|
||||
let get_block_count = get_block_template_rpc
|
||||
|
@ -681,12 +681,12 @@ async fn rpc_getblockcount_empty_state() {
|
|||
Mainnet,
|
||||
false,
|
||||
Buffer::new(mempool.clone(), 1),
|
||||
read_state,
|
||||
read_state.clone(),
|
||||
latest_chain_tip.clone(),
|
||||
);
|
||||
|
||||
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
|
||||
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();
|
||||
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
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[features]
|
||||
proptest-impl = ["proptest", "proptest-derive", "zebra-test", "zebra-chain/proptest-impl"]
|
||||
getblocktemplate-rpcs = []
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
|
|
|
@ -731,6 +731,15 @@ pub enum ReadRequest {
|
|||
///
|
||||
/// Returns a type with found utxos and transaction information.
|
||||
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 {
|
||||
|
@ -751,6 +760,8 @@ impl ReadRequest {
|
|||
ReadRequest::AddressBalance { .. } => "address_balance",
|
||||
ReadRequest::TransactionIdsByAddresses { .. } => "transaction_ids_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.
|
||||
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.
|
||||
|
@ -144,6 +149,11 @@ impl TryFrom<ReadResponse> for Response {
|
|||
| ReadResponse::AddressUtxos(_) => {
|
||||
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"))
|
||||
.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,
|
||||
utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE},
|
||||
};
|
||||
|
||||
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::{
|
||||
best_tip, block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
||||
hash_by_height, height_by_hash, tip, tip_height,
|
||||
|
|
|
@ -167,3 +167,25 @@ pub fn any_utxo(
|
|||
.any_utxo(&outpoint)
|
||||
.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