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:
Alfredo Garcia 2022-03-11 02:13:08 -03:00 committed by GitHub
parent 9862f6e5cf
commit f39ac48c59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 14 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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;
}