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:
parent
7d348e2260
commit
5eb8cea6e7
@ -476,8 +476,8 @@ impl Engine for AuthorityRound {
|
|||||||
|
|
||||||
/// Attempt to seal the block internally.
|
/// Attempt to seal the block internally.
|
||||||
///
|
///
|
||||||
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
|
/// This operation is synchronous and may (quite reasonably) not be available, in which case
|
||||||
/// be returned.
|
/// `Seal::None` will be returned.
|
||||||
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
// first check to avoid generating signature most of the time
|
// first check to avoid generating signature most of the time
|
||||||
// (but there's still a race to the `compare_and_swap`)
|
// (but there's still a race to the `compare_and_swap`)
|
||||||
|
@ -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(()) }
|
|
||||||
}
|
|
@ -33,15 +33,15 @@ use error::{Error, BlockError};
|
|||||||
use header::{Header, BlockNumber};
|
use header::{Header, BlockNumber};
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use rlp::UntrustedRlp;
|
use rlp::UntrustedRlp;
|
||||||
use ethkey::{recover, public_to_address, Signature};
|
use ethkey::{Message, public_to_address, recover, Signature};
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use block::*;
|
use block::*;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::{Engine, Seal, EngineError};
|
use engines::{Engine, Seal, EngineError, ConstructedVerifier};
|
||||||
use state::CleanupMode;
|
use state::CleanupMode;
|
||||||
use io::IoService;
|
use io::IoService;
|
||||||
use super::signer::EngineSigner;
|
use super::signer::EngineSigner;
|
||||||
use super::validator_set::ValidatorSet;
|
use super::validator_set::{ValidatorSet, SimpleList};
|
||||||
use super::transition::TransitionHandler;
|
use super::transition::TransitionHandler;
|
||||||
use super::vote_collector::VoteCollector;
|
use super::vote_collector::VoteCollector;
|
||||||
use self::message::*;
|
use self::message::*;
|
||||||
@ -101,6 +101,65 @@ pub struct Tendermint {
|
|||||||
validators: Box<ValidatorSet>,
|
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 {
|
impl Tendermint {
|
||||||
/// Create a new instance of Tendermint engine
|
/// Create a new instance of Tendermint engine
|
||||||
pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> {
|
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.
|
/// 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 {
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
let header = block.header();
|
let header = block.header();
|
||||||
let author = header.author();
|
let author = header.author();
|
||||||
@ -566,6 +628,57 @@ impl Engine for Tendermint {
|
|||||||
Ok(())
|
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) {
|
fn set_signer(&self, ap: Arc<AccountProvider>, address: Address, password: String) {
|
||||||
{
|
{
|
||||||
self.signer.write().set(ap, address, password);
|
self.signer.write().set(ap, address, password);
|
||||||
@ -577,6 +690,10 @@ impl Engine for Tendermint {
|
|||||||
self.signer.read().sign(hash).map_err(Into::into)
|
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) {
|
fn stop(&self) {
|
||||||
self.step_service.stop()
|
self.step_service.stop()
|
||||||
}
|
}
|
||||||
@ -665,6 +782,7 @@ mod tests {
|
|||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::{Engine, EngineError, Seal};
|
use engines::{Engine, EngineError, Seal};
|
||||||
|
use engines::epoch::EpochVerifier;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
|
/// 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);
|
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);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ impl PayloadInfo {
|
|||||||
|
|
||||||
/// Data-oriented view onto rlp-slice.
|
/// 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,
|
/// Should be used in places where, error handling is required,
|
||||||
/// eg. on input
|
/// eg. on input
|
||||||
|
Loading…
Reference in New Issue
Block a user