Skip to content

Commit

Permalink
Add deserialization workaround for validator updates
Browse files Browse the repository at this point in the history
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 <connect@thanethomson.com>
  • Loading branch information
thanethomson committed Jan 31, 2022
1 parent e170520 commit fa9dfa7
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 34 deletions.
72 changes: 68 additions & 4 deletions tendermint/src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ProtobufPublicKeyWrapper> 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 <https://github.com/informalsystems/tendermint-rs/issues/1021> for
/// context.
// TODO(thane): Remove this once the serialization in Tendermint has been fixed.
pub fn deserialize_public_key<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
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::<ProtobufPublicKeyWrapper>(v).map(Into::into)
} else {
serde_json::from_value::<PublicKey>(v)
}
.map_err(serde::de::Error::custom)
}

impl Protobuf<RawPublicKey> for PublicKey {}

impl TryFrom<RawPublicKey> for PublicKey {
Expand Down Expand Up @@ -326,7 +390,7 @@ impl Serialize for Algorithm {
}

impl<'de> Deserialize<'de> for Algorithm {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use de::Error;
let s = String::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
Expand Down Expand Up @@ -356,7 +420,7 @@ where

fn deserialize_ed25519_base64<'de, D>(deserializer: D) -> Result<Ed25519, D::Error>
where
D: de::Deserializer<'de>,
D: Deserializer<'de>,
{
use de::Error;
let encoded = String::deserialize(deserializer)?;
Expand All @@ -367,7 +431,7 @@ where
#[cfg(feature = "secp256k1")]
fn deserialize_secp256k1_base64<'de, D>(deserializer: D) -> Result<Secp256k1, D::Error>
where
D: de::Deserializer<'de>,
D: Deserializer<'de>,
{
use de::Error;
let encoded = String::deserialize(deserializer)?;
Expand Down
60 changes: 30 additions & 30 deletions tendermint/src/validator.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<PublicKey, D::Error>
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 {

Expand Down Expand Up @@ -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::<Update>(FMT1).unwrap();
let update2 = serde_json::from_str::<Update>(FMT2).unwrap();

assert_eq!(u64::from(update1.power), 573929);
assert_eq!(update1, update2);
}
}

0 comments on commit fa9dfa7

Please sign in to comment.