From 34c5d2a9fde36f9e43a9bfead7e4da5409e14252 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 29 Sep 2021 00:56:18 +0530 Subject: [PATCH] Add `block_search` RPC endpoint (#991) * Add block_search RPC endpoint and tests * Add .changelog entry * Fix comments --- .../unreleased/features/832-block-search.md | 3 + rpc/src/client.rs | 12 ++++ rpc/src/client/bin/main.rs | 21 ++++++ rpc/src/endpoint.rs | 1 + rpc/src/endpoint/block_search.rs | 48 +++++++++++++ rpc/src/method.rs | 5 ++ rpc/tests/kvstore_fixtures.rs | 53 ++++++++++++++ .../incoming/block_search.json | 70 +++++++++++++++++++ .../outgoing/block_search.json | 11 +++ tools/kvstore-test/tests/tendermint.rs | 14 ++++ tools/rpc-probe/src/quick.rs | 1 + 11 files changed, 239 insertions(+) create mode 100644 .changelog/unreleased/features/832-block-search.md create mode 100644 rpc/src/endpoint/block_search.rs create mode 100644 rpc/tests/kvstore_fixtures/incoming/block_search.json create mode 100644 rpc/tests/kvstore_fixtures/outgoing/block_search.json diff --git a/.changelog/unreleased/features/832-block-search.md b/.changelog/unreleased/features/832-block-search.md new file mode 100644 index 000000000..db05e839f --- /dev/null +++ b/.changelog/unreleased/features/832-block-search.md @@ -0,0 +1,3 @@ +- `[tendermint-rpc]` Add support for the `/block_search` RPC endpoint. See + for details +([#832](https://github.com/informalsystems/tendermint-rs/issues/832)) \ No newline at end of file diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 7a923f40f..2dc6e153a 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -85,6 +85,18 @@ pub trait Client { self.perform(block_results::Request::default()).await } + /// `/block_search`: search for blocks by BeginBlock and EndBlock events. + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.perform(block_search::Request::new(query, page, per_page, order)) + .await + } + /// `/blockchain`: get block headers for `min` <= `height` <= `max`. /// /// Block headers are returned in descending order (highest first). diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index 18572b553..b4e48c572 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -91,6 +91,18 @@ enum ClientRequest { /// The height of the block you want. height: u32, }, + /// Search for a block by way of a specific query. Uses the same + /// query syntax as the `subscribe` endpoint. + BlockSearch { + /// The query against which blocks should be matched. + query: Query, + #[structopt(long, default_value = "1")] + page: u32, + #[structopt(long, default_value = "10")] + per_page: u8, + #[structopt(long, default_value = "asc")] + order: Order, + }, // TODO(thane): Implement evidence broadcast /// Broadcast a transaction asynchronously (without waiting for the ABCI /// app to check it or for it to be committed). @@ -313,6 +325,15 @@ where serde_json::to_string_pretty(&client.block_results(height).await?) .map_err(Error::serde)? } + ClientRequest::BlockSearch { + query, + page, + per_page, + order, + } => { + serde_json::to_string_pretty(&client.block_search(query, page, per_page, order).await?) + .map_err(Error::serde)? + } ClientRequest::BroadcastTxAsync { tx } => serde_json::to_string_pretty( &client .broadcast_tx_async(Transaction::from(tx.into_bytes())) diff --git a/rpc/src/endpoint.rs b/rpc/src/endpoint.rs index 2cee26db2..8720be10b 100644 --- a/rpc/src/endpoint.rs +++ b/rpc/src/endpoint.rs @@ -4,6 +4,7 @@ pub mod abci_info; pub mod abci_query; pub mod block; pub mod block_results; +pub mod block_search; pub mod blockchain; pub mod broadcast; pub mod commit; diff --git a/rpc/src/endpoint/block_search.rs b/rpc/src/endpoint/block_search.rs new file mode 100644 index 000000000..ef1e19c4a --- /dev/null +++ b/rpc/src/endpoint/block_search.rs @@ -0,0 +1,48 @@ +//! `/block_search` endpoint JSON-RPC wrapper + +pub use super::{block, block_results}; + +use crate::{Method, Order}; +use serde::{Deserialize, Serialize}; + +/// Request for searching for blocks by their BeginBlock and EndBlock events. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Request { + pub query: String, + #[serde(with = "tendermint_proto::serializers::from_str")] + pub page: u32, + #[serde(with = "tendermint_proto::serializers::from_str")] + pub per_page: u8, + pub order_by: Order, +} + +impl Request { + /// Constructor. + pub fn new(query: impl ToString, page: u32, per_page: u8, order_by: Order) -> Self { + Self { + query: query.to_string(), + page, + per_page, + order_by, + } + } +} + +impl crate::Request for Request { + type Response = Response; + + fn method(&self) -> Method { + Method::BlockSearch + } +} + +impl crate::SimpleRequest for Request {} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + pub blocks: Vec, + #[serde(with = "tendermint_proto::serializers::from_str")] + pub total_count: u32, +} + +impl crate::Response for Response {} diff --git a/rpc/src/method.rs b/rpc/src/method.rs index f03c8b3b3..5a3fd47bc 100644 --- a/rpc/src/method.rs +++ b/rpc/src/method.rs @@ -24,6 +24,9 @@ pub enum Method { /// Get ABCI results for a particular block BlockResults, + /// Search for blocks by their BeginBlock and EndBlock events + BlockSearch, + /// Get blockchain info Blockchain, @@ -84,6 +87,7 @@ impl Method { Method::AbciQuery => "abci_query", Method::Block => "block", Method::BlockResults => "block_results", + Method::BlockSearch => "block_search", Method::Blockchain => "blockchain", Method::BroadcastEvidence => "broadcast_evidence", Method::BroadcastTxAsync => "broadcast_tx_async", @@ -114,6 +118,7 @@ impl FromStr for Method { "abci_query" => Method::AbciQuery, "block" => Method::Block, "block_results" => Method::BlockResults, + "block_search" => Method::BlockSearch, "blockchain" => Method::Blockchain, "broadcast_evidence" => Method::BroadcastEvidence, "broadcast_tx_async" => Method::BroadcastTxAsync, diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 5948000c3..b7df5db50 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -96,6 +96,17 @@ fn outgoing_fixtures() { .unwrap(); assert_eq!(wrapped.params().height.unwrap().value(), 10); } + "block_search" => { + let wrapped = + serde_json::from_str::>( + &content, + ) + .unwrap(); + assert_eq!(wrapped.params().query, "block.height > 1"); + assert_eq!(wrapped.params().page, 1); + assert_eq!(wrapped.params().per_page, 10); + assert_eq!(wrapped.params().order_by, Order::Ascending); + } "blockchain_from_1_to_10" => { let wrapped = serde_json::from_str::>(&content) @@ -447,6 +458,48 @@ fn incoming_fixtures() { assert!(result.txs_results.is_none()); assert!(result.validator_updates.is_empty()); } + "block_search" => { + let result = endpoint::block_search::Response::from_string(content).unwrap(); + assert_eq!(result.total_count as usize, result.blocks.len()); + // Test a few selected attributes of the results. + for block in result.blocks { + assert!(block.block.data.iter().next().is_none()); + assert!(block.block.evidence.iter().next().is_none()); + assert_eq!(block.block.header.app_hash.value(), [0u8; 8]); + assert_eq!(block.block.header.chain_id.as_str(), CHAIN_ID); + assert!(!block.block.header.consensus_hash.is_empty()); + assert!(block.block.header.data_hash.is_none()); + assert!(block.block.header.evidence_hash.is_none()); + assert_eq!(block.block.header.height.value(), 10); + assert!(block.block.header.last_block_id.is_some()); + assert_eq!(block.block.header.last_commit_hash, empty_merkle_root_hash); + assert_eq!(block.block.header.last_results_hash, empty_merkle_root_hash); + assert!(!block.block.header.next_validators_hash.is_empty()); + assert_ne!( + block.block.header.proposer_address.as_bytes(), + [0u8; tendermint::account::LENGTH] + ); + assert!( + block + .block + .header + .time + .duration_since(informal_epoch) + .unwrap() + .as_secs() + > 0 + ); + assert!(!block.block.header.validators_hash.is_empty()); + assert_eq!( + block.block.header.version, + tendermint::block::header::Version { block: 10, app: 1 } + ); + assert!(block.block.last_commit.is_some()); + assert!(!block.block_id.hash.is_empty()); + assert!(!block.block_id.part_set_header.hash.is_empty()); + assert_eq!(block.block_id.part_set_header.total, 1); + } + } "blockchain_from_1_to_10" => { let result = endpoint::blockchain::Response::from_string(content).unwrap(); assert_eq!(result.block_metas.len(), 10); diff --git a/rpc/tests/kvstore_fixtures/incoming/block_search.json b/rpc/tests/kvstore_fixtures/incoming/block_search.json new file mode 100644 index 000000000..cbd74b5d7 --- /dev/null +++ b/rpc/tests/kvstore_fixtures/incoming/block_search.json @@ -0,0 +1,70 @@ +{ + "jsonrpc": "2.0", + "id": "", + "result": { + "blocks": [ + { + "block_id": { + "hash": "4FFD15F274758E474898498A191EB8CA6FC6C466576255DA132908A12AC1674C", + "part_set_header": { + "total": 1, + "hash": "BBA710736635FA20CDB4F48732563869E90871D31FE9E7DE3D900CD4334D8775" + } + }, + "block": { + "header": { + "version": { + "block": "10", + "app": "1" + }, + "chain_id": "dockerchain", + "height": "10", + "time": "2020-03-15T16:57:08.151Z", + "last_block_id": { + "hash": "760E050B2404A4BC661635CA552FF45876BCD927C367ADF88961E389C01D32FF", + "part_set_header": { + "total": 1, + "hash": "485070D01F9543827B3F9BAF11BDCFFBFD2BDED0B63D7192FA55649B94A1D5DE" + } + }, + "last_commit_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "data_hash": "", + "validators_hash": "3C0A744897A1E0DBF1DEDE1AF339D65EDDCF10E6338504368B20C508D6D578DC", + "next_validators_hash": "3C0A744897A1E0DBF1DEDE1AF339D65EDDCF10E6338504368B20C508D6D578DC", + "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", + "app_hash": "0000000000000000", + "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "evidence_hash": "", + "proposer_address": "12CC3970B3AE9F19A4B1D98BE1799F2CB923E0A3" + }, + "data": { + "txs": null + }, + "evidence": { + "evidence": null + }, + "last_commit": { + "height": "9", + "round": 0, + "block_id": { + "hash": "760E050B2404A4BC661635CA552FF45876BCD927C367ADF88961E389C01D32FF", + "part_set_header": { + "total": 1, + "hash": "485070D01F9543827B3F9BAF11BDCFFBFD2BDED0B63D7192FA55649B94A1D5DE" + } + }, + "signatures": [ + { + "block_id_flag": 2, + "validator_address": "12CC3970B3AE9F19A4B1D98BE1799F2CB923E0A3", + "timestamp": "2020-03-15T16:57:08.151Z", + "signature": "GRBX/UNaf19vs5byJfAuXk2FQ05soOHmaMFCbrNBhHdNZtFKHp6J9eFwZrrG+YCxKMdqPn2tQWAes6X8kpd1DA==" + } + ] + } + } + } + ], + "total_count": "1" + } +} \ No newline at end of file diff --git a/rpc/tests/kvstore_fixtures/outgoing/block_search.json b/rpc/tests/kvstore_fixtures/outgoing/block_search.json new file mode 100644 index 000000000..91e1c9a82 --- /dev/null +++ b/rpc/tests/kvstore_fixtures/outgoing/block_search.json @@ -0,0 +1,11 @@ +{ + "id": "9ee74828-e8f1-429d-9d53-254c833bae00", + "jsonrpc": "2.0", + "method": "block_search", + "params": { + "order_by": "asc", + "page": "1", + "per_page": "10", + "query": "block.height > 1" + } +} \ No newline at end of file diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 7cf01b6ed..adac14cc2 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -138,6 +138,20 @@ mod rpc { assert!(block_results.txs_results.is_none()); } + async fn block_search() { + let res = localhost_http_client() + .block_search( + Query::gt("block.height", "1"), + 1, + 1, + Order::Ascending, + ) + .await + .unwrap(); + assert!(res.total_count > 0); + assert_eq!(res.total_count as usize, res.blocks.len()); + } + /// `/blockchain` endpoint #[tokio::test] async fn blockchain() { diff --git a/tools/rpc-probe/src/quick.rs b/tools/rpc-probe/src/quick.rs index c3557b395..33df58aa9 100644 --- a/tools/rpc-probe/src/quick.rs +++ b/tools/rpc-probe/src/quick.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"), consensus_params(10),