From 1fbbf5eb554acff7b6a8ab369325ef5d476d3e75 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 18 Jul 2019 11:15:33 -0700 Subject: [PATCH] tendermint-rs: /abci_query RPC endpoint (closes #287) Adds support for invoking the ABCIQuery endpoint: https://tendermint.com/rpc/#abciquery --- Cargo.toml | 4 +- tendermint-rs/src/abci.rs | 8 +- tendermint-rs/src/abci/path.rs | 26 +++++ tendermint-rs/src/abci/proof.rs | 51 ++++++++++ tendermint-rs/src/block/height.rs | 2 +- tendermint-rs/src/rpc/client.rs | 18 +++- tendermint-rs/src/rpc/endpoint.rs | 1 + tendermint-rs/src/rpc/endpoint/abci_query.rs | 95 +++++++++++++++++++ tendermint-rs/tests/integration.rs | 6 ++ tendermint-rs/tests/rpc.rs | 9 ++ .../tests/support/rpc/abci_query.json | 15 +++ 11 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 tendermint-rs/src/abci/path.rs create mode 100644 tendermint-rs/src/abci/proof.rs create mode 100644 tendermint-rs/src/rpc/endpoint/abci_query.rs create mode 100644 tendermint-rs/tests/support/rpc/abci_query.json diff --git a/Cargo.toml b/Cargo.toml index fa84107..15d4dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ yubihsm = { version = "0.26", features = ["setup", "usb"], optional = true } zeroize = "0.9" [dependencies.abscissa_core] -version = "0.2.0" +version = "0.2" [dependencies.tendermint] version = "0.9" @@ -59,7 +59,7 @@ tempfile = "3" rand = "0.6" [dev-dependencies.abscissa_core] -version = "0.2.0" +version = "0.2" features = ["testing"] [features] diff --git a/tendermint-rs/src/abci.rs b/tendermint-rs/src/abci.rs index 76e0572..41f3631 100644 --- a/tendermint-rs/src/abci.rs +++ b/tendermint-rs/src/abci.rs @@ -18,11 +18,15 @@ mod info; #[cfg(feature = "rpc")] mod log; #[cfg(feature = "rpc")] +mod path; +#[cfg(feature = "rpc")] +mod proof; +#[cfg(feature = "rpc")] mod responses; pub mod transaction; #[cfg(feature = "rpc")] pub use self::{ - code::Code, data::Data, gas::Gas, info::Info, log::Log, responses::Responses, - transaction::Transaction, + code::Code, data::Data, gas::Gas, info::Info, log::Log, path::Path, proof::Proof, + responses::Responses, transaction::Transaction, }; diff --git a/tendermint-rs/src/abci/path.rs b/tendermint-rs/src/abci/path.rs new file mode 100644 index 0000000..09f63be --- /dev/null +++ b/tendermint-rs/src/abci/path.rs @@ -0,0 +1,26 @@ +//! Paths to ABCI data + +use crate::error::Error; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +/// Path to ABCI data +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Path(String); + +impl Display for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl FromStr for Path { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Path(s.to_owned())) + } +} diff --git a/tendermint-rs/src/abci/proof.rs b/tendermint-rs/src/abci/proof.rs new file mode 100644 index 0000000..8429da8 --- /dev/null +++ b/tendermint-rs/src/abci/proof.rs @@ -0,0 +1,51 @@ +//! ABCI Merkle proofs + +use crate::error::Error; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; +use subtle_encoding::{Encoding, Hex}; + +/// ABCI Merkle proofs +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Proof(Vec); + +impl AsRef<[u8]> for Proof { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Display for Proof { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + &Hex::upper_case().encode_to_string(&self.0).unwrap() + ) + } +} + +impl FromStr for Proof { + type Err = Error; + + fn from_str(s: &str) -> Result { + let bytes = Hex::upper_case().decode(s)?; + Ok(Proof(bytes)) + } +} + +impl<'de> Deserialize<'de> for Proof { + fn deserialize>(deserializer: D) -> Result { + let hex = String::deserialize(deserializer)?; + Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{}", e)))?) + } +} + +impl Serialize for Proof { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/tendermint-rs/src/block/height.rs b/tendermint-rs/src/block/height.rs index 65974e9..10a6557 100644 --- a/tendermint-rs/src/block/height.rs +++ b/tendermint-rs/src/block/height.rs @@ -9,7 +9,7 @@ use std::{ /// Block height for a particular chain (i.e. number of blocks created since /// the chain began) #[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct Height(pub u64); +pub struct Height(u64); impl Height { /// Convert `u64` to block height. diff --git a/tendermint-rs/src/rpc/client.rs b/tendermint-rs/src/rpc/client.rs index cb77e0b..6e9e294 100644 --- a/tendermint-rs/src/rpc/client.rs +++ b/tendermint-rs/src/rpc/client.rs @@ -1,7 +1,7 @@ //! Tendermint RPC client use crate::{ - abci::Transaction, + abci::{self, Transaction}, block::Height, net, rpc::{self, endpoint::*, Error, Response}, @@ -33,6 +33,22 @@ impl Client { Ok(self.perform(abci_info::Request)?.response) } + /// `/abci_query`: query the ABCI application + pub fn abci_query( + &self, + path: Option, + data: D, + height: Option, + prove: bool, + ) -> Result + where + D: Into>, + { + Ok(self + .perform(abci_query::Request::new(path, data, height, prove))? + .response) + } + /// `/block`: get block at a given height. pub fn block(&self, height: H) -> Result where diff --git a/tendermint-rs/src/rpc/endpoint.rs b/tendermint-rs/src/rpc/endpoint.rs index ad612a2..25a5719 100644 --- a/tendermint-rs/src/rpc/endpoint.rs +++ b/tendermint-rs/src/rpc/endpoint.rs @@ -1,6 +1,7 @@ //! Tendermint JSONRPC endpoints pub mod abci_info; +pub mod abci_query; pub mod block; pub mod block_results; pub mod blockchain; diff --git a/tendermint-rs/src/rpc/endpoint/abci_query.rs b/tendermint-rs/src/rpc/endpoint/abci_query.rs new file mode 100644 index 0000000..6f2ca7b --- /dev/null +++ b/tendermint-rs/src/rpc/endpoint/abci_query.rs @@ -0,0 +1,95 @@ +//! `/abci_query` endpoint JSONRPC wrapper + +use crate::{ + abci::{Code, Log, Path, Proof}, + block, rpc, serializers, +}; +use serde::{Deserialize, Serialize}; + +/// Query the ABCI application for information +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Request { + /// Path to the data + path: Option, + + /// Data to query + data: Vec, + + /// Block height + height: Option, + + /// Include proof in response + prove: bool, +} + +impl Request { + /// Create a new ABCI query request + pub fn new(path: Option, data: D, height: Option, prove: bool) -> Self + where + D: Into>, + { + Self { + path, + data: data.into(), + height, + prove, + } + } +} + +impl rpc::Request for Request { + type Response = Response; + + fn method(&self) -> rpc::Method { + rpc::Method::AbciInfo + } +} + +/// ABCI query response wrapper +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + /// ABCI query results + pub response: AbciQuery, +} + +impl rpc::Response for Response {} + +/// ABCI query results +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AbciQuery { + /// Response code + pub code: Code, + + /// Log value + pub log: Log, + + /// Info value + pub info: Option, + + /// Index + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "serializers::serialize_i64", + deserialize_with = "serializers::parse_i64" + ) + )] + pub index: i64, + + /// Key + // TODO(tarcieri): parse to Vec? + pub key: String, + + /// Value + // TODO(tarcieri): parse to Vec? + pub value: String, + + /// Proof (if requested) + pub proof: Option, + + /// Block height + pub height: block::Height, + + /// Codespace + pub codespace: Option, +} diff --git a/tendermint-rs/tests/integration.rs b/tendermint-rs/tests/integration.rs index 0e51504..bb9c565 100644 --- a/tendermint-rs/tests/integration.rs +++ b/tendermint-rs/tests/integration.rs @@ -22,6 +22,12 @@ mod rpc { assert_eq!(&abci_info.data, "GaiaApp"); } + /// `/abci_query` endpoint + #[test] + fn abci_query() { + // TODO(tarcieri): write integration test for this endpoint + } + /// `/block` endpoint #[test] fn block() { diff --git a/tendermint-rs/tests/rpc.rs b/tendermint-rs/tests/rpc.rs index 7e4931d..bc5b50b 100644 --- a/tendermint-rs/tests/rpc.rs +++ b/tendermint-rs/tests/rpc.rs @@ -23,6 +23,15 @@ mod endpoints { assert_eq!(response.last_block_height.value(), 488120); } + #[test] + fn abci_query() { + let response = endpoint::abci_query::Response::from_json(&read_json_fixture("abci_query")) + .unwrap() + .response; + + assert_eq!(response.height.value(), 1); + } + #[test] fn block() { let response = endpoint::block::Response::from_json(&read_json_fixture("block")).unwrap(); diff --git a/tendermint-rs/tests/support/rpc/abci_query.json b/tendermint-rs/tests/support/rpc/abci_query.json new file mode 100644 index 0000000..d184fbe --- /dev/null +++ b/tendermint-rs/tests/support/rpc/abci_query.json @@ -0,0 +1,15 @@ +{ + "jsonrpc": "2.0", + "id": "", + "result": { + "response": { + "log": "exists", + "height": "1", + "proof": "010114FED0DAD959F36091AD761C922ABA3CBF1D8349990101020103011406AA2262E2F448242DF2C2607C3CDC705313EE3B0001149D16177BC71E445476174622EA559715C293740C", + "value": "61626364", + "key": "61626364", + "index": "-1", + "code": "0" + } + } +}