From fc36806a3ab7e2223f8e14ebad6c6726d400c7fa Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Apr 2019 09:11:53 -0700 Subject: [PATCH] tendermint-rs: /abci_info RPC endpoint --- tendermint-rs/src/block/commit.rs | 2 +- tendermint-rs/src/block/id.rs | 2 +- tendermint-rs/src/hash.rs | 2 +- tendermint-rs/src/rpc/endpoint.rs | 1 + tendermint-rs/src/rpc/endpoint/abci_info.rs | 70 +++++++++++++++++++ tendermint-rs/src/rpc/endpoint/net_info.rs | 4 +- tendermint-rs/src/transaction.rs | 6 +- tendermint-rs/tests/rpc.rs | 64 +++++++++-------- .../tests/support/rpc/abci_info.json | 11 +++ 9 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 tendermint-rs/src/rpc/endpoint/abci_info.rs create mode 100644 tendermint-rs/tests/support/rpc/abci_info.json diff --git a/tendermint-rs/src/block/commit.rs b/tendermint-rs/src/block/commit.rs index 5df8a44..cf297b6 100644 --- a/tendermint-rs/src/block/commit.rs +++ b/tendermint-rs/src/block/commit.rs @@ -4,7 +4,7 @@ use crate::{block, Vote}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -/// Last commit to a particular blockchain. +/// Last commit to a particular blockchain: +2/3 precommit signatures. /// /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/tendermint-rs/src/block/id.rs b/tendermint-rs/src/block/id.rs index 50e933e..f31418a 100644 --- a/tendermint-rs/src/block/id.rs +++ b/tendermint-rs/src/block/id.rs @@ -76,7 +76,7 @@ mod tests { fn parses_hex_strings() { let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap(); assert_eq!( - id.hash.as_slice().unwrap(), + id.hash.as_bytes().unwrap(), b"\x26\xC0\xA4\x1F\x32\x43\xC6\xBC\xD7\xAD\x2D\xFF\x8A\x8D\x83\xA7\ \x1D\x29\xD3\x07\xB5\x32\x6C\x22\x7F\x73\x4A\x1A\x51\x2F\xE4\x7D" .as_ref() diff --git a/tendermint-rs/src/hash.rs b/tendermint-rs/src/hash.rs index 11ff242..4b33ca6 100644 --- a/tendermint-rs/src/hash.rs +++ b/tendermint-rs/src/hash.rs @@ -66,7 +66,7 @@ impl Hash { } /// Borrow the `Hash` as a byte slice - pub fn as_slice(&self) -> Option<&[u8]> { + pub fn as_bytes(&self) -> Option<&[u8]> { match self { Hash::Sha256(ref h) => Some(h.as_ref()), Hash::Null => None, diff --git a/tendermint-rs/src/rpc/endpoint.rs b/tendermint-rs/src/rpc/endpoint.rs index 7303837..20d4144 100644 --- a/tendermint-rs/src/rpc/endpoint.rs +++ b/tendermint-rs/src/rpc/endpoint.rs @@ -1,5 +1,6 @@ //! Tendermint JSONRPC endpoints +pub mod abci_info; pub mod block; pub mod commit; pub mod genesis; diff --git a/tendermint-rs/src/rpc/endpoint/abci_info.rs b/tendermint-rs/src/rpc/endpoint/abci_info.rs new file mode 100644 index 0000000..ce45626 --- /dev/null +++ b/tendermint-rs/src/rpc/endpoint/abci_info.rs @@ -0,0 +1,70 @@ +//! `/abci_info` endpoint JSONRPC wrapper + +use crate::{block, hash, rpc, Hash}; +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; +use subtle_encoding::base64; + +/// Request ABCI information from a node +#[derive(Debug, Default)] +pub struct Request; + +impl rpc::Request for Request { + type Response = Response; + + fn path(&self) -> rpc::request::Path { + "/abci_info".parse().unwrap() + } +} + +/// ABCI information response +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + /// ABCI info + pub response: AbciInfoResponse, +} + +impl rpc::Response for Response {} + +/// ABCI information +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AbciInfoResponse { + /// Name of the application + pub data: String, + + /// Version + pub version: Option, + + /// Last block height + pub last_block_height: block::Height, + + /// Last app hash for the block + #[serde( + serialize_with = "serialize_app_hash", + deserialize_with = "parse_app_hash" + )] + pub last_block_app_hash: Hash, +} + +/// Parse Base64-encoded app hash +#[cfg(feature = "rpc")] +pub(crate) fn parse_app_hash<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) + .map_err(|e| D::Error::custom(format!("{}", e)))?; + + Hash::new(hash::Algorithm::Sha256, &bytes).map_err(|e| D::Error::custom(format!("{}", e))) +} + +/// Serialize Base64-encoded app hash +#[cfg(feature = "rpc")] +pub(crate) fn serialize_app_hash(hash: &Hash, serializer: S) -> Result +where + S: Serializer, +{ + hash.as_bytes() + .map(|bytes| String::from_utf8(base64::encode(bytes)).unwrap()) + .unwrap_or_default() + .serialize(serializer) +} diff --git a/tendermint-rs/src/rpc/endpoint/net_info.rs b/tendermint-rs/src/rpc/endpoint/net_info.rs index c010276..2f3df5b 100644 --- a/tendermint-rs/src/rpc/endpoint/net_info.rs +++ b/tendermint-rs/src/rpc/endpoint/net_info.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -/// Request the status of the node +/// Request network information from a node #[derive(Debug, Default)] pub struct Request; @@ -20,7 +20,7 @@ impl rpc::Request for Request { } } -/// Status responses +/// Net info responses #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Response { /// Are we presently listening? diff --git a/tendermint-rs/src/transaction.rs b/tendermint-rs/src/transaction.rs index 3ce9deb..47bc58a 100644 --- a/tendermint-rs/src/transaction.rs +++ b/tendermint-rs/src/transaction.rs @@ -27,14 +27,14 @@ impl Transaction { } /// Borrow the contents of this transaction as a byte slice - pub fn as_slice(&self) -> &[u8] { + pub fn as_bytes(&self) -> &[u8] { self.0.as_slice() } } impl AsRef<[u8]> for Transaction { fn as_ref(&self) -> &[u8] { - self.as_slice() + self.as_bytes() } } @@ -51,7 +51,7 @@ impl<'de> Deserialize<'de> for Transaction { #[cfg(feature = "serde")] impl Serialize for Transaction { fn serialize(&self, serializer: S) -> Result { - String::from_utf8(base64::encode(self.as_slice())) + String::from_utf8(base64::encode(self.as_bytes())) .unwrap() .serialize(serializer) } diff --git a/tendermint-rs/tests/rpc.rs b/tendermint-rs/tests/rpc.rs index ef20a4e..be23f11 100644 --- a/tendermint-rs/tests/rpc.rs +++ b/tendermint-rs/tests/rpc.rs @@ -10,17 +10,26 @@ mod endpoints { .unwrap() } + #[test] + fn abci_info() { + let response = endpoint::abci_info::Response::from_json(&read_json_fixture("abci_info")) + .unwrap() + .response; + + assert_eq!(response.data.as_str(), "GaiaApp"); + assert_eq!(response.last_block_height.value(), 488120); + } + #[test] fn block() { - let block_json = read_json_fixture("block"); - let block_response = endpoint::block::Response::from_json(&block_json).unwrap(); + let block = endpoint::block::Response::from_json(&read_json_fixture("block")).unwrap(); let tendermint::Block { header, data, evidence, last_commit, - } = block_response.block; + } = block.block; assert_eq!(header.version.block, 10); assert_eq!(header.chain_id.as_str(), "cosmoshub-1"); @@ -34,22 +43,22 @@ mod endpoints { #[test] fn commit() { - let commit_json = read_json_fixture("commit"); - let commit_response = endpoint::commit::Response::from_json(&commit_json).unwrap(); + let response = endpoint::commit::Response::from_json(&read_json_fixture("commit")).unwrap(); + let header = response.signed_header.header; - println!("commit_response: {:?}", commit_response); + assert_eq!(header.chain_id.as_ref(), "cosmoshub-1"); } #[test] fn genesis() { - let genesis_json = read_json_fixture("genesis"); - let genesis_response = endpoint::genesis::Response::from_json(&genesis_json).unwrap(); + let response = + endpoint::genesis::Response::from_json(&read_json_fixture("genesis")).unwrap(); let tendermint::Genesis { chain_id, consensus_params, .. - } = genesis_response.genesis; + } = response.genesis; assert_eq!(chain_id.as_str(), "cosmoshub-1"); assert_eq!(consensus_params.block_size.max_bytes, 150000); @@ -57,35 +66,30 @@ mod endpoints { #[test] fn net_info() { - let net_info_json = read_json_fixture("net_info"); - let net_info_response = endpoint::net_info::Response::from_json(&net_info_json).unwrap(); - - assert_eq!(net_info_response.n_peers, 2); - assert_eq!( - net_info_response.peers[0].node_info.network.as_str(), - "cosmoshub-1" - ); + let response = + endpoint::net_info::Response::from_json(&read_json_fixture("net_info")).unwrap(); + + assert_eq!(response.n_peers, 2); + assert_eq!(response.peers[0].node_info.network.as_str(), "cosmoshub-1"); } #[test] fn status() { - let status_json = read_json_fixture("status"); - let status_response = endpoint::status::Response::from_json(&status_json).unwrap(); - - assert_eq!(status_response.node_info.network.as_str(), "cosmoshub-1"); - assert_eq!( - status_response.sync_info.latest_block_height.value(), - 410744 - ); - assert_eq!(status_response.validator_info.voting_power.value(), 0); + let response = endpoint::status::Response::from_json(&read_json_fixture("status")).unwrap(); + + assert_eq!(response.node_info.network.as_str(), "cosmoshub-1"); + assert_eq!(response.sync_info.latest_block_height.value(), 410744); + assert_eq!(response.validator_info.voting_power.value(), 0); } #[test] fn validators() { - let validators_json = read_json_fixture("validators"); - let validators_response = - endpoint::validators::Response::from_json(&validators_json).unwrap(); + let response = + endpoint::validators::Response::from_json(&read_json_fixture("validators")).unwrap(); + + assert_eq!(response.block_height.value(), 42); + let validators = response.validators; - println!("validators: {:?}", validators_response); + assert_eq!(validators.len(), 65); } } diff --git a/tendermint-rs/tests/support/rpc/abci_info.json b/tendermint-rs/tests/support/rpc/abci_info.json new file mode 100644 index 0000000..39cb113 --- /dev/null +++ b/tendermint-rs/tests/support/rpc/abci_info.json @@ -0,0 +1,11 @@ +{ + "jsonrpc": "2.0", + "id": "", + "result": { + "response": { + "data": "GaiaApp", + "last_block_height": "488120", + "last_block_app_hash": "2LnCw0fN+Zq/gs5SOuya/GRHUmtWftAqAkTUuoxl4g4=" + } + } +}