Skip to content

Commit

Permalink
Add block_search RPC endpoint (#991)
Browse files Browse the repository at this point in the history
* Add block_search RPC endpoint and tests

* Add .changelog entry

* Fix comments
  • Loading branch information
hu55a1n1 authored Sep 28, 2021
1 parent 9d83b54 commit 34c5d2a
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/unreleased/features/832-block-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- `[tendermint-rpc]` Add support for the `/block_search` RPC endpoint. See
<https://docs.tendermint.com/master/rpc/\#/Info/block_search> for details
([#832](https://github.com/informalsystems/tendermint-rs/issues/832))
12 changes: 12 additions & 0 deletions rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<block_search::Response, Error> {
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).
Expand Down
21 changes: 21 additions & 0 deletions rpc/src/client/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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()))
Expand Down
1 change: 1 addition & 0 deletions rpc/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
48 changes: 48 additions & 0 deletions rpc/src/endpoint/block_search.rs
Original file line number Diff line number Diff line change
@@ -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<block::Response>,
#[serde(with = "tendermint_proto::serializers::from_str")]
pub total_count: u32,
}

impl crate::Response for Response {}
5 changes: 5 additions & 0 deletions rpc/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
53 changes: 53 additions & 0 deletions rpc/tests/kvstore_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ fn outgoing_fixtures() {
.unwrap();
assert_eq!(wrapped.params().height.unwrap().value(), 10);
}
"block_search" => {
let wrapped =
serde_json::from_str::<RequestWrapper<endpoint::block_search::Request>>(
&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::<RequestWrapper<endpoint::blockchain::Request>>(&content)
Expand Down Expand Up @@ -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);
Expand Down
70 changes: 70 additions & 0 deletions rpc/tests/kvstore_fixtures/incoming/block_search.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
11 changes: 11 additions & 0 deletions rpc/tests/kvstore_fixtures/outgoing/block_search.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
14 changes: 14 additions & 0 deletions tools/kvstore-test/tests/tendermint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions tools/rpc-probe/src/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn quick_probe_plan(output_path: &Path, request_wait: Duration) -> Result<Pl
.with_min_height(10)
.with_name("block_at_height_10"),
block_results(10).with_name("block_results_at_height_10"),
block_search("block.height > 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),
Expand Down

0 comments on commit 34c5d2a

Please sign in to comment.