Skip to content

Commit

Permalink
Merge pull request #6 from interchainio/validator_set
Browse files Browse the repository at this point in the history
Validator Set Hash
  • Loading branch information
ebuchman committed Aug 18, 2019
2 parents f792231 + ed79706 commit 6f6015d
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mod serializers;
pub mod signature;
pub mod time;
mod timeout;
#[cfg(feature = "amino-types")]
pub mod validator;
mod version;
pub mod vote;
Expand Down
9 changes: 6 additions & 3 deletions src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ use sha2::{Digest, Sha256};
/// Size of Merkle root hash
pub const HASH_SIZE: usize = 32;

/// Hash is the output of the cryptographic digest function
pub type Hash = [u8; HASH_SIZE];

/// Compute a simple Merkle root from the arbitrary sized byte slices
pub fn simple_hash_from_byte_slices(byte_slices: &[&[u8]]) -> [u8; HASH_SIZE] {
pub fn simple_hash_from_byte_slices(byte_slices: &[&[u8]]) -> Hash {
let length = byte_slices.len();
match length {
0 => [0; HASH_SIZE],
Expand All @@ -31,7 +34,7 @@ fn get_split_point(length: usize) -> usize {
}

// tmhash(0x00 || leaf)
fn leaf_hash(bytes: &[u8]) -> [u8; HASH_SIZE] {
fn leaf_hash(bytes: &[u8]) -> Hash {
// make a new array starting with 0 and copy in the bytes
let mut leaf_bytes = Vec::with_capacity(bytes.len() + 1);
leaf_bytes.push(0x00);
Expand All @@ -47,7 +50,7 @@ fn leaf_hash(bytes: &[u8]) -> [u8; HASH_SIZE] {
}

// tmhash(0x01 || left || right)
fn inner_hash(left: &[u8], right: &[u8]) -> [u8; HASH_SIZE] {
fn inner_hash(left: &[u8], right: &[u8]) -> Hash {
// make a new array starting with 0x1 and copy in the bytes
let mut inner_bytes = Vec::with_capacity(left.len() + right.len() + 1);
inner_bytes.push(0x01);
Expand Down
8 changes: 8 additions & 0 deletions src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ impl PublicKey {
}
}

/// Serialize this key as raw bytes
pub fn as_bytes(self) -> Vec<u8> {
match self {
PublicKey::Ed25519(ref pk) => pk.as_bytes(),
PublicKey::Secp256k1(ref pk) => pk.as_bytes(),
}.to_vec()
}

/// Serialize this key as amino bytes
pub fn to_amino_bytes(self) -> Vec<u8> {
match self {
Expand Down
122 changes: 118 additions & 4 deletions src/validator.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
//! Tendermint validators

use crate::{account, vote, PublicKey};
use crate::{account, merkle, vote, PublicKey};
use prost::Message;
#[cfg(feature = "serde")]
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "rpc")]
use subtle_encoding::base64;

/// Validator set contains a vector of validators
#[derive(Debug)]
pub struct Set {
validators: Vec<Info>,
}

impl Set {
/// Create a new validator set.
/// vals is mutable so it can be sorted by address.
pub fn new(mut vals: Vec<Info>) -> Set {
vals.sort_by(|v1, v2| v1.address.partial_cmp(&v2.address).unwrap());
Set { validators: vals }
}

/// Compute the Merkle root of the validator set
pub fn hash(self) -> merkle::Hash {
// We need to get from Vec<Info> to &[&[u8]] so we can call simple_hash_from_byte_slices.
// This looks like: Vec<Info> -> Vec<Vec<u8>> -> Vec<&[u8]> -> &[&[u8]]
// Can we simplify this?
// Perhaps simple_hash_from_byteslices should take Vec<Vec<u8>> directly ?
let validator_bytes: Vec<Vec<u8>> = self
.validators
.into_iter()
.map(|x| x.hash_bytes())
.collect();
let validator_byteslices: Vec<&[u8]> = (&validator_bytes)
.into_iter()
.map(|x| x.as_slice())
.collect();
merkle::simple_hash_from_byte_slices(validator_byteslices.as_slice())
}
}

/// Validator information
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug)]
pub struct Info {
/// Validator account address
pub address: account::Id,
Expand All @@ -23,6 +56,61 @@ pub struct Info {
pub proposer_priority: Option<ProposerPriority>,
}

impl From<PublicKey> for account::Id {
fn from(pub_key: PublicKey) -> account::Id {
match pub_key {
PublicKey::Ed25519(pk) => account::Id::from(pk),
PublicKey::Secp256k1(pk) => account::Id::from(pk),
}
}
}

impl Info {
/// Create a new validator.
pub fn new(pk: PublicKey, vp: vote::Power) -> Info {
Info {
address: account::Id::from(pk),
pub_key: pk,
voting_power: vp,
proposer_priority: None,
}
}
}

/// InfoHashable is the form of the validator used for computing the Merkle tree.
/// It does not include the address, as that is redundant with the pubkey,
/// nor the proposer priority, as that changes with every block even if the validator set didn't.
/// It contains only the pubkey and the voting power, and is amino encoded.
/// TODO: currently only works for Ed25519 pubkeys
#[derive(Clone, PartialEq, Message)]
struct InfoHashable {
#[prost(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")]
pub pub_key: Vec<u8>,
#[prost(uint64, tag = "2")]
voting_power: u64,
}

/// Info -> InfoHashable
impl From<&Info> for InfoHashable {
fn from(info: &Info) -> InfoHashable {
InfoHashable {
pub_key: info.pub_key.as_bytes(),
voting_power: info.voting_power.value(),
}
}
}

// returns the bytes to be hashed into the Merkle tree -
// the leaves of the tree. this is an amino encoding of the
// pubkey and voting power, so it includes the pubkey's amino prefix.
impl Info {
fn hash_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::new();
InfoHashable::from(self).encode(&mut bytes).unwrap();
bytes
}
}

/// Proposer priority
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ProposerPriority(i64);
Expand Down Expand Up @@ -60,7 +148,7 @@ impl Serialize for ProposerPriority {

/// Updates to the validator set
#[cfg(feature = "rpc")]
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Update {
/// Validator public key
#[serde(deserialize_with = "deserialize_public_key")]
Expand Down Expand Up @@ -99,3 +187,29 @@ where
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use subtle_encoding::hex;

// make a validator from a hex ed25519 pubkey and a voting power
fn make_validator(pk_string: &str, vp: u64) -> Info {
let pk = PublicKey::from_raw_ed25519(&hex::decode_upper(pk_string).unwrap()).unwrap();
Info::new(pk, vote::Power::new(vp))
}

#[test]
fn test_validator_set() {
// test vector generated by Go code
let v1 = make_validator("F349539C7E5EF7C49549B09C4BFC2335318AB0FE51FBFAA2433B4F13E816F4A7", 148151478422287875);
let v2 = make_validator("5646AA4C706B7AF73768903E77D117487D2584B76D83EB8FF287934EE7758AFC", 158095448483785107);
let v3 = make_validator("EB6B732C4BD86B5FA3F3BC3DB688DA0ED182A7411F81C2D405506B298FC19E52", 770561664770006272);
let hash_string = "B92B4474567A1B57969375C13CF8129AA70230642BD7FB9FB2CC316E87CE01D7";
let hash_expect = &hex::decode_upper(hash_string).unwrap();

let val_set = Set::new(vec![v1, v2, v3]);
let hash = val_set.hash();
assert_eq!(hash_expect, &hash);
}
}
5 changes: 5 additions & 0 deletions src/vote/power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
pub struct Power(u64);

impl Power {
/// Create a new Power
pub fn new(p: u64) -> Power {
Power(p)
}

/// Get the current voting power
pub fn value(self) -> u64 {
self.0
Expand Down

0 comments on commit 6f6015d

Please sign in to comment.