Skip to content

Commit 34c5d2a

Browse files
authored
Add block_search RPC endpoint (#991)
* Add block_search RPC endpoint and tests * Add .changelog entry * Fix comments
1 parent 9d83b54 commit 34c5d2a

File tree

11 files changed

+239
-0
lines changed

11 files changed

+239
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- `[tendermint-rpc]` Add support for the `/block_search` RPC endpoint. See
2+
<https://docs.tendermint.com/master/rpc/\#/Info/block_search> for details
3+
([#832](https://github.com/informalsystems/tendermint-rs/issues/832))

rpc/src/client.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ pub trait Client {
8585
self.perform(block_results::Request::default()).await
8686
}
8787

88+
/// `/block_search`: search for blocks by BeginBlock and EndBlock events.
89+
async fn block_search(
90+
&self,
91+
query: Query,
92+
page: u32,
93+
per_page: u8,
94+
order: Order,
95+
) -> Result<block_search::Response, Error> {
96+
self.perform(block_search::Request::new(query, page, per_page, order))
97+
.await
98+
}
99+
88100
/// `/blockchain`: get block headers for `min` <= `height` <= `max`.
89101
///
90102
/// Block headers are returned in descending order (highest first).

rpc/src/client/bin/main.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ enum ClientRequest {
9191
/// The height of the block you want.
9292
height: u32,
9393
},
94+
/// Search for a block by way of a specific query. Uses the same
95+
/// query syntax as the `subscribe` endpoint.
96+
BlockSearch {
97+
/// The query against which blocks should be matched.
98+
query: Query,
99+
#[structopt(long, default_value = "1")]
100+
page: u32,
101+
#[structopt(long, default_value = "10")]
102+
per_page: u8,
103+
#[structopt(long, default_value = "asc")]
104+
order: Order,
105+
},
94106
// TODO(thane): Implement evidence broadcast
95107
/// Broadcast a transaction asynchronously (without waiting for the ABCI
96108
/// app to check it or for it to be committed).
@@ -313,6 +325,15 @@ where
313325
serde_json::to_string_pretty(&client.block_results(height).await?)
314326
.map_err(Error::serde)?
315327
}
328+
ClientRequest::BlockSearch {
329+
query,
330+
page,
331+
per_page,
332+
order,
333+
} => {
334+
serde_json::to_string_pretty(&client.block_search(query, page, per_page, order).await?)
335+
.map_err(Error::serde)?
336+
}
316337
ClientRequest::BroadcastTxAsync { tx } => serde_json::to_string_pretty(
317338
&client
318339
.broadcast_tx_async(Transaction::from(tx.into_bytes()))

rpc/src/endpoint.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod abci_info;
44
pub mod abci_query;
55
pub mod block;
66
pub mod block_results;
7+
pub mod block_search;
78
pub mod blockchain;
89
pub mod broadcast;
910
pub mod commit;

rpc/src/endpoint/block_search.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! `/block_search` endpoint JSON-RPC wrapper
2+
3+
pub use super::{block, block_results};
4+
5+
use crate::{Method, Order};
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Request for searching for blocks by their BeginBlock and EndBlock events.
9+
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
10+
pub struct Request {
11+
pub query: String,
12+
#[serde(with = "tendermint_proto::serializers::from_str")]
13+
pub page: u32,
14+
#[serde(with = "tendermint_proto::serializers::from_str")]
15+
pub per_page: u8,
16+
pub order_by: Order,
17+
}
18+
19+
impl Request {
20+
/// Constructor.
21+
pub fn new(query: impl ToString, page: u32, per_page: u8, order_by: Order) -> Self {
22+
Self {
23+
query: query.to_string(),
24+
page,
25+
per_page,
26+
order_by,
27+
}
28+
}
29+
}
30+
31+
impl crate::Request for Request {
32+
type Response = Response;
33+
34+
fn method(&self) -> Method {
35+
Method::BlockSearch
36+
}
37+
}
38+
39+
impl crate::SimpleRequest for Request {}
40+
41+
#[derive(Clone, Debug, Deserialize, Serialize)]
42+
pub struct Response {
43+
pub blocks: Vec<block::Response>,
44+
#[serde(with = "tendermint_proto::serializers::from_str")]
45+
pub total_count: u32,
46+
}
47+
48+
impl crate::Response for Response {}

rpc/src/method.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub enum Method {
2424
/// Get ABCI results for a particular block
2525
BlockResults,
2626

27+
/// Search for blocks by their BeginBlock and EndBlock events
28+
BlockSearch,
29+
2730
/// Get blockchain info
2831
Blockchain,
2932

@@ -84,6 +87,7 @@ impl Method {
8487
Method::AbciQuery => "abci_query",
8588
Method::Block => "block",
8689
Method::BlockResults => "block_results",
90+
Method::BlockSearch => "block_search",
8791
Method::Blockchain => "blockchain",
8892
Method::BroadcastEvidence => "broadcast_evidence",
8993
Method::BroadcastTxAsync => "broadcast_tx_async",
@@ -114,6 +118,7 @@ impl FromStr for Method {
114118
"abci_query" => Method::AbciQuery,
115119
"block" => Method::Block,
116120
"block_results" => Method::BlockResults,
121+
"block_search" => Method::BlockSearch,
117122
"blockchain" => Method::Blockchain,
118123
"broadcast_evidence" => Method::BroadcastEvidence,
119124
"broadcast_tx_async" => Method::BroadcastTxAsync,

rpc/tests/kvstore_fixtures.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ fn outgoing_fixtures() {
9696
.unwrap();
9797
assert_eq!(wrapped.params().height.unwrap().value(), 10);
9898
}
99+
"block_search" => {
100+
let wrapped =
101+
serde_json::from_str::<RequestWrapper<endpoint::block_search::Request>>(
102+
&content,
103+
)
104+
.unwrap();
105+
assert_eq!(wrapped.params().query, "block.height > 1");
106+
assert_eq!(wrapped.params().page, 1);
107+
assert_eq!(wrapped.params().per_page, 10);
108+
assert_eq!(wrapped.params().order_by, Order::Ascending);
109+
}
99110
"blockchain_from_1_to_10" => {
100111
let wrapped =
101112
serde_json::from_str::<RequestWrapper<endpoint::blockchain::Request>>(&content)
@@ -447,6 +458,48 @@ fn incoming_fixtures() {
447458
assert!(result.txs_results.is_none());
448459
assert!(result.validator_updates.is_empty());
449460
}
461+
"block_search" => {
462+
let result = endpoint::block_search::Response::from_string(content).unwrap();
463+
assert_eq!(result.total_count as usize, result.blocks.len());
464+
// Test a few selected attributes of the results.
465+
for block in result.blocks {
466+
assert!(block.block.data.iter().next().is_none());
467+
assert!(block.block.evidence.iter().next().is_none());
468+
assert_eq!(block.block.header.app_hash.value(), [0u8; 8]);
469+
assert_eq!(block.block.header.chain_id.as_str(), CHAIN_ID);
470+
assert!(!block.block.header.consensus_hash.is_empty());
471+
assert!(block.block.header.data_hash.is_none());
472+
assert!(block.block.header.evidence_hash.is_none());
473+
assert_eq!(block.block.header.height.value(), 10);
474+
assert!(block.block.header.last_block_id.is_some());
475+
assert_eq!(block.block.header.last_commit_hash, empty_merkle_root_hash);
476+
assert_eq!(block.block.header.last_results_hash, empty_merkle_root_hash);
477+
assert!(!block.block.header.next_validators_hash.is_empty());
478+
assert_ne!(
479+
block.block.header.proposer_address.as_bytes(),
480+
[0u8; tendermint::account::LENGTH]
481+
);
482+
assert!(
483+
block
484+
.block
485+
.header
486+
.time
487+
.duration_since(informal_epoch)
488+
.unwrap()
489+
.as_secs()
490+
> 0
491+
);
492+
assert!(!block.block.header.validators_hash.is_empty());
493+
assert_eq!(
494+
block.block.header.version,
495+
tendermint::block::header::Version { block: 10, app: 1 }
496+
);
497+
assert!(block.block.last_commit.is_some());
498+
assert!(!block.block_id.hash.is_empty());
499+
assert!(!block.block_id.part_set_header.hash.is_empty());
500+
assert_eq!(block.block_id.part_set_header.total, 1);
501+
}
502+
}
450503
"blockchain_from_1_to_10" => {
451504
let result = endpoint::blockchain::Response::from_string(content).unwrap();
452505
assert_eq!(result.block_metas.len(), 10);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"jsonrpc": "2.0",
3+
"id": "",
4+
"result": {
5+
"blocks": [
6+
{
7+
"block_id": {
8+
"hash": "4FFD15F274758E474898498A191EB8CA6FC6C466576255DA132908A12AC1674C",
9+
"part_set_header": {
10+
"total": 1,
11+
"hash": "BBA710736635FA20CDB4F48732563869E90871D31FE9E7DE3D900CD4334D8775"
12+
}
13+
},
14+
"block": {
15+
"header": {
16+
"version": {
17+
"block": "10",
18+
"app": "1"
19+
},
20+
"chain_id": "dockerchain",
21+
"height": "10",
22+
"time": "2020-03-15T16:57:08.151Z",
23+
"last_block_id": {
24+
"hash": "760E050B2404A4BC661635CA552FF45876BCD927C367ADF88961E389C01D32FF",
25+
"part_set_header": {
26+
"total": 1,
27+
"hash": "485070D01F9543827B3F9BAF11BDCFFBFD2BDED0B63D7192FA55649B94A1D5DE"
28+
}
29+
},
30+
"last_commit_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
31+
"data_hash": "",
32+
"validators_hash": "3C0A744897A1E0DBF1DEDE1AF339D65EDDCF10E6338504368B20C508D6D578DC",
33+
"next_validators_hash": "3C0A744897A1E0DBF1DEDE1AF339D65EDDCF10E6338504368B20C508D6D578DC",
34+
"consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F",
35+
"app_hash": "0000000000000000",
36+
"last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
37+
"evidence_hash": "",
38+
"proposer_address": "12CC3970B3AE9F19A4B1D98BE1799F2CB923E0A3"
39+
},
40+
"data": {
41+
"txs": null
42+
},
43+
"evidence": {
44+
"evidence": null
45+
},
46+
"last_commit": {
47+
"height": "9",
48+
"round": 0,
49+
"block_id": {
50+
"hash": "760E050B2404A4BC661635CA552FF45876BCD927C367ADF88961E389C01D32FF",
51+
"part_set_header": {
52+
"total": 1,
53+
"hash": "485070D01F9543827B3F9BAF11BDCFFBFD2BDED0B63D7192FA55649B94A1D5DE"
54+
}
55+
},
56+
"signatures": [
57+
{
58+
"block_id_flag": 2,
59+
"validator_address": "12CC3970B3AE9F19A4B1D98BE1799F2CB923E0A3",
60+
"timestamp": "2020-03-15T16:57:08.151Z",
61+
"signature": "GRBX/UNaf19vs5byJfAuXk2FQ05soOHmaMFCbrNBhHdNZtFKHp6J9eFwZrrG+YCxKMdqPn2tQWAes6X8kpd1DA=="
62+
}
63+
]
64+
}
65+
}
66+
}
67+
],
68+
"total_count": "1"
69+
}
70+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"id": "9ee74828-e8f1-429d-9d53-254c833bae00",
3+
"jsonrpc": "2.0",
4+
"method": "block_search",
5+
"params": {
6+
"order_by": "asc",
7+
"page": "1",
8+
"per_page": "10",
9+
"query": "block.height > 1"
10+
}
11+
}

tools/kvstore-test/tests/tendermint.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ mod rpc {
138138
assert!(block_results.txs_results.is_none());
139139
}
140140

141+
async fn block_search() {
142+
let res = localhost_http_client()
143+
.block_search(
144+
Query::gt("block.height", "1"),
145+
1,
146+
1,
147+
Order::Ascending,
148+
)
149+
.await
150+
.unwrap();
151+
assert!(res.total_count > 0);
152+
assert_eq!(res.total_count as usize, res.blocks.len());
153+
}
154+
141155
/// `/blockchain` endpoint
142156
#[tokio::test]
143157
async fn blockchain() {

0 commit comments

Comments
 (0)