Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Light client: trait impls #36

Merged
merged 21 commits into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4fef6a1
Use concrete basic types for time, hash, bytes, validator id
liamsi Sep 18, 2019
7a10257
cargo fmt
liamsi Sep 18, 2019
d9455af
into_iter unnecessarily moves the Vec (clippy)
liamsi Sep 19, 2019
1a232e5
Resolve a bunch of clippy warnings about:
liamsi Sep 22, 2019
d3ce237
make the lite client a module instead of a separate crate:
liamsi Sep 25, 2019
4b44d6a
add amino type for header Version
liamsi Sep 25, 2019
360a37a
working towards hashing the header: conversion between fields and their
liamsi Sep 25, 2019
3217eae
Header::hash works now (tested against JSON fixture only and yields:
liamsi Sep 25, 2019
4f53c44
remove todo
liamsi Sep 25, 2019
123adcb
implement lite::Validator trait
liamsi Sep 26, 2019
dba1303
implement lite::ValidatorSet trait
liamsi Sep 26, 2019
b7e9e46
implement lite::Vote trait
liamsi Sep 26, 2019
a0588a0
Merge branch 'bucky/lite' into lite_impl
ebuchman Nov 1, 2019
cc66c2b
bring back BufMut; fix clippy warnings
ebuchman Nov 1, 2019
37f9353
Address some review comments:
liamsi Nov 5, 2019
372cb4d
Consistency: From<&vote::Vote> same order as the fields in the struct
liamsi Nov 5, 2019
4140bbb
deduplicate encoding a hash: add a helper
liamsi Nov 5, 2019
9131c4e
deduplicate encoding hashes and varints: add a helper for each
liamsi Nov 5, 2019
15f8b02
simplify sign_bytes to return Vec<u8> instead of passing in a BufMut
liamsi Nov 5, 2019
e686c45
Add test for lite::Header.hash() in rpc tests
liamsi Nov 6, 2019
c941c95
wrap the CanonicalVote in a struct that contains the validator ID
liamsi Nov 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tendermint/src/amino_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod secret_connection;
pub mod signature;
pub mod time;
pub mod validate;
pub mod version;
pub mod vote;

pub use self::{
Expand All @@ -24,5 +25,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 @@ -6,10 +6,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 @@ -49,6 +51,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 :-/
block_id: Some(BlockId {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is block_id an Option here if we're always going to populate it with Some ? Is there a case elsewhere we use a None?

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't remember tbh. These amino types are quite old. Let me check if the encoding matches if remove the Option. I think it was sth related to prost and the #[prost(message)] attribute.

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, it looks like one can simply add the #[prost(message)] for a nested struct if it is an Option. Otherwise, it seems a little more effort is necessary. e.g. I locally changed the field's type to BlockId instead of Option<BlockId>. Then the compiler (due to prost-amino) will complain:

Click here for Rust chatty compiler output
error[E0599]: no method named `as_ref` found for type `amino_types::block_id::BlockId` in the current scope
  --> tendermint/src/amino_types/vote.rs:22:28
   |
22 | #[derive(Clone, PartialEq, Message)]
   |                            ^^^^^^^ method not found in `amino_types::block_id::BlockId`
   | 
  ::: tendermint/src/amino_types/block_id.rs:11:1
   |
11 | pub struct BlockId {
   | ------------------ method `as_ref` not found for this
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `as_ref`, perhaps you need to implement it:
           candidate #1: `std::convert::AsRef`

error[E0308]: mismatched types
  --> tendermint/src/amino_types/vote.rs:22:28
   |
22 | #[derive(Clone, PartialEq, Message)]
   |                            ^^^^^^^
   |                            |
   |                            expected struct `amino_types::block_id::BlockId`, found enum `std::option::Option`
   |                            this match expression has type `amino_types::block_id::BlockId`
   |
   = note: expected type `amino_types::block_id::BlockId`
              found type `std::option::Option<_>`

error[E0599]: no method named `get_or_insert_with` found for type `amino_types::block_id::BlockId` in the current scope
  --> tendermint/src/amino_types/vote.rs:22:28
   |
22 | #[derive(Clone, PartialEq, Message)]
   |                            ^^^^^^^ method not found in `amino_types::block_id::BlockId`
   | 
  ::: tendermint/src/amino_types/block_id.rs:11:1
   |
11 | pub struct BlockId {
   | ------------------ method `get_or_insert_with` not found for this

error[E0308]: mismatched types
  --> tendermint/src/amino_types/vote.rs:22:28
   |
22 | #[derive(Clone, PartialEq, Message)]
   |                            ^^^^^^^ expected struct `amino_types::block_id::BlockId`, found enum `std::option::Option`
   |
   = note: expected type `amino_types::block_id::BlockId`
              found type `std::option::Option<_>`

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0308, E0599.
For more information about an error, try `rustc --explain E0308`.
error: Could not compile `tendermint`.
´´´

</details>

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, it looks like this can be fixed by adding in a required attribute: https://github.com/danburkert/prost/blob/9551f2852e414df72339634087c46ce5a08e85b2/prost-derive/src/field/message.rs#L14-L56

I'm looking into that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the required attribute, e.g.:

#[prost(message, required, tag = "4")]
pub block_id: BlockId,

makes the compiler happy 😄but breaks some encoding test-vectors 🤔

thread 'amino_types::vote::tests::test_sign_bytes_compatibility' panicked at 'assertion failed: `(left == right)`
  left: `[15, 34, 0, 42, 11, 8, 128, 146, 184, 195, 152, 254, 255, 255, 255, 1]`,
 right: `[13, 42, 11, 8, 128, 146, 184, 195, 152, 254, 255, 255, 255, 1]`', tendermint/src/amino_types/vote.rs:317:9

Hmm, this feels somehow familiar. Will investigate further.

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, here is the catch:

In golang one can simply not initalize a field. Golang won't complain and implicitly init everything with default values. Then, Amino will treat this as sth. that can be skipped (and hence is optional in the protobuf2 sense which is the default in proto3).

Now on the rust side you have to init a field (unless you make it an option). If you make it an Option, the encoding behaves like amino (None will be not encoded at all). If you have a regular field (e.g. BlockId), and add a required attribute, prost(-amino) will treat as required (in the protobuf2 sense) and signal, we got fiel blah of type blubb but nothing in there (for blockId this is fieldnumber 4 and hence 4 << 3 | 2 = 34 followed by a 0x00 for indicating that this was empty; which is exactly the two bytes difference in above test-vector).

Amino actually does the same - enc. defaults as required as described above - but rolls back if it found out there is nothing (via that zero 0x00) unless told otherwise via writeEmpty (at least it seems to work now):
https://github.com/tendermint/go-amino/blob/fbf776258498dbb3aa6637ca82c7fe5c7255fd2f/binary-encode.go#L496-L524

Alt Text

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,
},
}),
round: vote.round as i64,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some reason this doesn't follow the same order as the fields in the struct, ie. the round before the block_id?

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. Thanks, fixed!

timestamp: Some(TimeMsg::from(vote.timestamp)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same re why this is an Option

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. Time is a bit different because we want to encode a particular time if None was passed in ("1970-01-01 00:00:00 +0000 UTC").

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_i64(self.height)
Expand Down Expand Up @@ -102,7 +128,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
93 changes: 90 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,89 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would there ever be an encoding error and what would it mean to handle it gracefully? Seems like the kind of thing where a panic is actually waranted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would there ever be an encoding error and what would it mean to handle it gracefully?

I can't think of a scenario where there could be and encoding error here (while encoding for hashing). If there is any case, it still feels wrong to me to panic instead of returning an error and let the caller decide if it is correct to panic (or handle the error in another way).
If we can not hash the header, this probably means a bug in the encoding library (or another bigger problem) and the caller should probably decide to panic anyways. I just feel it isn't the job of the hash method to decide.

amino_types::ConsensusVersion::from(&self.version)
.encode(&mut version_enc)
.unwrap();
let mut height_enc = vec![];
prost_amino::encoding::encode_varint(self.height.value(), &mut height_enc);
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 = encode_bytes(&chain_id_bytes);
let mut num_tx_enc = vec![];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use a function for this repeated pattern of encoding a varint

prost_amino::encoding::encode_varint(self.num_txs, &mut num_tx_enc);
let mut total_tx_enc = vec![];
prost_amino::encoding::encode_varint(self.total_txs, &mut total_tx_enc);
let mut last_block_id_enc = vec![];
amino_types::BlockId::from(&self.last_block_id)
.encode(&mut last_block_id_enc)
.unwrap();
let mut last_commit_hash_enc = vec![];
if let Some(last_commit_hash_bytes) = self.last_commit_hash.as_bytes() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we dedup this pattern too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (there is a helper encode_hash now).

last_commit_hash_enc = encode_bytes(last_commit_hash_bytes);
}
let mut data_hash_enc = vec![];
if let Some(data_hash_bytes) = self.data_hash.as_bytes() {
data_hash_enc = encode_bytes(data_hash_bytes);
}
let mut validator_hash_enc = vec![];
if let Some(validator_hash_bytes) = self.validators_hash.as_bytes() {
validator_hash_enc = encode_bytes(validator_hash_bytes);
}
let mut next_validator_hash_enc = vec![];
if let Some(next_validator_hash_bytes) = self.next_validators_hash.as_bytes() {
next_validator_hash_enc = encode_bytes(next_validator_hash_bytes);
}
let mut consensus_hash_enc = vec![];
if let Some(consensus_hash_bytes) = self.consensus_hash.as_bytes() {
consensus_hash_enc = encode_bytes(consensus_hash_bytes);
}
let mut app_hash_enc = vec![];
if let Some(app_hash_bytes) = self.app_hash.as_bytes() {
app_hash_enc = encode_bytes(app_hash_bytes);
}
let mut last_result_hash_enc = vec![];
if let Some(last_result_hash_bytes) = self.last_results_hash.as_bytes() {
last_result_hash_enc = encode_bytes(last_result_hash_bytes);
}
let mut evidence_hash_enc = vec![];
if let Some(evidence_hash_bytes) = self.evidence_hash.as_bytes() {
evidence_hash_enc = encode_bytes(evidence_hash_bytes);
}
let proposer_address_bytes = self.proposer_address.as_bytes();
let proposer_address_enc = encode_bytes(&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()))
Copy link
Member Author

@liamsi liamsi Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit too exhaustive but aims to do exactly the same as: https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393
(It actually does will add a few tests after #41 is merged)

This could probably be simplified substantially. Maybe, simple_hash_from_byte_slices had a similar logic as a Hasher in golang (Write the slices and then Sum to create the tree once and return the hash)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely seems excessive.

Good idea re a write based API. Though I don't think you usually see that for Merkle trees? At least the Merkle tree version needs to track the boundaries of each write, where as a normal hasher just concatenates everything together.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in e686c45

}
}

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

fn encode_bytes(bytes: &[u8]) -> Vec<u8> {
let mut chain_id_enc = vec![];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be called bytes_enc I guess

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other encoding methods are named encode_*. But it is a private helper function. Happy to rename it.

prost_amino::encode_length_delimiter(bytes.len(), &mut chain_id_enc).unwrap();
chain_id_enc.append(&mut bytes.to_vec());
chain_id_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::*;
7 changes: 6 additions & 1 deletion tendermint/src/lite/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::block::Height;
use crate::Hash;
#[allow(clippy::all)]
use crate::Time;
use bytes::BufMut;

/// TrustedState stores the latest state trusted by a lite client,
/// including the last header and the validator set to use to verify
Expand Down Expand Up @@ -81,6 +82,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
ebuchman marked this conversation as resolved.
Show resolved Hide resolved
fn into_vec(&self) -> Vec<Option<Self::Vote>>;
}

Expand All @@ -96,7 +99,9 @@ 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<B>(&self, sign_bytes: &mut B)
where
B: BufMut;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why did this need to take a buf mut instead of returning something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I did run into issues regarding the lifetime. I'll try to see if I can simply return &[u8] as previously.

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the problem is that the slice we want return here is created from a temp value inside that method (instead of sth borrowed from parameters of that method, in this case only self). e.g. this does not work:

    fn sign_bytes(&self) -> &[u8] {
        let mut sign_bytes = vec![];
        CanonicalVote::new(AminoVote::from(self), "TODO")
            .encode(&mut sign_bytes)
            .unwrap();
        sign_bytes.as_slice()
    }

On simple way to returning something would be to return a Vec<u8> instead of &[u8]. As it isn't reference (no borrowing involved) this should work without any problems.
A much more complex alternative might be to add a field that caches the the sign bytes. Not sure the latter works or would be worth the effort (?).
Passing in the BufMut is probably a quite efficient. But I understand, we want to stick to pure functions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd make sense to return a Vec<u8> here.

Copy link
Member Author

@liamsi liamsi Nov 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tarcieri. Changed to Vec<u8> for now.

fn signature(&self) -> &[u8];
}

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

