diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs
index 018489f26..3210368db 100644
--- a/ethcore/src/engines/authority_round/mod.rs
+++ b/ethcore/src/engines/authority_round/mod.rs
@@ -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`)
diff --git a/ethcore/src/engines/epoch_verifier.rs b/ethcore/src/engines/epoch_verifier.rs
deleted file mode 100644
index cd712baef..000000000
--- a/ethcore/src/engines/epoch_verifier.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2015-2017 Parity Technologies (UK) Ltd.
-// This file is part of Parity.
-
-// Parity is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity. If not, see .
-
-// Epoch verifiers.
-
-use error::Error;
-use header::Header;
-
-/// Verifier for all blocks within an epoch with self-contained state.
-///
-/// See docs on `Engine` relating to proving functions for more details.
-pub trait EpochVerifier: Send + Sync {
- /// Get the epoch number.
- fn epoch_number(&self) -> u64;
-
- /// Lightly verify the next block header.
- /// This may not be a header belonging to a different epoch.
- fn verify_light(&self, header: &Header) -> Result<(), Error>;
-
- /// Perform potentially heavier checks on the next block header.
- fn verify_heavy(&self, header: &Header) -> Result<(), Error> {
- self.verify_light(header)
- }
-
- /// Check if the header is the end of an epoch.
- fn is_epoch_end(&self, header: &Header, Ancestry) -> EpochChange;
-
-}
-
-/// Special "no-op" verifier for stateless, epoch-less engines.
-pub struct NoOp;
-
-impl EpochVerifier for NoOp {
- fn epoch_number(&self) -> u64 { 0 }
- fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
-}
diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs
index f40c7539e..453e8f9fb 100644
--- a/ethcore/src/engines/tendermint/mod.rs
+++ b/ethcore/src/engines/tendermint/mod.rs
@@ -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::*;
@@ -101,6 +101,65 @@ pub struct Tendermint {
validators: Box,
}
+struct EpochVerifier
+ where F: Fn(&Signature, &Message) -> Result + Send + Sync
+{
+ subchain_validators: SimpleList,
+ recover: F
+}
+
+impl super::EpochVerifier for EpochVerifier
+ where F: Fn(&Signature, &Message) -> Result + 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;
+ if n > threshold {
+ Ok(())
+ } else {
+ Err(EngineError::BadSealFieldSize(OutOfBounds {
+ min: Some(threshold),
+ max: None,
+ found: n
+ }).into())
+ }
+ }
+
+ fn check_finality_proof(&self, proof: &[u8]) -> Option> {
+ 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 {
+ 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) -> Result, Error> {
@@ -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();
@@ -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> {
+ 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, address: Address, password: String) {
{
self.signer.write().set(ap, address, password);
@@ -577,6 +690,10 @@ impl Engine for Tendermint {
self.signer.read().sign(hash).map_err(Into::into)
}
+ fn snapshot_components(&self) -> Option> {
+ Some(Box::new(::snapshot::PoaSnapshot))
+ }
+
fn stop(&self) {
self.step_service.stop()
}
@@ -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".
@@ -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();
+ }
}
diff --git a/util/rlp/src/untrusted_rlp.rs b/util/rlp/src/untrusted_rlp.rs
index c027438b0..16714fe98 100644
--- a/util/rlp/src/untrusted_rlp.rs
+++ b/util/rlp/src/untrusted_rlp.rs
@@ -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