Skip to content

Commit

Permalink
Add tx_search endpoint for RPC client (#701)
Browse files Browse the repository at this point in the history
* Add tx_search endpoint for RPC client

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Update CHANGELOG

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Fix tx_search Request docs

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Update proto/src/serializers/bytes.rs

Co-authored-by: Greg Szabo <16846635+greg-szabo@users.noreply.github.com>

* Replace sleep with Tokio 0.2.x-compatible method call

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Supply full tx to broadcast_tx helper method

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Fix tx_search query for integration test

Signed-off-by: Thane Thomson <connect@thanethomson.com>

Co-authored-by: Greg Szabo <16846635+greg-szabo@users.noreply.github.com>
  • Loading branch information
thanethomson and greg-szabo committed Dec 1, 2020
1 parent e334fb7 commit 25f7ba5
Show file tree
Hide file tree
Showing 19 changed files with 1,110 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
- `[light-client]` Only require Tokio when `rpc-client` feature is enabled ([#425])
- `[rpc]` The `WebSocketClient` now adds support for all remaining RPC requests
by way of implementing the `Client` trait ([#646])
- `[rpc]` Support for the `tx_search` RPC endpoint has been added ([#701])
- `[tendermint]` (Since v0.17.0-rc3) Bech32 encoding fix ([#690])

[#425]: https://github.com/informalsystems/tendermint-rs/issues/425
[#646]: https://github.com/informalsystems/tendermint-rs/pull/646
[#690]: https://github.com/informalsystems/tendermint-rs/issues/690
[#701]: https://github.com/informalsystems/tendermint-rs/pull/701

## v0.17.0-rc3

Expand Down
17 changes: 15 additions & 2 deletions proto-compiler/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ const QUOTED: &str = r#"#[serde(with = "crate::serializers::from_str")]"#;
const QUOTED_WITH_DEFAULT: &str = r#"#[serde(with = "crate::serializers::from_str", default)]"#;
const HEXSTRING: &str = r#"#[serde(with = "crate::serializers::bytes::hexstring")]"#;
const BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::base64string")]"#;
const VEC_BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::vec_base64string")]"#;
const OPTIONAL: &str = r#"#[serde(with = "crate::serializers::optional")]"#;
const VEC_SKIP_IF_EMPTY: &str =
r#"#[serde(skip_serializing_if = "Vec::is_empty", with = "serde_bytes")]"#;
const NULLABLEVECARRAY: &str = r#"#[serde(with = "crate::serializers::txs")]"#;
const NULLABLE: &str = r#"#[serde(with = "crate::serializers::nullable")]"#;
const ALIAS_POWER_QUOTED: &str =
r#"#[serde(alias = "power", with = "crate::serializers::from_str")]"#;
const PART_SET_HEADER_TOTAL: &str = r#"#[serde(with = "crate::serializers::part_set_header_total")]"#;
const PART_SET_HEADER_TOTAL: &str =
r#"#[serde(with = "crate::serializers::part_set_header_total")]"#;
const RENAME_PUBKEY: &str = r#"#[serde(rename = "tendermint/PubKeyEd25519", with = "crate::serializers::bytes::base64string")]"#;
const RENAME_DUPLICATEVOTE: &str = r#"#[serde(rename = "tendermint/DuplicateVoteEvidence")]"#;
const RENAME_LIGHTCLIENTATTACK: &str =
Expand Down Expand Up @@ -69,6 +71,8 @@ pub static CUSTOM_TYPE_ATTRIBUTES: &[(&str, &str)] = &[
(".tendermint.types.CanonicalVote", SERIALIZED),
(".tendermint.types.BlockMeta", SERIALIZED),
(".tendermint.types.Evidence", EVIDENCE_VARIANT),
(".tendermint.types.TxProof", SERIALIZED),
(".tendermint.crypto.Proof", SERIALIZED),
];

/// Custom field attributes applied on top of protobuf fields in (a) struct(s)
Expand Down Expand Up @@ -98,7 +102,10 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[
".tendermint.types.CanonicalBlockID.part_set_header",
ALIAS_PARTS,
),
(".tendermint.types.PartSetHeader.total", PART_SET_HEADER_TOTAL),
(
".tendermint.types.PartSetHeader.total",
PART_SET_HEADER_TOTAL,
),
(".tendermint.types.PartSetHeader.hash", HEXSTRING),
(".tendermint.types.Header.height", QUOTED),
(".tendermint.types.Header.time", OPTIONAL),
Expand Down Expand Up @@ -144,4 +151,10 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[
".tendermint.types.Evidence.sum.light_client_attack_evidence",
RENAME_LIGHTCLIENTATTACK,
),
(".tendermint.types.TxProof.data", BASE64STRING),
(".tendermint.types.TxProof.root_hash", HEXSTRING),
(".tendermint.crypto.Proof.index", QUOTED),
(".tendermint.crypto.Proof.total", QUOTED),
(".tendermint.crypto.Proof.aunts", VEC_BASE64STRING),
(".tendermint.crypto.Proof.leaf_hash", BASE64STRING),
];
5 changes: 5 additions & 0 deletions proto/src/prost/tendermint.crypto.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(::serde::Deserialize, ::serde::Serialize)]
pub struct Proof {
#[prost(int64, tag="1")]
#[serde(with = "crate::serializers::from_str")]
pub total: i64,
#[prost(int64, tag="2")]
#[serde(with = "crate::serializers::from_str")]
pub index: i64,
#[prost(bytes, tag="3")]
#[serde(with = "crate::serializers::bytes::base64string")]
pub leaf_hash: std::vec::Vec<u8>,
#[prost(bytes, repeated, tag="4")]
#[serde(with = "crate::serializers::bytes::vec_base64string")]
pub aunts: ::std::vec::Vec<std::vec::Vec<u8>>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down
3 changes: 3 additions & 0 deletions proto/src/prost/tendermint.types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,13 @@ pub struct BlockMeta {
}
/// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree.
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(::serde::Deserialize, ::serde::Serialize)]
pub struct TxProof {
#[prost(bytes, tag="1")]
#[serde(with = "crate::serializers::bytes::hexstring")]
pub root_hash: std::vec::Vec<u8>,
#[prost(bytes, tag="2")]
#[serde(with = "crate::serializers::bytes::base64string")]
pub data: std::vec::Vec<u8>,
#[prost(message, optional, tag="3")]
pub proof: ::std::option::Option<super::crypto::Proof>,
Expand Down
33 changes: 33 additions & 0 deletions proto/src/serializers/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ pub mod base64string {
}
}

/// Serialize into Vec<base64string>, deserialize from Vec<base64string>
pub mod vec_base64string {
use serde::{Deserialize, Deserializer, Serializer};
use subtle_encoding::base64;

/// Deserialize array into Vec<Vec<u8>>
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
Option::<Vec<String>>::deserialize(deserializer)?
.unwrap_or_default()
.into_iter()
.map(|s| base64::decode(&s).map_err(serde::de::Error::custom))
.collect()
}

/// Serialize from Vec<T> into Vec<base64string>
pub fn serialize<S, T>(value: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
let base64_strings = value
.iter()
.map(|v| {
String::from_utf8(base64::encode(v.as_ref())).map_err(serde::ser::Error::custom)
})
.collect::<Result<Vec<String>, S::Error>>()?;
serializer.collect_seq(base64_strings)
}
}

/// Serialize into Option<base64string>, deserialize from Option<base64string>
pub mod option_base64string {
use serde::{Deserialize, Deserializer, Serializer};
Expand Down
20 changes: 20 additions & 0 deletions rpc-probe/src/kvstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,23 @@ pub fn status() -> PlannedInteraction {
pub fn subscribe(query: &str) -> PlannedInteraction {
PlannedSubscription::new(query).into()
}

pub fn tx_search(
query: &str,
prove: bool,
page: u32,
per_page: u8,
order_by: &str,
) -> PlannedInteraction {
Request::new(
"tx_search",
json!({
"query": query,
"prove": prove,
"page": format!("{}", page),
"per_page": format!("{}", per_page),
"order_by": order_by,
}),
)
.into()
}
2 changes: 2 additions & 0 deletions rpc-probe/src/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub fn quick_probe_plan(output_path: &Path, request_wait: Duration) -> Result<Pl
// This should have been created in the previous set of
// interactions.
abci_query("tx0").with_name("abci_query_with_existing_key"),
tx_search("tx.height > 1", false, 1, 10, "asc").with_name("tx_search_no_prove"),
tx_search("tx.height > 1", true, 1, 10, "asc").with_name("tx_search_with_prove"),
]),
],
)
Expand Down
16 changes: 15 additions & 1 deletion rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub use transport::http::HttpClient;
pub use transport::websocket::{WebSocketClient, WebSocketClientDriver};

use crate::endpoint::*;
use crate::{Result, SimpleRequest};
use crate::query::Query;
use crate::{Order, Result, SimpleRequest};
use async_trait::async_trait;
use tendermint::abci::{self, Transaction};
use tendermint::block::Height;
Expand Down Expand Up @@ -159,6 +160,19 @@ pub trait Client {
self.perform(evidence::Request::new(e)).await
}

/// `/tx_search`: search for transactions with their results.
async fn tx_search(
&self,
query: Query,
prove: bool,
page: u32,
per_page: u8,
order: Order,
) -> Result<tx_search::Response> {
self.perform(tx_search::Request::new(query, prove, page, per_page, order))
.await
}

/// Perform a request against the RPC endpoint
async fn perform<R>(&self, request: R) -> Result<R::Response>
where
Expand Down
1 change: 1 addition & 0 deletions rpc/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ pub mod health;
pub mod net_info;
pub mod status;
pub mod subscribe;
pub mod tx_search;
pub mod unsubscribe;
pub mod validators;
67 changes: 67 additions & 0 deletions rpc/src/endpoint/tx_search.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! `/tx_search` endpoint JSON-RPC wrapper

use crate::{Method, Order};
use serde::{Deserialize, Serialize};
use tendermint::{abci, block};
use tendermint_proto::types::TxProof;

/// Request for searching for transactions with their results.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Request {
pub query: String,
pub prove: bool,
#[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,
prove: bool,
page: u32,
per_page: u8,
order_by: Order,
) -> Self {
Self {
query: query.to_string(),
prove,
page,
per_page,
order_by,
}
}
}

impl crate::Request for Request {
type Response = Response;

fn method(&self) -> Method {
Method::TxSearch
}
}

impl crate::SimpleRequest for Request {}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Response {
pub txs: Vec<ResultTx>,
#[serde(with = "tendermint_proto::serializers::from_str")]
pub total_count: u32,
}

impl crate::Response for Response {}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ResultTx {
pub hash: abci::transaction::Hash,
pub height: block::Height,
pub index: u32,
pub tx_result: abci::DeliverTx,
pub tx: abci::Transaction,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<TxProof>,
}
6 changes: 4 additions & 2 deletions rpc/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ pub enum EventData {
/// Transaction result info.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TxInfo {
pub height: String,
#[serde(with = "tendermint_proto::serializers::from_str")]
pub height: i64,
pub index: Option<i64>,
pub tx: String,
#[serde(with = "tendermint_proto::serializers::bytes::base64string")]
pub tx: Vec<u8>,
pub result: TxResult,
}

Expand Down
5 changes: 3 additions & 2 deletions rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! functionality and different client transports based on which features you
//! select when using it.
//!
//! Two features are provided at present.
//! Two features are provided at present:
//!
//! * `http-client` - Provides [`HttpClient`], which is a basic RPC client that
//! interacts with remote Tendermint nodes via **JSON-RPC over HTTP**. This
Expand Down Expand Up @@ -51,6 +51,7 @@ pub mod error;
pub mod event;
mod id;
mod method;
mod order;
pub mod query;
pub mod request;
pub mod response;
Expand All @@ -59,6 +60,6 @@ mod utils;
mod version;

pub use self::{
error::Error, id::Id, method::Method, request::Request, request::SimpleRequest,
error::Error, id::Id, method::Method, order::Order, request::Request, request::SimpleRequest,
response::Response, result::Result, version::Version,
};
13 changes: 9 additions & 4 deletions rpc/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ pub enum Method {
/// Get node status
Status,

/// Search for transactions with their results
TxSearch,

/// Get validator info for a block
Validators,

Expand All @@ -73,6 +76,7 @@ impl Method {
Method::Block => "block",
Method::BlockResults => "block_results",
Method::Blockchain => "blockchain",
Method::BroadcastEvidence => "broadcast_evidence",
Method::BroadcastTxAsync => "broadcast_tx_async",
Method::BroadcastTxSync => "broadcast_tx_sync",
Method::BroadcastTxCommit => "broadcast_tx_commit",
Expand All @@ -81,10 +85,10 @@ impl Method {
Method::Health => "health",
Method::NetInfo => "net_info",
Method::Status => "status",
Method::Validators => "validators",
Method::Subscribe => "subscribe",
Method::BroadcastEvidence => "broadcast_evidence",
Method::TxSearch => "tx_search",
Method::Unsubscribe => "unsubscribe",
Method::Validators => "validators",
}
}
}
Expand All @@ -99,6 +103,7 @@ impl FromStr for Method {
"block" => Method::Block,
"block_results" => Method::BlockResults,
"blockchain" => Method::Blockchain,
"broadcast_evidence" => Method::BroadcastEvidence,
"broadcast_tx_async" => Method::BroadcastTxAsync,
"broadcast_tx_sync" => Method::BroadcastTxSync,
"broadcast_tx_commit" => Method::BroadcastTxCommit,
Expand All @@ -107,10 +112,10 @@ impl FromStr for Method {
"health" => Method::Health,
"net_info" => Method::NetInfo,
"status" => Method::Status,
"validators" => Method::Validators,
"subscribe" => Method::Subscribe,
"tx_search" => Method::TxSearch,
"unsubscribe" => Method::Unsubscribe,
"broadcast_evidence" => Method::BroadcastEvidence,
"validators" => Method::Validators,
other => return Err(Error::method_not_found(other)),
})
}
Expand Down
15 changes: 15 additions & 0 deletions rpc/src/order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Ordering of paginated RPC responses.

use serde::{Deserialize, Serialize};

/// Ordering of paginated RPC responses.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Order {
/// Ascending order
#[serde(rename = "asc")]
Ascending,

/// Descending order
#[serde(rename = "desc")]
Descending,
}
Loading

0 comments on commit 25f7ba5

Please sign in to comment.