Skip to content

Commit

Permalink
Light client: trait impls (#36)
Browse files Browse the repository at this point in the history
* Use concrete basic types for time, hash, bytes, validator id

* cargo fmt

* into_iter unnecessarily moves the Vec (clippy)

* Resolve a bunch of clippy warnings about:
https://rust-lang.github.io/rust-clippy/master/#try_err

* make the lite client a module instead of a separate crate:

This is just the simplest way to move forward implementing the traits of
the lite package. There are alternatives:
We do not want a create a circular dependency between lite and
tendermint (which does not even compile). To avoid this we could:
1) make lite a module of tendermint, or, 2) replicate a lot of the types
of the tendermint crate in the lite package (e.g. Time, Ids etc), or 3)
have a dependency from tendermint to the lite package only (but then the
trait impls do need to live in the lite crate instead with the types in
the tendermint crate directly).

* add amino type for header Version

* working towards hashing the header: conversion between fields and their
respective amino types, and, directly encode some

* Header::hash works now (tested against JSON fixture only and yields:
 2DC46AD76277039F1B65FE3C7F2064788B1C12FE701CFE7EC93F751586A48781)

will add a test after #42 gets merged

* remove todo

* implement lite::Validator trait

* implement lite::ValidatorSet trait

* implement lite::Vote trait

* bring back BufMut; fix clippy warnings

* Address some review comments:

 - Consistency: Rename encode_bytes to bytes_enc
 - remove redundant return statement

* Consistency: From<&vote::Vote> same order as the fields in the struct

* deduplicate encoding a hash: add a helper

* deduplicate encoding hashes and varints: add a helper for each

* simplify sign_bytes to return Vec<u8> instead of passing in a BufMut

* Add test for lite::Header.hash() in rpc tests

* wrap the CanonicalVote in a struct that contains the validator ID
and the signature
  • Loading branch information
liamsi committed Dec 10, 2019
1 parent 449fc98 commit b30991f
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 11 deletions.
2 changes: 2 additions & 0 deletions tendermint/src/amino_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod remote_error;
pub mod signature;
pub mod time;
pub mod validate;
pub mod version;
pub mod vote;

