Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions ethcore/src/engines/authority_round/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,8 @@ impl Engine for AuthorityRound {

/// Attempt to seal the block internally.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned.
/// This operation is synchronous and may (quite reasonably) not be available, in which case
/// `Seal::None` will be returned.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
// first check to avoid generating signature most of the time
// (but there's still a race to the `compare_and_swap`)
Expand Down
49 changes: 0 additions & 49 deletions ethcore/src/engines/epoch_verifier.rs

This file was deleted.

196 changes: 193 additions & 3 deletions ethcore/src/engines/tendermint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ use error::{Error, BlockError};
use header::{Header, BlockNumber};
use builtin::Builtin;
use rlp::UntrustedRlp;
use ethkey::{recover, public_to_address, Signature};
use ethkey::{Message, public_to_address, recover, Signature};
use account_provider::AccountProvider;
use block::*;
use spec::CommonParams;
use engines::{Engine, Seal, EngineError};
use engines::{Engine, Seal, EngineError, ConstructedVerifier};
use state::CleanupMode;
use io::IoService;
use super::signer::EngineSigner;
use super::validator_set::ValidatorSet;
use super::validator_set::{ValidatorSet, SimpleList};
use super::transition::TransitionHandler;
use super::vote_collector::VoteCollector;
use self::message::*;
Expand Down Expand Up @@ -101,6 +101,65 @@ pub struct Tendermint {
validators: Box<ValidatorSet>,
}

struct EpochVerifier<F>
where F: Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync
{
subchain_validators: SimpleList,
recover: F
}

impl <F> super::EpochVerifier for EpochVerifier<F>
where F: Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync
{
fn verify_light(&self, header: &Header) -> Result<(), Error> {
let message = header.bare_hash();

let mut addresses = HashSet::new();
let ref header_signatures_field = header.seal().get(2).ok_or(BlockError::InvalidSeal)?;
for rlp in UntrustedRlp::new(header_signatures_field).iter() {
let signature: H520 = rlp.as_val()?;
let address = (self.recover)(&signature.into(), &message)?;

if !self.subchain_validators.contains(header.parent_hash(), &address) {
return Err(EngineError::NotAuthorized(address.to_owned()).into());
}
addresses.insert(address);
}

let n = addresses.len();
let threshold = self.subchain_validators.len() * 2/3;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should this be a majority (i.e. 1/2) or 2/3 (for Byzantine fault tolerance)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tendermint requires 2/3

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍

if n > threshold {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does it matter if it's ">" or ">="? I'd imagine in the case of three subchain_validators we'd want verification to pass when we get 2 of them

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should always be >. In the case of 3 subchain validators you would actually need all 3 to be honest. With an amount of byzantine players f, we have to have at least 3f + 1 players total for safety.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍

Ok(())
} else {
Err(EngineError::BadSealFieldSize(OutOfBounds {
min: Some(threshold),
max: None,
found: n
}).into())
}
}

fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
let header: Header = ::rlp::decode(proof);
self.verify_light(&header).ok().map(|_| vec![header.hash()])
}
}

fn combine_proofs(signal_number: BlockNumber, set_proof: &[u8], finality_proof: &[u8]) -> Vec<u8> {
let mut stream = ::rlp::RlpStream::new_list(3);
stream.append(&signal_number).append(&set_proof).append(&finality_proof);
stream.out()
}

fn destructure_proofs(combined: &[u8]) -> Result<(BlockNumber, &[u8], &[u8]), Error> {
let rlp = UntrustedRlp::new(combined);
Ok((
rlp.at(0)?.as_val()?,
rlp.at(1)?.data()?,
rlp.at(2)?.data()?,
))
}

