feature(rpc): Implement `getbestblockhash` method (#3754)
* feature(rpc): start adding a `getblock` method * fix(rpc): replace oneshot * fix(rpc): replace a panic with error * fix(rpc): fix test * feature(rpc): add hex to response * refactor(rpc): use generic instead of alias * docs(rpc): improve docs for getblock method * test(rpc): add a test for getblock method * deps(rpc): remove non needed tower features Co-authored-by: teor <teor@riseup.net> * docs(rpc): add a note to getblock doc * refactor(rpc): replace alias * fix(rpc): use `zcash_serialize_to_vec()` instead of logging format * tests(rpc): add network argument to `populated_state()` * refactor(rpc): use an error for state service readiness * fix(rpc): add parameter * fix(rpc): clippy * nit(rpc): remove new line from imports * fix(rpc): remove commented code * fix(rpc): simplify error Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use a `SerializedBlock` type to help serializing blocks (#3725) * Create a `SerializedBlock` helper type Create a type that can be used as a byte slice, but is guaranteed to represent a valid block. * Use `into_iter` instead of `iter` There's no need to borrow the elements, they can be moved out directly. This will be necessary because `&Arc<T>` doesn't implement `Borrow<T>`, so a `SerializedBlock` can't be built directly from an `&Arc<Block>`. * Use `SerializedBlock` in `GetBlock` Make the type stricter to avoid storing possibly invalid values. The bytes are still serialized as a hexadecimal string, through the usage of `hex`. The `serde::Deserialize` can't be derived because `hex` requires the type to also implement `FromHex`. * feature(rpc): add suggestions from code review Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * tests(rpc): make sure mempool has no requests in get_block test * fix(rpc): change height argument type in getblock method * fix(rpc): rustfmt * fix(rpc): replace panic * fix(rpc): change getblock response * fix(rpc): fix lightwalletd test * tests(rpc): add a getblock error test * fix(rpc): try another regex * feature(rpc): add `getbestblockhash` RPC method * feature(rpc): Add a `pub struct SerializedBlockHash` type * tests(rpc): add a unit test for `getbestblockhash` method * tests(rpc): make sure no requests are sent to mempool in getbestblockhash test * tests(rpc): refactor check Co-authored-by: teor <teor@riseup.net> * fix(rpc): fixes after rebase * refactor(rpc): refactor `GetBestBlockHash` * fix(rpc): unused variables Co-authored-by: teor <teor@riseup.net> * docs(rpc): update * fix(rpc): add panic * fix(rpc): fix panic Co-authored-by: teor <teor@riseup.net> Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
This commit is contained in:
parent
9862f6e5cf
commit
f39ac48c59
|
@ -1,5 +1,7 @@
|
|||
use std::{fmt, io};
|
||||
|
||||
use hex::{FromHex, ToHex};
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use proptest_derive::Arbitrary;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -22,24 +24,68 @@ use super::Header;
|
|||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
|
||||
pub struct Hash(pub [u8; 32]);
|
||||
|
||||
impl fmt::Display for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl Hash {
|
||||
/// Return the hash bytes in big-endian byte-order suitable for printing out byte by byte.
|
||||
///
|
||||
/// Zebra displays transaction and block hashes in big-endian byte-order,
|
||||
/// following the u256 convention set by Bitcoin and zcashd.
|
||||
fn bytes_in_display_order(&self) -> [u8; 32] {
|
||||
let mut reversed_bytes = self.0;
|
||||
reversed_bytes.reverse();
|
||||
f.write_str(&hex::encode(&reversed_bytes))
|
||||
reversed_bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.encode_hex::<String>())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut reversed_bytes = self.0;
|
||||
reversed_bytes.reverse();
|
||||
f.debug_tuple("block::Hash")
|
||||
.field(&hex::encode(&reversed_bytes))
|
||||
.field(&self.encode_hex::<String>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHex for &Hash {
|
||||
fn encode_hex<T: FromIterator<char>>(&self) -> T {
|
||||
self.bytes_in_display_order().encode_hex()
|
||||
}
|
||||
|
||||
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
|
||||
self.bytes_in_display_order().encode_hex_upper()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHex for Hash {
|
||||
fn encode_hex<T: FromIterator<char>>(&self) -> T {
|
||||
(&self).encode_hex()
|
||||
}
|
||||
|
||||
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
|
||||
(&self).encode_hex_upper()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromHex for Hash {
|
||||
type Error = <[u8; 32] as FromHex>::Error;
|
||||
|
||||
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||
let hash = <[u8; 32]>::from_hex(hex)?;
|
||||
|
||||
Ok(hash.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for Hash {
|
||||
fn from(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Header> for Hash {
|
||||
fn from(block_header: &'a Header) -> Self {
|
||||
let mut hash_writer = sha256d::Writer::default();
|
||||
|
|
|
@ -13,7 +13,7 @@ use jsonrpc_derive::rpc;
|
|||
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||
|
||||
use zebra_chain::{
|
||||
block::SerializedBlock,
|
||||
block::{self, SerializedBlock},
|
||||
serialization::{SerializationError, ZcashDeserialize},
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
|
@ -80,10 +80,7 @@ pub trait Rpc {
|
|||
///
|
||||
/// zcashd reference: <https://zcash.github.io/rpc/getblock.html>
|
||||
///
|
||||
/// Result:
|
||||
/// {
|
||||
/// "data": String, // The block encoded as hex
|
||||
/// }
|
||||
/// Result: [`GetBlock`]
|
||||
///
|
||||
/// Note 1: We only expose the `data` field as lightwalletd uses the non-verbose
|
||||
/// mode for all getblock calls: <https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L232>
|
||||
|
@ -94,6 +91,17 @@ pub trait Rpc {
|
|||
/// Note 3: The `verbosity` parameter is ignored but required in the call.
|
||||
#[rpc(name = "getblock")]
|
||||
fn get_block(&self, height: String, verbosity: u8) -> BoxFuture<Result<GetBlock>>;
|
||||
|
||||
/// getbestblockhash
|
||||
///
|
||||
/// Returns the hash of the current best blockchain tip block.
|
||||
///
|
||||
/// zcashd reference: <https://zcash.github.io/rpc/getbestblockhash.html>
|
||||
///
|
||||
/// Result: [`GetBestBlockHash`]
|
||||
///
|
||||
#[rpc(name = "getbestblockhash")]
|
||||
fn get_best_block_hash(&self) -> BoxFuture<Result<GetBestBlockHash>>;
|
||||
}
|
||||
|
||||
/// RPC method implementations.
|
||||
|
@ -248,6 +256,34 @@ where
|
|||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
|
@ -273,3 +309,7 @@ pub struct SentTransactionHash(#[serde(with = "hex")] transaction::Hash);
|
|||
#[derive(serde::Serialize)]
|
||||
/// Response to a `getblock` RPC request.
|
||||
pub struct GetBlock(#[serde(with = "hex")] SerializedBlock);
|
||||
|
||||
#[derive(Debug, PartialEq, serde::Serialize)]
|
||||
/// Response to a `getbestblockhash` RPC request.
|
||||
pub struct GetBestBlockHash(#[serde(with = "hex")] block::Hash);
|
||||
|
|
|
@ -11,6 +11,9 @@ use zebra_test::mock_service::MockService;
|
|||
|
||||
use super::super::*;
|
||||
|
||||
// Number of blocks to populate state with
|
||||
const NUMBER_OF_BLOCKS: u32 = 10;
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_getinfo() {
|
||||
zebra_test::init();
|
||||
|
@ -42,9 +45,6 @@ async fn rpc_getinfo() {
|
|||
async fn rpc_getblock() {
|
||||
zebra_test::init();
|
||||
|
||||
// Number of blocks to populate state with
|
||||
const NUMBER_OF_BLOCKS: u32 = 10;
|
||||
|
||||
// Put the first `NUMBER_OF_BLOCKS` blocks in a vector
|
||||
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
|
||||
.range(0..=NUMBER_OF_BLOCKS)
|
||||
|
@ -98,3 +98,45 @@ async fn rpc_getblock_error() {
|
|||
mempool.expect_no_requests().await;
|
||||
state.expect_no_requests().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_getbestblockhash() {
|
||||
zebra_test::init();
|
||||
|
||||
// Put `NUMBER_OF_BLOCKS` blocks in a vector
|
||||
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
|
||||
.range(0..=NUMBER_OF_BLOCKS)
|
||||
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
|
||||
.collect();
|
||||
|
||||
// Get the hash of the block at the tip using hardcoded block tip bytes.
|
||||
// We want to test the RPC response is equal to this hash
|
||||
let tip_block: Block = zebra_test::vectors::BLOCK_MAINNET_10_BYTES
|
||||
.zcash_deserialize_into()
|
||||
.unwrap();
|
||||
let tip_block_hash = tip_block.hash();
|
||||
|
||||
// Get a mempool handle
|
||||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
// Create a populated state service, the tip will be in `NUMBER_OF_BLOCKS`.
|
||||
let state = zebra_state::populated_state(blocks.clone(), Network::Mainnet).await;
|
||||
|
||||
// Init RPC
|
||||
let rpc = RpcImpl {
|
||||
app_version: "Zebra version test".to_string(),
|
||||
mempool: Buffer::new(mempool.clone(), 1),
|
||||
state,
|
||||
};
|
||||
|
||||
// Get the tip hash using RPC method `get_best_block_hash`
|
||||
let get_best_block_hash = rpc
|
||||
.get_best_block_hash()
|
||||
.await
|
||||
.expect("We should have a GetBestBlockHash struct");
|
||||
let response_hash = get_best_block_hash.0;
|
||||
|
||||
// Check if response is equal to block 10 hash.
|
||||
assert_eq!(response_hash, tip_block_hash);
|
||||
|
||||
mempool.expect_no_requests().await;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue