diff --git a/tendermint-rs/src/abci/gas.rs b/tendermint-rs/src/abci/gas.rs index ed2bc75..7010ce4 100644 --- a/tendermint-rs/src/abci/gas.rs +++ b/tendermint-rs/src/abci/gas.rs @@ -16,6 +16,13 @@ use std::{ #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] pub struct Gas(u64); +impl Gas { + /// Get the inner integer value + pub fn value(self) -> u64 { + self.0 + } +} + impl From for Gas { fn from(amount: u64) -> Gas { Gas(amount) diff --git a/tendermint-rs/src/abci/responses.rs b/tendermint-rs/src/abci/responses.rs index a8fa26a..46cb8f0 100644 --- a/tendermint-rs/src/abci/responses.rs +++ b/tendermint-rs/src/abci/responses.rs @@ -2,7 +2,7 @@ use super::{code::Code, data::Data, gas::Gas, info::Info, log::Log}; use crate::{consensus, validator}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use std::fmt::{self, Display}; /// Responses for ABCI calls which occur during block processing. @@ -11,24 +11,33 @@ use std::fmt::{self, Display}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Responses { /// Deliver TX response. - // TODO(tarcieri): remove the `rename` attribute when this lands upstream: + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: // - #[serde(rename = "DeliverTx")] - pub deliver_tx: Option, + #[serde(alias = "DeliverTx")] + #[serde(default, deserialize_with = "deserialize_deliver_tx")] + pub deliver_tx: Vec, /// Begin block response. - // TODO(tarcieri): remove the `rename` attribute when this lands upstream: + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: // - #[serde(rename = "BeginBlock")] + #[serde(alias = "BeginBlock")] pub begin_block: Option, /// End block response. - // TODO(tarcieri): remove the `rename` attribute when this lands upstream: + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: // - #[serde(rename = "EndBlock")] + #[serde(alias = "EndBlock")] pub end_block: Option, } +/// Return an empty vec in the event `deliver_tx` is `null` +fn deserialize_deliver_tx<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + /// Deliver TX response. /// /// This type corresponds to the `ResponseDeliverTx` proto from: @@ -87,7 +96,8 @@ pub struct BeginBlock { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EndBlock { /// Validator updates - pub validator_updates: Option>, + #[serde(deserialize_with = "deserialize_validator_updates")] + pub validator_updates: Vec, /// New consensus params pub consensus_param_updates: Option, @@ -97,6 +107,16 @@ pub struct EndBlock { pub tags: Vec, } +/// Return an empty vec in the event `validator_updates` is `null` +fn deserialize_validator_updates<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + /// Tags #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Tag { diff --git a/tendermint-rs/src/validator.rs b/tendermint-rs/src/validator.rs index e612a68..9af9c34 100644 --- a/tendermint-rs/src/validator.rs +++ b/tendermint-rs/src/validator.rs @@ -3,6 +3,8 @@ use crate::{account, vote, PublicKey}; #[cfg(feature = "serde")] use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "rpc")] +use subtle_encoding::base64; /// Validator information #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -57,12 +59,44 @@ impl Serialize for ProposerPriority { } /// Updates to the validator set -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug)] +#[cfg(feature = "rpc")] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Update { /// Validator public key + #[serde(deserialize_with = "deserialize_public_key")] pub pub_key: PublicKey, /// New voting power pub power: vote::Power, } + +/// Validator updates use a slightly different public key format than the one +/// implemented in `tendermint::PublicKey`. +/// +/// This is an internal thunk type to parse the `validator_updates` format and +/// then convert to `tendermint::PublicKey`. +/// Public keys allowed in Tendermint protocols +#[cfg(feature = "rpc")] +#[derive(Serialize, Deserialize)] +#[serde(tag = "type", content = "data")] +enum PK { + /// Ed25519 keys + #[serde(rename = "ed25519")] + Ed25519(String), +} + +#[cfg(feature = "rpc")] +fn deserialize_public_key<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + match &PK::deserialize(deserializer)? { + PK::Ed25519(base64_value) => { + let bytes = + base64::decode(base64_value).map_err(|e| D::Error::custom(format!("{}", e)))?; + + PublicKey::from_raw_ed25519(&bytes) + .ok_or_else(|| D::Error::custom("error parsing Ed25519 key")) + } + } +} diff --git a/tendermint-rs/tests/rpc.rs b/tendermint-rs/tests/rpc.rs index ff50d31..7e4931d 100644 --- a/tendermint-rs/tests/rpc.rs +++ b/tendermint-rs/tests/rpc.rs @@ -44,6 +44,43 @@ mod endpoints { assert_eq!(last_commit.precommits.len(), 65); } + #[test] + fn block_results() { + let response = + endpoint::block_results::Response::from_json(&read_json_fixture("block_results")) + .unwrap(); + assert_eq!(response.height.value(), 1814); + + let tendermint::abci::Responses { + deliver_tx, + begin_block: _, + end_block, + } = response.results; + + let log_json = &deliver_tx[0].log.as_ref().unwrap().parse_json().unwrap(); + let log_json_value = &log_json.as_array().as_ref().unwrap()[0]; + + assert_eq!(log_json_value["msg_index"].as_str().unwrap(), "0"); + assert_eq!(log_json_value["success"].as_bool().unwrap(), true); + + assert_eq!(deliver_tx[0].gas_wanted.value(), 200000); + assert_eq!(deliver_tx[0].gas_used.value(), 105662); + + let tag = deliver_tx[0] + .tags + .iter() + .find(|t| t.key.eq("ZGVzdGluYXRpb24tdmFsaWRhdG9y")) + .unwrap(); + + assert_eq!( + &tag.value, + "Y29zbW9zdmFsb3BlcjFlaDVtd3UwNDRnZDVudGtrYzJ4Z2ZnODI0N21nYzU2Zno0c2RnMw==" + ); + + let validator_update = &end_block.as_ref().unwrap().validator_updates[0]; + assert_eq!(validator_update.power.value(), 1233243); + } + #[test] fn blockchain() { let response = diff --git a/tendermint-rs/tests/support/rpc/block_results.json b/tendermint-rs/tests/support/rpc/block_results.json new file mode 100644 index 0000000..ade9b07 --- /dev/null +++ b/tendermint-rs/tests/support/rpc/block_results.json @@ -0,0 +1,94 @@ +{ + "jsonrpc": "2.0", + "id": "", + "result": { + "height": "1814", + "results": { + "DeliverTx": [ + { + "log": "[{\"msg_index\":\"0\",\"success\":true,\"log\":\"\"}]", + "gasWanted": "200000", + "gasUsed": "105662", + "tags": [ + { + "key": "YWN0aW9u", + "value": "ZGVsZWdhdGU=" + }, + { + "key": "ZGVsZWdhdG9y", + "value": "Y29zbW9zMW53eWV5cXVkenJ1NWw2NGU4M2RubXE3OXE0c3Rxejdmd2w1djVh" + }, + { + "key": "ZGVzdGluYXRpb24tdmFsaWRhdG9y", + "value": "Y29zbW9zdmFsb3BlcjFlaDVtd3UwNDRnZDVudGtrYzJ4Z2ZnODI0N21nYzU2Zno0c2RnMw==" + } + ] + }, + { + "log": "[{\"msg_index\":\"0\",\"success\":true,\"log\":\"\"}]", + "gasWanted": "99164", + "gasUsed": "99164", + "tags": [ + { + "key": "YWN0aW9u", + "value": "ZGVsZWdhdGU=" + }, + { + "key": "ZGVsZWdhdG9y", + "value": "Y29zbW9zMTBhN2V2eXlkY2s0Mm5odGE5M3RubXY3eXU0aGFxenQ5NHh5dTU0" + }, + { + "key": "ZGVzdGluYXRpb24tdmFsaWRhdG9y", + "value": "Y29zbW9zdmFsb3BlcjF1cnRweHdmdXU4azU3YXF0MGg1emhzdm1qdDRtMm1tZHIwanV6Zw==" + } + ] + }, + { + "log": "[{\"msg_index\":\"0\",\"success\":true,\"log\":\"\"}]", + "gasWanted": "200000", + "gasUsed": "106515", + "tags": [ + { + "key": "YWN0aW9u", + "value": "ZGVsZWdhdGU=" + }, + { + "key": "ZGVsZWdhdG9y", + "value": "Y29zbW9zMXFtcmNqenNrZ3Rsd21mczlwcWRyZnBtcDVsNWM4cDVyM3kzZTl0" + }, + { + "key": "ZGVzdGluYXRpb24tdmFsaWRhdG9y", + "value": "Y29zbW9zdmFsb3BlcjFzeHg5bXN6dmUwZ2FlZHo1bGQ3cWRramtmdjh6OTkyYXg2OWswOA==" + } + ] + } + ], + "EndBlock": { + "validator_updates": [ + { + "pub_key": { + "type": "ed25519", + "data": "lObsqlAjmPsnBfBE+orb8vBbKrH2G5VskSUlAq/YcXc=" + }, + "power": "1233243" + }, + { + "pub_key": { + "type": "ed25519", + "data": "PflSgb+lC1GI22wc6N/54cNzD7KSYQyCWR5LuQxjYVY=" + }, + "power": "1194975" + }, + { + "pub_key": { + "type": "ed25519", + "data": "AmPqEmF5YNmlv2vu8lEcDeQ3hyR+lymnqx2VixdMEzA=" + }, + "power": "12681" + } + ] + }, + "BeginBlock": {} + } + } +}