From 33a48411a080692b3b962330d9f4492829a25c60 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 15 Feb 2022 10:44:34 -0800 Subject: [PATCH] Add block_by_hash RPC endpoint and tests (#1089) * Add block_by_hash RPC endpoint and tests * rpc-probe part of this * Fix comments * Update .changelog/unreleased/features/832-block-by-hash.md Co-authored-by: Thane Thomson * cargo fmt Co-authored-by: Thane Thomson --- .../unreleased/features/832-block-by-hash.md | 1 + rpc/src/client.rs | 8 +++ rpc/src/client/bin/main.rs | 10 +++ rpc/src/endpoint.rs | 1 + rpc/src/endpoint/block_by_hash.rs | 47 ++++++++++++++ rpc/src/method.rs | 5 ++ rpc/tests/kvstore_fixtures.rs | 18 +++++ .../incoming/block_by_hash.json | 65 +++++++++++++++++++ .../outgoing/block_by_hash.json | 8 +++ tools/kvstore-test/tests/tendermint.rs | 26 ++++++++ tools/rpc-probe/src/common.rs | 10 +++ tools/rpc-probe/src/kvstore.rs | 1 + 12 files changed, 200 insertions(+) create mode 100644 .changelog/unreleased/features/832-block-by-hash.md create mode 100644 rpc/src/endpoint/block_by_hash.rs create mode 100644 rpc/tests/kvstore_fixtures/incoming/block_by_hash.json create mode 100644 rpc/tests/kvstore_fixtures/outgoing/block_by_hash.json diff --git a/.changelog/unreleased/features/832-block-by-hash.md b/.changelog/unreleased/features/832-block-by-hash.md new file mode 100644 index 000000000..28f17c6b3 --- /dev/null +++ b/.changelog/unreleased/features/832-block-by-hash.md @@ -0,0 +1 @@ +- `[tendermint-rpc]` Add support for the `/block_by_hash` RPC endpoint. See for details ([#832](https://github.com/informalsystems/tendermint-rs/issues/832)). diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 150bf42a3..02c349a34 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -67,6 +67,14 @@ pub trait Client { self.perform(block::Request::new(height.into())).await } + /// `/block_by_hash`: get block by hash. + async fn block_by_hash( + &self, + hash: tendermint::Hash, + ) -> Result { + self.perform(block_by_hash::Request::new(hash)).await + } + /// `/block`: get the latest block. async fn latest_block(&self) -> Result { self.perform(block::Request::default()).await diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index b6f559a15..9abc7492c 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -78,6 +78,8 @@ enum ClientRequest { }, /// Get a block at a given height. Block { height: u32 }, + /// Get a block by its hash. + BlockByHash { hash: String }, /// Get block headers between two heights (min <= height <= max). Blockchain { /// The minimum height @@ -316,6 +318,14 @@ where ClientRequest::Block { height } => { serde_json::to_string_pretty(&client.block(height).await?).map_err(Error::serde)? } + ClientRequest::BlockByHash { hash } => serde_json::to_string_pretty( + &client + .block_by_hash( + tendermint::Hash::from_str(&hash).map_err(|e| Error::parse(e.to_string()))?, + ) + .await?, + ) + .map_err(Error::serde)?, ClientRequest::Blockchain { min, max } => { serde_json::to_string_pretty(&client.blockchain(min, max).await?) .map_err(Error::serde)? diff --git a/rpc/src/endpoint.rs b/rpc/src/endpoint.rs index 8720be10b..2e749aaa6 100644 --- a/rpc/src/endpoint.rs +++ b/rpc/src/endpoint.rs @@ -3,6 +3,7 @@ pub mod abci_info; pub mod abci_query; pub mod block; +pub mod block_by_hash; pub mod block_results; pub mod block_search; pub mod blockchain; diff --git a/rpc/src/endpoint/block_by_hash.rs b/rpc/src/endpoint/block_by_hash.rs new file mode 100644 index 000000000..1775ca507 --- /dev/null +++ b/rpc/src/endpoint/block_by_hash.rs @@ -0,0 +1,47 @@ +//! `/block_by_hash` endpoint JSON-RPC wrapper + +use serde::{Deserialize, Serialize}; + +use tendermint::block::{self, Block}; +use tendermint::Hash; + +/// Get information about a specific block by its hash +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Request { + /// Hash of the block to request. + /// + /// If no hash is provided, it will return no block (as if the hash + /// did not match any block). + pub hash: Option, +} + +impl Request { + /// Create a new request for information about a particular block + pub fn new>(hash: H) -> Self { + Self { + hash: Some(hash.into()), + } + } +} + +impl crate::Request for Request { + type Response = Response; + + fn method(&self) -> crate::Method { + crate::Method::BlockByHash + } +} + +impl crate::SimpleRequest for Request {} + +/// Block responses +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + /// Block ID + pub block_id: block::Id, + + /// Block data + pub block: Option, +} + +impl crate::Response for Response {} diff --git a/rpc/src/method.rs b/rpc/src/method.rs index f2c821202..3297d0d37 100644 --- a/rpc/src/method.rs +++ b/rpc/src/method.rs @@ -22,6 +22,9 @@ pub enum Method { /// Get block info Block, + /// Get block info by hash + BlockByHash, + /// Get ABCI results for a particular block BlockResults, @@ -87,6 +90,7 @@ impl Method { Method::AbciInfo => "abci_info", Method::AbciQuery => "abci_query", Method::Block => "block", + Method::BlockByHash => "block_by_hash", Method::BlockResults => "block_results", Method::BlockSearch => "block_search", Method::Blockchain => "blockchain", @@ -118,6 +122,7 @@ impl FromStr for Method { "abci_info" => Method::AbciInfo, "abci_query" => Method::AbciQuery, "block" => Method::Block, + "block_by_hash" => Method::BlockByHash, "block_results" => Method::BlockResults, "block_search" => Method::BlockSearch, "blockchain" => Method::Blockchain, diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index d8915b3cc..86126cf1c 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -90,6 +90,17 @@ fn outgoing_fixtures() { .unwrap(); assert_eq!(wrapped.params().height.unwrap().value(), 10); } + "block_by_hash" => { + // First, get the hash at height 1. + let wrapped = serde_json::from_str::< + RequestWrapper, + >(&content) + .unwrap(); + assert_eq!( + wrapped.params().hash.unwrap().to_string(), + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF" + ); + } "block_results_at_height_10" => { let wrapped = serde_json::from_str::< RequestWrapper, @@ -472,6 +483,13 @@ fn incoming_fixtures() { assert!(result.txs_results.is_none()); assert!(result.validator_updates.is_empty()); } + "block_by_hash" => { + let result = endpoint::block::Response::from_string(content).unwrap(); + assert_eq!( + result.block_id.hash.to_string(), + "BCF3DB412E80A396D10BF5B5E6D3E63D3B06DEB25AA958BCB8CE18D023838042" + ); + } "block_search" => { let result = endpoint::block_search::Response::from_string(content).unwrap(); assert_eq!(result.total_count as usize, result.blocks.len()); diff --git a/rpc/tests/kvstore_fixtures/incoming/block_by_hash.json b/rpc/tests/kvstore_fixtures/incoming/block_by_hash.json new file mode 100644 index 000000000..975884a09 --- /dev/null +++ b/rpc/tests/kvstore_fixtures/incoming/block_by_hash.json @@ -0,0 +1,65 @@ +{ + "id": "988f944f-4d85-486f-ad27-2f3574c2a4a3", + "jsonrpc": "2.0", + "result": { + "block": { + "data": { + "txs": [] + }, + "evidence": { + "evidence": [] + }, + "header": { + "app_hash": "0000000000000000", + "chain_id": "dockerchain", + "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", + "data_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "height": "10", + "last_block_id": { + "hash": "03979BC4F521D92D137F8A93B64D7D1A8589560F64C4543784DECCBCEDF1D13F", + "parts": { + "hash": "00DC8C2DE1DE6B66960CB5F15F802286D6423903C06804C35053E0F757B34E47", + "total": 1 + } + }, + "last_commit_hash": "B194E4E363E010ED4F80860FAF452B45D81C77D7C68C753424F44596A229D692", + "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "next_validators_hash": "D506A39182DDCC44917A3E7827E784B501B354789F99BAC8D56AE50ABE34972B", + "proposer_address": "6B3F66DCF73507BCE7148D6580DAC27074108628", + "time": "2021-11-25T17:04:38.160820316Z", + "validators_hash": "D506A39182DDCC44917A3E7827E784B501B354789F99BAC8D56AE50ABE34972B", + "version": { + "app": "1", + "block": "11" + } + }, + "last_commit": { + "block_id": { + "hash": "03979BC4F521D92D137F8A93B64D7D1A8589560F64C4543784DECCBCEDF1D13F", + "parts": { + "hash": "00DC8C2DE1DE6B66960CB5F15F802286D6423903C06804C35053E0F757B34E47", + "total": 1 + } + }, + "height": "9", + "round": 0, + "signatures": [ + { + "block_id_flag": 2, + "signature": "5yClL8UlPdvb2tzNguZu3UaTH5X5S8S635u9nBQjZQw3NFhrZklXm6Aw7Mxvhn3y7CL0yKHdRmH0FnPh8cs2Cg==", + "timestamp": "2021-11-25T17:04:38.160820316Z", + "validator_address": "6B3F66DCF73507BCE7148D6580DAC27074108628" + } + ] + } + }, + "block_id": { + "hash": "BCF3DB412E80A396D10BF5B5E6D3E63D3B06DEB25AA958BCB8CE18D023838042", + "parts": { + "hash": "7F02924A557B6B6AEB9390183F5458C88EAC02956AFDCCAAFC09B59A80D1EEA8", + "total": 1 + } + } + } +} \ No newline at end of file diff --git a/rpc/tests/kvstore_fixtures/outgoing/block_by_hash.json b/rpc/tests/kvstore_fixtures/outgoing/block_by_hash.json new file mode 100644 index 000000000..5560f514b --- /dev/null +++ b/rpc/tests/kvstore_fixtures/outgoing/block_by_hash.json @@ -0,0 +1,8 @@ +{ + "id": "a37bcc8e-6a32-4157-9200-a26be9981bbd", + "jsonrpc": "2.0", + "method": "block", + "params": { + "hash": "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF" + } +} diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 869feb50f..e83c043de 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -14,6 +14,7 @@ /// /// (Make sure you install cargo-make using `cargo install cargo-make` first.) mod rpc { + use std::str::FromStr; use std::cmp::min; use tendermint_rpc::{ @@ -120,6 +121,31 @@ mod rpc { ); } + /// `/block_search` endpoint + #[tokio::test] + async fn block_by_hash() { + let res = localhost_http_client() + .block_by_hash( + tendermint::Hash::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap() + ) + .await + .unwrap(); + assert!(res.block.is_none()); + + // Reuse block(1) to get an existing hash. + let height = 1u64; + let block_info = localhost_http_client() + .block(Height::try_from(height).unwrap()) + .await + .unwrap(); + let res = localhost_http_client() + .block_by_hash(block_info.block_id.hash) + .await + .unwrap(); + assert!(res.block.is_some()); + assert_eq!(block_info.block.header.height.value(), height); + } + /// `/block_results` endpoint #[tokio::test] async fn block_results() { diff --git a/tools/rpc-probe/src/common.rs b/tools/rpc-probe/src/common.rs index f8e996727..299befa22 100644 --- a/tools/rpc-probe/src/common.rs +++ b/tools/rpc-probe/src/common.rs @@ -30,6 +30,16 @@ pub fn block(height: u64) -> PlannedInteraction { .into() } +pub fn block_by_hash(hash: &str) -> PlannedInteraction { + Request::new( + "block_by_hash", + json!({ + "hash": format!("{}", hash), + }), + ) + .into() +} + pub fn block_search(query: &str, page: u32, per_page: u32, order_by: &str) -> PlannedInteraction { Request::new( "block_search", diff --git a/tools/rpc-probe/src/kvstore.rs b/tools/rpc-probe/src/kvstore.rs index 6b33a12c4..c03cddda6 100644 --- a/tools/rpc-probe/src/kvstore.rs +++ b/tools/rpc-probe/src/kvstore.rs @@ -21,6 +21,7 @@ pub fn quick_probe_plan(output_path: &Path, request_wait: Duration) -> Result 1", 1, 10, "asc").with_name("block_search"), blockchain(1, 10).with_name("blockchain_from_1_to_10"), commit(10).with_name("commit_at_height_10"),