From 46a4c2884dad45c65d37fd5d696dccf4cc1834e1 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 31 Jan 2022 17:35:17 -0500 Subject: [PATCH] backport: `block_results` RPC endpoint changes from #1061 (#1086) * Add convenience method to decode RPC requests from strings Signed-off-by: Thane Thomson * Add deserialization workaround for validator updates This introduces a (somewhat hacky, yet temporary) approach to being able to deserialize public keys in validator updates until such time that Tendermint addresses the problem. Signed-off-by: Thane Thomson * Add changelog entry Signed-off-by: Thane Thomson --- .../workarounds/1021-rpc-block_results.md | 4 ++ rpc/src/request.rs | 8 ++- tendermint/src/public_key.rs | 72 +++++++++++++++++-- tendermint/src/validator.rs | 60 ++++++++-------- 4 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 .changelog/unreleased/workarounds/1021-rpc-block_results.md diff --git a/.changelog/unreleased/workarounds/1021-rpc-block_results.md b/.changelog/unreleased/workarounds/1021-rpc-block_results.md new file mode 100644 index 000000000..b0b563110 --- /dev/null +++ b/.changelog/unreleased/workarounds/1021-rpc-block_results.md @@ -0,0 +1,4 @@ +- `[tendermint-rpc]` Allow deserialization of public keys from validator updates + from `block_results` endpoint in multiple JSON formats until this is fixed in + Tendermint + ([#1021](https://github.com/informalsystems/tendermint-rs/issues/1021)) diff --git a/rpc/src/request.rs b/rpc/src/request.rs index a2ed62085..fb6faad8f 100644 --- a/rpc/src/request.rs +++ b/rpc/src/request.rs @@ -1,7 +1,7 @@ //! JSON-RPC requests use super::{Id, Method, Version}; -use crate::prelude::*; +use crate::{prelude::*, Error}; use core::fmt::Debug; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -17,6 +17,12 @@ pub trait Request: Debug + DeserializeOwned + Serialize + Sized + Send { fn into_json(self) -> String { Wrapper::new(self).into_json() } + + /// Parse a JSON-RPC request from a JSON string. + fn from_string(s: impl AsRef<[u8]>) -> Result { + let wrapper: Wrapper = serde_json::from_slice(s.as_ref()).map_err(Error::serde)?; + Ok(wrapper.params) + } } /// Simple JSON-RPC requests which correlate with a single response from the diff --git a/tendermint/src/public_key.rs b/tendermint/src/public_key.rs index 7c0522432..0a7565af4 100644 --- a/tendermint/src/public_key.rs +++ b/tendermint/src/public_key.rs @@ -8,12 +8,13 @@ mod pub_key_request; mod pub_key_response; pub use pub_key_request::PubKeyRequest; pub use pub_key_response::PubKeyResponse; +use serde_json::Value; use crate::prelude::*; use crate::{error::Error, signature::Signature}; use core::convert::TryFrom; use core::{cmp::Ordering, fmt, ops::Deref, str::FromStr}; -use serde::{de, ser, Deserialize, Serialize}; +use serde::{de, ser, Deserialize, Deserializer, Serialize}; use subtle_encoding::{base64, bech32, hex}; use tendermint_proto::crypto::public_key::Sum; use tendermint_proto::crypto::PublicKey as RawPublicKey; @@ -56,6 +57,69 @@ pub enum PublicKey { Secp256k1(Secp256k1), } +// Internal thunk type to facilitate deserialization from the raw Protobuf data +// structure's JSON representation. +#[derive(Serialize, Deserialize)] +struct ProtobufPublicKeyWrapper { + #[serde(rename = "Sum")] + sum: ProtobufPublicKey, +} + +impl From for PublicKey { + fn from(wrapper: ProtobufPublicKeyWrapper) -> Self { + match wrapper.sum { + ProtobufPublicKey::Ed25519 { ed25519 } => PublicKey::Ed25519(ed25519), + #[cfg(feature = "secp256k1")] + ProtobufPublicKey::Secp256k1 { secp256k1 } => PublicKey::Secp256k1(secp256k1), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] // JSON custom serialization for priv_validator_key.json +enum ProtobufPublicKey { + #[serde(rename = "tendermint.crypto.PublicKey_Ed25519")] + Ed25519 { + #[serde( + serialize_with = "serialize_ed25519_base64", + deserialize_with = "deserialize_ed25519_base64" + )] + ed25519: Ed25519, + }, + + #[cfg(feature = "secp256k1")] + #[serde(rename = "tendermint.crypto.PublicKey_Secp256K1")] + Secp256k1 { + #[serde( + serialize_with = "serialize_secp256k1_base64", + deserialize_with = "deserialize_secp256k1_base64" + )] + secp256k1: Secp256k1, + }, +} + +/// Custom deserialization for public keys to handle multiple potential JSON +/// formats from Tendermint. +/// +/// See for +/// context. +// TODO(thane): Remove this once the serialization in Tendermint has been fixed. +pub fn deserialize_public_key<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let v = Value::deserialize(deserializer)?; + if v.as_object() + .map(|obj| obj.contains_key("Sum")) + .unwrap_or(false) + { + serde_json::from_value::(v).map(Into::into) + } else { + serde_json::from_value::(v) + } + .map_err(serde::de::Error::custom) +} + impl Protobuf for PublicKey {} impl TryFrom for PublicKey { @@ -326,7 +390,7 @@ impl Serialize for Algorithm { } impl<'de> Deserialize<'de> for Algorithm { - fn deserialize>(deserializer: D) -> Result { + fn deserialize>(deserializer: D) -> Result { use de::Error; let s = String::deserialize(deserializer)?; s.parse().map_err(D::Error::custom) @@ -356,7 +420,7 @@ where fn deserialize_ed25519_base64<'de, D>(deserializer: D) -> Result where - D: de::Deserializer<'de>, + D: Deserializer<'de>, { use de::Error; let encoded = String::deserialize(deserializer)?; @@ -367,7 +431,7 @@ where #[cfg(feature = "secp256k1")] fn deserialize_secp256k1_base64<'de, D>(deserializer: D) -> Result where - D: de::Deserializer<'de>, + D: Deserializer<'de>, { use de::Error; let encoded = String::deserialize(deserializer)?; diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index b4e17b6e5..58da81376 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -1,9 +1,9 @@ //! Tendermint validators -use serde::{de::Error as _, Deserialize, Deserializer, Serialize}; -use subtle_encoding::base64; +use serde::{Deserialize, Serialize}; use crate::prelude::*; +use crate::public_key::deserialize_public_key; use crate::{account, hash::Hash, merkle, vote, Error, PublicKey, Signature}; use core::convert::{TryFrom, TryInto}; @@ -326,34 +326,6 @@ pub struct Update { 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` in `deserialize_public_key` below. -#[derive(Serialize, Deserialize)] -#[serde(tag = "type", content = "data")] -enum Pk { - /// Ed25519 keys - #[serde(rename = "ed25519")] - Ed25519(String), -} - -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")) - } - } -} - #[cfg(test)] mod tests { @@ -438,4 +410,32 @@ mod tests { 148_151_478_422_287_875 + 158_095_448_483_785_107 + 770_561_664_770_006_272 ); } + + #[test] + fn deserialize_validator_updates() { + const FMT1: &str = r#"{ + "pub_key": { + "Sum": { + "type": "tendermint.crypto.PublicKey_Ed25519", + "value": { + "ed25519": "VqJCr3vjQdffcLIG6RMBl2MgXDFYNY6b3Joaa43gV3o=" + } + } + }, + "power": "573929" + }"#; + const FMT2: &str = r#"{ + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "VqJCr3vjQdffcLIG6RMBl2MgXDFYNY6b3Joaa43gV3o=" + }, + "power": "573929" + }"#; + + let update1 = serde_json::from_str::(FMT1).unwrap(); + let update2 = serde_json::from_str::(FMT2).unwrap(); + + assert_eq!(u64::from(update1.power), 573929); + assert_eq!(update1, update2); + } }