Generalize engine trait (#6591)

* move common forks and parameters to common params

* port specs over to new format

* fix RPC tests

* parity-machine skeleton

* remove block type

* extract out ethereum-specific methods into EthereumMachine

* beginning to integrate Machine into engines. dealing with stale transitions in Ethash

* initial porting to machine

* move block reward back into engine

* abstract block reward logic

* move last hash and DAO HF logic into machine

* begin making engine function parameters generic

* abstract epoch verifier and ethash block reward logic

* instantiate special ethereummachine for ethash in spec

* optional full verification in verify_block_family

* re-instate tx_filter in a way that works for all engines

* fix warnings

* fix most tests, further generalize engine trait

* uncomment nullengine, get ethcore tests compiling

* fix warnings

* update a bunch of specs

* re-enable engine signer, validator set, and transition handler

* migrate basic_authority engine

* move last hashes into executedblock

* port tendermint

* make all ethcore tests pass

* json-tests compilation

* fix RPC tests: change in gas limit for new block changed PoW hash

* fix minor grumbles

* validate chainspecs

* fix broken import

* fix transaction verification for pre-homestead
This commit is contained in:
Robert Habermeier
2017-09-26 14:19:08 +02:00
committed by Gav Wood
parent d8af9f4e7b
commit bc167a211b
85 changed files with 2233 additions and 1923 deletions

View File

@@ -22,20 +22,24 @@
//! 3. Final verification against the blockchain done before enactment.
use std::collections::HashSet;
use hash::keccak;
use triehash::ordered_trie_root;
use heapsize::HeapSizeOf;
use bigint::hash::H256;
use unexpected::{Mismatch, OutOfBounds};
use bytes::Bytes;
use engines::Engine;
use error::{BlockError, Error};
use blockchain::*;
use client::BlockChainClient;
use engines::EthEngine;
use error::{BlockError, Error};
use header::{BlockNumber, Header};
use rlp::UntrustedRlp;
use transaction::SignedTransaction;
use views::BlockView;
use bigint::hash::H256;
use bigint::prelude::U256;
use bytes::Bytes;
use hash::keccak;
use heapsize::HeapSizeOf;
use rlp::UntrustedRlp;
use time::get_time;
use triehash::ordered_trie_root;
use unexpected::{Mismatch, OutOfBounds};
/// Preprocessed block data gathered in `verify_block_unordered` call
pub struct PreverifiedBlock {
@@ -56,14 +60,14 @@ impl HeapSizeOf for PreverifiedBlock {
}
/// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block
pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> {
pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &EthEngine) -> Result<(), Error> {
verify_header_params(&header, engine, true)?;
verify_block_integrity(bytes, &header.transactions_root(), &header.uncles_hash())?;
engine.verify_block_basic(&header, Some(bytes))?;
engine.verify_block_basic(&header)?;
for u in UntrustedRlp::new(bytes).at(2)?.iter().map(|rlp| rlp.as_val::<Header>()) {
let u = u?;
verify_header_params(&u, engine, false)?;
engine.verify_block_basic(&u, None)?;
engine.verify_block_basic(&u)?;
}
// Verify transactions.
// TODO: either use transaction views or cache the decoded transactions.
@@ -77,11 +81,11 @@ pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Res
/// Phase 2 verification. Perform costly checks such as transaction signatures and block nonce for ethash.
/// Still operates on a individual block
/// Returns a `PreverifiedBlock` structure populated with transactions
pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, check_seal: bool) -> Result<PreverifiedBlock, Error> {
pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &EthEngine, check_seal: bool) -> Result<PreverifiedBlock, Error> {
if check_seal {
engine.verify_block_unordered(&header, Some(&bytes))?;
engine.verify_block_unordered(&header)?;
for u in UntrustedRlp::new(&bytes).at(2)?.iter().map(|rlp| rlp.as_val::<Header>()) {
engine.verify_block_unordered(&u?, None)?;
engine.verify_block_unordered(&u?)?;
}
}
// Verify transactions.
@@ -92,7 +96,7 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che
{
let v = BlockView::new(&bytes);
for t in v.transactions() {
let t = engine.verify_transaction(t, &header)?;
let t = engine.verify_transaction_unordered(t, &header)?;
if let Some(max_nonce) = nonce_cap {
if t.nonce >= max_nonce {
return Err(BlockError::TooManyTransactions(t.sender()).into());
@@ -108,13 +112,30 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che
})
}
/// Phase 3 verification. Check block information against parent and uncles.
pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error> {
// TODO: verify timestamp
let parent = bc.block_header(&header.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownParent(header.parent_hash().clone())))?;
verify_parent(&header, &parent)?;
engine.verify_block_family(&header, &parent, Some(bytes))?;
/// Parameters for full verification of block family: block bytes, transactions, blockchain, and state access.
pub type FullFamilyParams<'a> = (&'a [u8], &'a [SignedTransaction], &'a BlockProvider, &'a BlockChainClient);
/// Phase 3 verification. Check block information against parent and uncles.
pub fn verify_block_family(header: &Header, parent: &Header, engine: &EthEngine, do_full: Option<FullFamilyParams>) -> Result<(), Error> {
// TODO: verify timestamp
verify_parent(&header, &parent, engine.params().gas_limit_bound_divisor)?;
engine.verify_block_family(&header, &parent)?;
let (bytes, txs, bc, client) = match do_full {
Some(x) => x,
None => return Ok(()),
};
verify_uncles(header, bytes, bc, engine)?;
for transaction in txs {
engine.machine().verify_transaction(transaction, header, client)?;
}
Ok(())
}
fn verify_uncles(header: &Header, bytes: &[u8], bc: &BlockProvider, engine: &EthEngine) -> Result<(), Error> {
let num_uncles = UntrustedRlp::new(bytes).at(2)?.item_count()?;
if num_uncles != 0 {
if num_uncles > engine.maximum_uncle_count() {
@@ -189,11 +210,12 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &
return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash())));
}
verify_parent(&uncle, &uncle_parent)?;
engine.verify_block_family(&uncle, &uncle_parent, Some(bytes))?;
verify_parent(&uncle, &uncle_parent, engine.params().gas_limit_bound_divisor)?;
engine.verify_block_family(&uncle, &uncle_parent)?;
verified.insert(uncle.hash());
}
}
Ok(())
}
@@ -215,7 +237,13 @@ pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error>
}
/// Check basic header parameters.
pub fn verify_header_params(header: &Header, engine: &Engine, is_full: bool) -> Result<(), Error> {
pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool) -> Result<(), Error> {
if header.seal().len() != engine.seal_fields() {
return Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: engine.seal_fields(), found: header.seal().len() }
)));
}
if header.number() >= From::from(BlockNumber::max_value()) {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { max: Some(From::from(BlockNumber::max_value())), min: None, found: header.number() })))
}
@@ -230,6 +258,15 @@ pub fn verify_header_params(header: &Header, engine: &Engine, is_full: bool) ->
if header.number() != 0 && header.extra_data().len() > maximum_extra_data_size {
return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: None, max: Some(maximum_extra_data_size), found: header.extra_data().len() })));
}
if let Some(ref ext) = engine.machine().ethash_extensions() {
if header.number() >= ext.dao_hardfork_transition &&
header.number() <= ext.dao_hardfork_transition + 9 &&
header.extra_data()[..] != b"dao-hard-fork"[..] {
return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: None, max: None, found: 0 })));
}
}
if is_full {
let max_time = get_time().sec as u64 + 30;
if header.timestamp() > max_time {
@@ -240,7 +277,7 @@ pub fn verify_header_params(header: &Header, engine: &Engine, is_full: bool) ->
}
/// Check header parameters agains parent header.
fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> {
fn verify_parent(header: &Header, parent: &Header, gas_limit_divisor: U256) -> Result<(), Error> {
if !header.parent_hash().is_zero() && &parent.hash() != header.parent_hash() {
return Err(From::from(BlockError::InvalidParentHash(Mismatch { expected: parent.hash(), found: header.parent_hash().clone() })))
}
@@ -250,6 +287,18 @@ fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> {
if header.number() != parent.number() + 1 {
return Err(From::from(BlockError::InvalidNumber(Mismatch { expected: parent.number() + 1, found: header.number() })));
}
if header.number() == 0 {
return Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }).into());
}
let parent_gas_limit = *parent.gas_limit();
let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor;
let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() })));
}
Ok(())
}
@@ -285,7 +334,7 @@ mod tests {
use error::BlockError::*;
use views::*;
use blockchain::*;
use engines::Engine;
use engines::EthEngine;
use spec::*;
use transaction::*;
use tests::helpers::*;
@@ -406,17 +455,38 @@ mod tests {
}
}
fn basic_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
fn basic_test(bytes: &[u8], engine: &EthEngine) -> Result<(), Error> {
let header = BlockView::new(bytes).header();
verify_block_basic(&header, bytes, engine)
}
fn family_test<BC>(bytes: &[u8], engine: &Engine, bc: &BC) -> Result<(), Error> where BC: BlockProvider {
let header = BlockView::new(bytes).header();
verify_block_family(&header, bytes, engine, bc)
fn family_test<BC>(bytes: &[u8], engine: &EthEngine, bc: &BC) -> Result<(), Error> where BC: BlockProvider {
let view = BlockView::new(bytes);
let header = view.header();
let transactions: Vec<_> = view.transactions()
.into_iter()
.map(SignedTransaction::new)
.collect::<Result<_,_>>()?;
// TODO: client is really meant to be used for state query here by machine
// additions that need access to state (tx filter in specific)
// no existing tests need access to test, so having this not function
// is fine.
let client = ::client::TestBlockChainClient::default();
let parent = bc.block_header(header.parent_hash())
.ok_or(BlockError::UnknownParent(header.parent_hash().clone()))?;
let full_params: FullFamilyParams = (
bytes,
&transactions[..],
bc as &BlockProvider,
&client as &::client::BlockChainClient
);
verify_block_family(&header, &parent, engine, Some(full_params))
}
fn unordered_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
fn unordered_test(bytes: &[u8], engine: &EthEngine) -> Result<(), Error> {
let header = BlockView::new(bytes).header();
verify_block_unordered(header, bytes.to_vec(), engine, false)?;
Ok(())
@@ -590,6 +660,15 @@ mod tests {
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &bad_uncles), engine, &bc),
DuplicateUncle(good_uncle1.hash()));
header = good.clone();
header.set_gas_limit(0.into());
header.set_difficulty("0000000000000000000000000000000000000000000000000000000000020000".parse::<U256>().unwrap());
match family_test(&create_test_block(&header), engine, &bc) {
Err(Error::Block(InvalidGasLimit(_))) => {},
Err(_) => { panic!("should be invalid difficulty fail"); },
_ => { panic!("Should be error, got Ok"); },
}
// TODO: some additional uncle checks
}
@@ -597,6 +676,7 @@ mod tests {
fn dust_protection() {
use ethkey::{Generator, Random};
use transaction::{Transaction, Action};
use machine::EthereumMachine;
use engines::NullEngine;
let mut params = CommonParams::default();
@@ -618,7 +698,8 @@ mod tests {
let good_transactions = [bad_transactions[0].clone(), bad_transactions[1].clone()];
let engine = NullEngine::new(params, BTreeMap::new());
let machine = EthereumMachine::regular(params, BTreeMap::new());
let engine = NullEngine::new(Default::default(), machine);
check_fail(unordered_test(&create_test_block_with_data(&header, &bad_transactions, &[]), &engine), TooManyTransactions(keypair.address()));
unordered_test(&create_test_block_with_data(&header, &good_transactions, &[]), &engine).unwrap();
}