// check vote is valid from validator
if !val.verify_signature(vote.sign_bytes(), vote.signature()) {
let mut sign_bytes = vec![];
vote.sign_bytes(&mut sign_bytes);
if !val.verify_signature(&sign_bytes, vote.signature()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 121-133 are duplicated in verify_commit_trusting() and here. Maybe a function that takes a validator and a vote and returns error be useful to both? I know in verify_commit_trusting() we do an extra check but that could be done before or after.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. There is a subtle difference in those lines though: in one case the validator is looked up in the set in the other it part of the looped elements (also some of the lines are part of the control flow via None => continue,). This could be deduplicated (completely) if we do not zip the iterators and do a lookup in both cases. I'll leave this as is for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a subtle difference in those lines though: in one case the validator is looked up in the set in the other it part of the looped elements (also some of the lines are part of the control flow via None => continue,).

Yes, I saw that but was thinking that once there is a validator and a vote the checks should be the same (most the loop body). But I looked closer and it's not much left in common to justify a separate function.

Another question, for verify_commit_full() we do assume: The vals and commit have a 1-to-1 correspondence; is this checked elsewhere? I was looking for a check that verifies that val.address == vote.validator_address. Or val.verify_signature() will take care of it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, for vals_iter.zip(commit_iter), I believe this works as we want as long as there is no nil validator in the set (I think this is the case but I have a hard time navigating the Rust code :( ) ...otherwise according to the doc: If the first iterator returns None, zip will short-circuit and next will not be called on the second iterator.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only check is that length matches. Also AFAIR the commits / validators are sorted. That means if the the len matches there is the desired correspondence. But I'll double check and follow up in #63. It seems this is more confusing than helpful maybe we should deduplicate and do the additional lookups instead.

return Err(Error::InvalidSignature);
}
signed_power += val.power();
Expand Down Expand Up @@ -176,7 +178,9 @@ where
};

// check vote is valid from validator
if !val.verify_signature(vote.sign_bytes(), vote.signature()) {
let mut sign_bytes = vec![];
vote.sign_bytes(&mut 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
Loading