Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): add getblockhash rpc method #4967

Merged
merged 19 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
16 changes: 8 additions & 8 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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.
///
Expand Down
141 changes: 134 additions & 7 deletions zebra-rpc/src/methods/get_block_template.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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
Expand All @@ -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))
}
}
24 changes: 21 additions & 3 deletions zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
assertion_line: 270
expression: block_hash
---
"00074c46a4aa8172df8ae2ad1848a2e084e1b6989b7d9e6132adc938bf835b36"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
assertion_line: 270
expression: block_hash
---
"079f4c752729be63e6341ee9bce42fbbe37236aba22e3deb82405f3c2805c112"
Loading