pub use self::{
Expand All @@ -22,5 +23,6 @@ pub use self::{
signature::{SignableMsg, SignedMsgType},
time::TimeMsg,
validate::ConsensusMessage,
version::ConsensusVersion,
vote::{SignVoteRequest, SignedVoteResponse, AMINO_NAME as VOTE_AMINO_NAME},
};
29 changes: 29 additions & 0 deletions tendermint/src/amino_types/block_id.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*};
use crate::block::parts;
use crate::{
block,
error::Error,
Expand All @@ -14,6 +15,12 @@ pub struct BlockId {
pub parts_header: Option<PartsSetHeader>,
}

impl BlockId {
pub fn new(hash: Vec<u8>, parts_header: Option<PartsSetHeader>) -> Self {
BlockId { hash, parts_header }
}
}

impl block::ParseId for BlockId {
fn parse_block_id(&self) -> Result<block::Id, Error> {
let hash = Hash::new(hash::Algorithm::Sha256, &self.hash)?;
Expand All @@ -25,6 +32,16 @@ impl block::ParseId for BlockId {
}
}

impl From<&block::Id> for BlockId {
fn from(bid: &block::Id) -> Self {
let bid_hash = bid.hash.as_bytes().unwrap().to_vec();
match &bid.parts {
Some(parts) => BlockId::new(bid_hash, Some(PartsSetHeader::from(parts))),
None => BlockId::new(bid_hash, None),
}
}
}

impl ConsensusMessage for BlockId {
fn validate_basic(&self) -> Result<(), ValidationError> {
// Hash can be empty in case of POLBlockID in Proposal.
Expand Down Expand Up @@ -64,6 +81,18 @@ pub struct PartsSetHeader {
pub hash: Vec<u8>,
}

impl PartsSetHeader {
pub fn new(total: i64, hash: Vec<u8>) -> Self {
PartsSetHeader { total, hash }
}
}

impl From<&parts::Header> for PartsSetHeader {
fn from(parts: &parts::Header) -> Self {
PartsSetHeader::new(parts.total as i64, parts.hash.as_bytes().unwrap().to_vec())
}
}

impl PartsSetHeader {
fn parse_parts_header(&self) -> Option<block::parts::Header> {
Hash::new(hash::Algorithm::Sha256, &self.hash)
Expand Down
21 changes: 21 additions & 0 deletions tendermint/src/amino_types/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::block::*;

#[derive(Clone, Message)]
pub struct ConsensusVersion {
/// Block version
#[prost(uint64, tag = "1")]
pub block: u64,

/// App version
#[prost(uint64, tag = "2")]
pub app: u64,
}

impl From<&header::Version> for ConsensusVersion {
fn from(version: &header::Version) -> Self {
ConsensusVersion {
block: version.block,
app: version.app,
}
}
}
28 changes: 27 additions & 1 deletion tendermint/src/amino_types/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ use super::{
validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*},
SignedMsgType,
};
use crate::amino_types::PartsSetHeader;
use crate::{
block::{self, ParseId},
chain, consensus,
error::Error,
vote, Hash,
};
use bytes::BufMut;
use prost::{error::EncodeError, Message};
Expand Down Expand Up @@ -50,6 +52,30 @@ impl Vote {
}
}

impl From<&vote::Vote> for Vote {
fn from(vote: &vote::Vote) -> Self {
Vote {
vote_type: vote.vote_type.to_u32(),
height: vote.height.value() as i64, // TODO potential overflow :-/
round: vote.round as i64,
block_id: Some(BlockId {
hash: match vote.block_id.hash {
Hash::Sha256(h) => h.to_vec(),
_ => vec![],
},
parts_header: match &vote.block_id.parts {
Some(parts) => Some(PartsSetHeader::from(parts)),
None => None,
},
}),
timestamp: Some(TimeMsg::from(vote.timestamp)),
validator_address: vote.validator_address.as_bytes().to_vec(),
validator_index: vote.validator_index as i64, // TODO potential overflow :-/
signature: vote.signature.as_bytes().to_vec(),
}
}
}

impl block::ParseHeight for Vote {
fn parse_block_height(&self) -> Result<block::Height, Error> {
block::Height::try_from(self.height)
Expand Down Expand Up @@ -103,7 +129,7 @@ impl block::ParseHeight for CanonicalVote {
}

impl CanonicalVote {
fn new(vote: Vote, chain_id: &str) -> CanonicalVote {
pub fn new(vote: Vote, chain_id: &str) -> CanonicalVote {
CanonicalVote {
vote_type: vote.vote_type,
chain_id: chain_id.to_string(),
Expand Down
80 changes: 77 additions & 3 deletions tendermint/src/block/header.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Block headers
use crate::{account, block, chain, lite, Hash, Time};
use crate::merkle::simple_hash_from_byte_slices;
use crate::{account, amino_types, block, chain, lite, Hash, Time};
use prost::Message;
use {
crate::serializers,
serde::{Deserialize, Serialize},
Expand Down Expand Up @@ -83,11 +85,62 @@ impl lite::Header for Header {
}

fn next_validators_hash(&self) -> Hash {
unimplemented!()
self.next_validators_hash
}

fn hash(&self) -> Hash {
unimplemented!()
let mut version_enc = vec![];
// TODO: if there is an encoding problem this will
// panic (as the golang code would):
// https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393
// https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/encoding_helper.go#L9:6
// Instead, handle errors gracefully here.
amino_types::ConsensusVersion::from(&self.version)
.encode(&mut version_enc)
.unwrap();
let height_enc = encode_varint(self.height.value());
let mut time_enc = vec![];
amino_types::TimeMsg::from(self.time)
.encode(&mut time_enc)
.unwrap();
let chain_id_bytes = self.chain_id.as_bytes();
let chain_id_enc = bytes_enc(&chain_id_bytes);
let num_tx_enc = encode_varint(self.num_txs);
let total_tx_enc = encode_varint(self.total_txs);
let mut last_block_id_enc = vec![];
amino_types::BlockId::from(&self.last_block_id)
.encode(&mut last_block_id_enc)
.unwrap();
let last_commit_hash_enc = encode_hash(self.last_commit_hash);
let data_hash_enc = encode_hash(self.data_hash);
let validator_hash_enc = encode_hash(self.validators_hash);
let next_validator_hash_enc = encode_hash(self.next_validators_hash);
let consensus_hash_enc = encode_hash(self.consensus_hash);
let app_hash_enc = encode_hash(self.app_hash);
let last_result_hash_enc = encode_hash(self.last_results_hash);
let evidence_hash_enc = encode_hash(self.evidence_hash);
let proposer_address_bytes = self.proposer_address.as_bytes();
let proposer_address_enc = bytes_enc(&proposer_address_bytes);

let mut byteslices: Vec<&[u8]> = vec![];
byteslices.push(version_enc.as_slice());
byteslices.push(chain_id_enc.as_slice());
byteslices.push(height_enc.as_slice());
byteslices.push(time_enc.as_slice());
byteslices.push(num_tx_enc.as_slice());
byteslices.push(total_tx_enc.as_slice());
byteslices.push(last_block_id_enc.as_slice());
byteslices.push(last_commit_hash_enc.as_slice());
byteslices.push(data_hash_enc.as_slice());
byteslices.push(validator_hash_enc.as_slice());
byteslices.push(next_validator_hash_enc.as_slice());
byteslices.push(consensus_hash_enc.as_slice());
byteslices.push(app_hash_enc.as_slice());
byteslices.push(last_result_hash_enc.as_slice());
byteslices.push(evidence_hash_enc.as_slice());
byteslices.push(proposer_address_enc.as_slice());

Hash::Sha256(simple_hash_from_byte_slices(byteslices.as_slice()))
}
}

Expand All @@ -111,3 +164,24 @@ pub struct Version {
)]
pub app: u64,
}

fn bytes_enc(bytes: &[u8]) -> Vec<u8> {
let mut chain_id_enc = vec![];
prost_amino::encode_length_delimiter(bytes.len(), &mut chain_id_enc).unwrap();
chain_id_enc.append(&mut bytes.to_vec());
chain_id_enc
}

fn encode_hash(hash: Hash) -> Vec<u8> {
let mut hash_enc = vec![];
if let Some(last_commit_hash_bytes) = hash.as_bytes() {
hash_enc = bytes_enc(last_commit_hash_bytes);
}
hash_enc
}

fn encode_varint(val: u64) -> Vec<u8> {
let mut val_enc = vec![];
prost_amino::encoding::encode_varint(val, &mut val_enc);
val_enc
}
5 changes: 5 additions & 0 deletions tendermint/src/chain/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ impl Id {
// so in theory this should never panic
str::from_utf8(byte_slice).unwrap()
}

/// Get the chain ID as a raw bytes.
pub fn as_bytes(&self) -> &[u8] {
&self.as_str().as_bytes()
}
}

impl AsRef<str> for Id {
Expand Down
3 changes: 2 additions & 1 deletion tendermint/src/lite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use self::types::*;
pub mod types;
pub mod verifier;

pub use self::types::*;
pub use self::verifier::*;
4 changes: 3 additions & 1 deletion tendermint/src/lite/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub trait Commit {
/// we ignore absent votes and votes for nil here.
/// NOTE: we may want to check signatures for nil votes,
/// and thus use an ternary enum here instead of the binary Option.
// TODO figure out if we want/can do an iter() method here that returns a
// VoteIterator instead of returning a vec
fn into_vec(&self) -> Vec<Option<Self::Vote>>;
}

Expand All @@ -96,7 +98,7 @@ pub trait Commit {
/// is only necessary to avoid slashing in the multi chain context.
pub trait Vote {
fn validator_id(&self) -> Id;
fn sign_bytes(&self) -> &[u8];
fn sign_bytes(&self) -> Vec<u8>;
fn signature(&self) -> &[u8];
}

Expand Down
7 changes: 5 additions & 2 deletions tendermint/src/lite/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ where
};

// check vote is valid from validator
if !val.verify_signature(vote.sign_bytes(), vote.signature()) {
let sign_bytes = vote.sign_bytes();
if !val.verify_signature(&sign_bytes, vote.signature()) {
return Err(Error::InvalidSignature);
}
signed_power += val.power();
Expand Down Expand Up @@ -176,7 +177,9 @@ where
};

// check vote is valid from validator
if !val.verify_signature(vote.sign_bytes(), vote.signature()) {
let sign_bytes = vote.sign_bytes();

if !val.verify_signature(&sign_bytes, vote.signature()) {
return Err(Error::InvalidSignature);
}
signed_power += val.power();
Expand Down
7 changes: 7 additions & 0 deletions tendermint/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ impl Signature {
Signature::Ed25519(_) => Algorithm::Ed25519,
}
}

/// Return the raw bytes of this signature
pub fn as_bytes(&self) -> &[u8] {
match self {
Signature::Ed25519(sig) => sig.as_bytes(),
}
}
}

impl<'de> Deserialize<'de> for Signature {
Expand Down
48 changes: 47 additions & 1 deletion tendermint/src/validator.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
//! Tendermint validators

use crate::{account, merkle, vote, PublicKey};
use crate::validator::signatory::{Signature, Verifier};
use crate::{account, lite, merkle, vote, Hash, PublicKey};
use prost::Message;
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
use signatory;
use signatory::ed25519;
use signatory_dalek;
use signatory_dalek::Ed25519Verifier;
use subtle_encoding::base64;

/// Validator set contains a vector of validators
Expand Down Expand Up @@ -30,6 +35,31 @@ impl Set {
}
}

impl lite::ValidatorSet for Set {
type Validator = Info;

fn hash(&self) -> Hash {
// TODO almost the same as above's pub fn hash(self) -> merkle::Hash
let validator_bytes: &Vec<Vec<u8>> =
&self.validators.iter().map(|x| x.hash_bytes()).collect();
let validator_byteslices: Vec<&[u8]> =
(&validator_bytes).iter().map(|x| x.as_slice()).collect();
Hash::Sha256(merkle::simple_hash_from_byte_slices(
validator_byteslices.as_slice(),
))
}

fn total_power(&self) -> u64 {
self.validators.iter().fold(0u64, |total, val_info| {
total + val_info.voting_power.value()
})
}

fn into_vec(&self) -> Vec<Self::Validator> {
self.validators.to_vec()
}
}

/// Validator information
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Info {
Expand All @@ -47,6 +77,22 @@ pub struct Info {
pub proposer_priority: Option<ProposerPriority>,
}

impl lite::Validator for Info {
fn power(&self) -> u64 {
self.voting_power.value()
}

fn verify_signature(&self, sign_bytes: &[u8], signature: &[u8]) -> bool {
if let Some(pk) = &self.pub_key.ed25519() {
let verifier = Ed25519Verifier::from(pk);
if let Ok(sig) = ed25519::Signature::from_bytes(signature) {
return verifier.verify(sign_bytes, &sig).is_ok();
}
}
false
}
}

impl From<PublicKey> for account::Id {
fn from(pub_key: PublicKey) -> account::Id {
match pub_key {
Expand Down
Loading

0 comments on commit b30991f

Please sign in to comment.