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:
committed by
Gav Wood
parent
d8af9f4e7b
commit
bc167a211b
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user