Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tx_search endpoint for RPC client #701

Merged
merged 10 commits into from
Dec 1, 2020
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
- `[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])

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

[#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