Tendermint epoch transitions (#6085)

* Adds signals_epoch_end to tendermint

* Adds is_epoch_end

* Adds snapshot_components

* Adds tendermint Epoch Verifier

* Fix documentation typos

* Change check_finality_proof to panic

* Fix compilation

* Adds Unconfirmed path to epoch_verifier

* Verify if address is validator in EpochVerifier

* check_finality_proof errors on failure

* Don't share combine/destructure_proofs

* Remove invalid import

* Remove duplicate epoch verifier trait

* Fix docs

* Adds recover stub to tendermint EpochVerifier

* Adds verify_light test

* Avoid boxed closure

* Style fixes
This commit is contained in:
Feynman Liang 2017-07-26 08:25:32 -07:00 committed by keorn
parent 7d348e2260
commit 5eb8cea6e7
4 changed files with 196 additions and 55 deletions

View File

@ -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`)

View File

@ -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 <http://www.gnu.org/licenses/>.
// 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(()) }
}

View File

@ -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<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;
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<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> {
@ -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<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);
@ -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()
}
@ -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();
}
}

View File

@ -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