impl Tendermint {
/// Create a new instance of Tendermint engine
pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> {
Expand Down Expand Up @@ -427,6 +486,9 @@ impl Engine for Tendermint {
}

/// Attempt to seal generate a proposal seal.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which case
/// `Seal::None` will be returned.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
let header = block.header();
let author = header.author();
Expand Down Expand Up @@ -566,6 +628,57 @@ impl Engine for Tendermint {
Ok(())
}

fn signals_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange
{
let first = header.number() == 0;
self.validators.signals_epoch_end(first, header, block, receipts)
}

fn is_epoch_end(
&self,
chain_head: &Header,
_chain: &super::Headers,
transition_store: &super::PendingTransitionStore,
) -> Option<Vec<u8>> {
let first = chain_head.number() == 0;

if let Some(change) = self.validators.is_epoch_end(first, chain_head) {
return Some(change)
} else if let Some(pending) = transition_store(chain_head.hash()) {
let signal_number = chain_head.number();
let finality_proof = ::rlp::encode(chain_head);
return Some(combine_proofs(signal_number, &pending.proof, &finality_proof))
}

None
}

fn epoch_verifier<'a>(&self, _header: &Header, proof: &'a [u8]) -> ConstructedVerifier<'a> {
let (signal_number, set_proof, finality_proof) = match destructure_proofs(proof) {
Ok(x) => x,
Err(e) => return ConstructedVerifier::Err(e),
};

let first = signal_number == 0;
match self.validators.epoch_set(first, self, signal_number, set_proof) {
Ok((list, finalize)) => {
let verifier = Box::new(EpochVerifier {
subchain_validators: list,
recover: |signature: &Signature, message: &Message| {
Ok(public_to_address(&::ethkey::recover(&signature, &message)?))
},
});

match finalize {
Some(finalize) => ConstructedVerifier::Unconfirmed(verifier, finality_proof, finalize),
None => ConstructedVerifier::Trusted(verifier),
}
}
Err(e) => ConstructedVerifier::Err(e),
}
}

fn set_signer(&self, ap: Arc<AccountProvider>, address: Address, password: String) {
{
self.signer.write().set(ap, address, password);
Expand All @@ -577,6 +690,10 @@ impl Engine for Tendermint {
self.signer.read().sign(hash).map_err(Into::into)
}

fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PoaSnapshot))
}

fn stop(&self) {
self.step_service.stop()
}
Expand Down Expand Up @@ -665,6 +782,7 @@ mod tests {
use account_provider::AccountProvider;
use spec::Spec;
use engines::{Engine, EngineError, Seal};
use engines::epoch::EpochVerifier;
use super::*;

/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
Expand Down Expand Up @@ -949,4 +1067,76 @@ mod tests {
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
assert_eq!(client.chain_info().best_block_number, 1);
}

#[test]
fn epoch_verifier_verify_light() {
use ethkey::Error as EthkeyError;

let (spec, tap) = setup();
let engine = spec.engine;

let mut parent_header: Header = Header::default();
parent_header.set_gas_limit(U256::from_str("222222").unwrap());

let mut header = Header::default();
header.set_number(2);
header.set_gas_limit(U256::from_str("222222").unwrap());
let proposer = insert_and_unlock(&tap, "1");
header.set_author(proposer);
let mut seal = proposal_seal(&tap, &header, 0);

let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();

let voter = insert_and_unlock(&tap, "0");
let signature0 = tap.sign(voter, None, vote_info.sha3()).unwrap();

seal[1] = ::rlp::NULL_RLP.to_vec();
seal[2] = ::rlp::encode_list(&vec![H520::from(signature1.clone())]).into_vec();
header.set_seal(seal.clone());

let epoch_verifier = super::EpochVerifier {
subchain_validators: SimpleList::new(vec![proposer.clone(), voter.clone()]),
recover: {
let signature1 = signature1.clone();
let signature0 = signature0.clone();
let proposer = proposer.clone();
let voter = voter.clone();
move |s: &Signature, _: &Message| {
if *s == signature1 {
Ok(proposer)
} else if *s == signature0 {
Ok(voter)
} else {
Err(Error::Ethkey(EthkeyError::InvalidSignature))
}
}
},
};

// One good signature is not enough.
match epoch_verifier.verify_light(&header) {
Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {},
_ => panic!(),
}

seal[2] = ::rlp::encode_list(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).into_vec();
header.set_seal(seal.clone());

assert!(epoch_verifier.verify_light(&header).is_ok());

let bad_voter = insert_and_unlock(&tap, "101");
let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap();

seal[2] = ::rlp::encode_list(&vec![H520::from(signature1), H520::from(bad_signature)]).into_vec();
header.set_seal(seal);

// One good and one bad signature.
match epoch_verifier.verify_light(&header) {
Err(Error::Ethkey(EthkeyError::InvalidSignature)) => {},
_ => panic!(),
};

engine.stop();
}
}
2 changes: 1 addition & 1 deletion util/rlp/src/untrusted_rlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl PayloadInfo {

/// Data-oriented view onto rlp-slice.
///
/// This is immutable structere. No operations change it.
/// This is an immutable structure. No operations change it.
///
/// Should be used in places where, error handling is required,
/// eg. on input
Expand Down