diff --git a/src/block.rs b/src/block.rs index 3c42e4e27..f98934988 100644 --- a/src/block.rs +++ b/src/block.rs @@ -162,4 +162,4 @@ fn open_block() { let mut db = OverlayDB::new_temp(); engine.spec().ensure_db_good(&mut db); let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()]); -} \ No newline at end of file +} diff --git a/src/engine.rs b/src/engine.rs index 7fee085e9..7d86aa0a1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,7 @@ use common::*; use block::Block; use spec::Spec; +use verification::VerificationError; /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// Provides hooks into each of the major parts of block import. @@ -25,7 +26,7 @@ pub trait Engine { fn evm_schedule(&self, env_info: &EnvInfo) -> EvmSchedule; /// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`. - fn maximum_extra_data_size(&self, _env_info: &EnvInfo) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) } + fn maximum_extra_data_size(&self) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) } fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) } /// Block transformation functions, before and after the transactions. @@ -36,12 +37,12 @@ pub trait Engine { /// `parent` (the parent header) and `block` (the header's full block) may be provided for additional /// checks. Returns either a null `Ok` or a general error detailing the problem with import. // TODO: consider including State in the params. - fn verify_block(&self, _header: &Header, _parent: Option<&Header>, _block: Option<&[u8]>) -> Result<(), EthcoreError> { Ok(()) } + fn verify_block(&self, _mode: VerificationMode, _header: &Header, _parent: Option<&Header>, _block: Option<&[u8]>) -> Result<(), VerificationError> { Ok(()) } /// Additional verification for transactions in blocks. // TODO: Add flags for which bits of the transaction to check. // TODO: consider including State in the params. - fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), EthcoreError> { Ok(()) } + fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), VerificationError> { Ok(()) } /// Don't forget to call Super::populateFromParent when subclassing & overriding. // TODO: consider including State in the params. @@ -55,3 +56,11 @@ pub trait Engine { // TODO: sealing stuff - though might want to leave this for later. } + +#[derive(Debug, PartialEq, Eq)] +pub enum VerificationMode { + /// Do a quick and basic verification if possible. + Quick, + /// Do a full verification. + Full +} diff --git a/src/ethereum/ethash.rs b/src/ethereum/ethash.rs index 433a03681..8e26e8f86 100644 --- a/src/ethereum/ethash.rs +++ b/src/ethereum/ethash.rs @@ -2,6 +2,7 @@ use common::*; use block::*; use spec::*; use engine::*; +use verification::*; /// Engine using Ethash proof-of-work consensus algorithm, suitable for Ethereum /// mainnet chains in the Olympic, Frontier and Homestead eras. @@ -25,6 +26,91 @@ impl Engine for Ethash { let a = block.header().author.clone(); block.state_mut().add_balance(&a, &decode(&self.spec().engine_params.get("block_reward").unwrap())); } + + fn verify_block(&self, mode: VerificationMode, header: &Header, parent: Option<&Header>, block: Option<&[u8]>) -> Result<(), VerificationError> { + if mode == VerificationMode::Quick { + let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap()); + if header.difficulty < min_difficulty { + return Err(VerificationError::block( + BlockVerificationError::InvalidDifficulty { required: min_difficulty, got: header.difficulty }, + block.map(|b| b.to_vec()))); + } + let min_gas_limit = decode(self.spec().engine_params.get("minGasLimit").unwrap()); + if header.gas_limit < min_gas_limit { + return Err(VerificationError::block( + BlockVerificationError::InvalidGasLimit { min: min_gas_limit, max: From::from(0), got: header.gas_limit }, + block.map(|b| b.to_vec()))); + } + let len: U256 = From::from(header.extra_data.len()); + let maximum_extra_data_size: U256 = From::from(self.maximum_extra_data_size()); + if header.number != From::from(0) && len > maximum_extra_data_size { + return Err(VerificationError::block( + BlockVerificationError::ExtraDataTooBig { required: maximum_extra_data_size, got: len }, + block.map(|b| b.to_vec()))); + } + match parent { + Some(p) => { + // Check difficulty is correct given the two timestamps. + let expected_difficulty = self.calculate_difficuty(header, p); + if header.difficulty != expected_difficulty { + return Err(VerificationError::block( + BlockVerificationError::InvalidDifficulty { required: expected_difficulty, got: header.difficulty }, + block.map(|b| b.to_vec()))); + } + let gas_limit_divisor = decode(self.spec().engine_params.get("gasLimitBoundDivisor").unwrap()); + let min_gas = p.gas_limit - p.gas_limit / gas_limit_divisor; + let max_gas = p.gas_limit + p.gas_limit / gas_limit_divisor; + if header.gas_limit <= min_gas || header.gas_limit >= max_gas { + return Err(VerificationError::block( + BlockVerificationError::InvalidGasLimit { min: min_gas_limit, max: max_gas, got: header.gas_limit }, + block.map(|b| b.to_vec()))); + } + }, + None => () + } + // TODO: Verify seal + } + Ok(()) + } + + fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), VerificationError> { Ok(()) } +} + +impl Ethash { + fn calculate_difficuty(&self, header: &Header, parent: &Header) -> U256 { + const EXP_DIFF_PERIOD: u64 = 100000; + if header.number == From::from(0) { + panic!("Can't calculate genesis block difficulty"); + } + + let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap()); + let difficulty_bound_divisor = decode(self.spec().engine_params.get("difficultyBoundDivisor").unwrap()); + let duration_limit = decode(self.spec().engine_params.get("durationLimit").unwrap()); + let frontier_limit = decode(self.spec().engine_params.get("frontierCompatibilityModeLimit").unwrap()); + let mut target = if header.number < frontier_limit { + if header.timestamp >= parent.timestamp + duration_limit { + parent.difficulty - (parent.difficulty / difficulty_bound_divisor) + } + else { + parent.difficulty + (parent.difficulty / difficulty_bound_divisor) + } + } + else { + let diff_inc = (header.timestamp - parent.timestamp) / From::from(10); + if diff_inc <= From::from(1) { + parent.difficulty + parent.difficulty / From::from(2048) * (U256::from(1) - diff_inc) + } + else { + parent.difficulty - parent.difficulty / From::from(2048) * max(diff_inc - From::from(1), From::from(99)) + } + }; + target = max(min_difficulty, target); + let period = ((parent.number + From::from(1)).as_u64() / EXP_DIFF_PERIOD) as usize; + if period > 1 { + target = max(min_difficulty, target + (U256::from(1) << (period - 2))); + } + target + } } // TODO: test for on_close_block. @@ -44,3 +130,4 @@ fn playpen() { let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()]); // let c = b.close(); } + diff --git a/src/lib.rs b/src/lib.rs index 2154a7c14..b1c6b0a31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,4 +102,5 @@ pub mod extras; pub mod client; pub mod sync; pub mod block; +pub mod verification; pub mod ethereum; diff --git a/src/verification.rs b/src/verification.rs index 95951da53..f4aa4dcb6 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -1,10 +1,35 @@ -use util::uint::*; -use util::hash::*; -use util::rlp::*; -use util::sha3::Hashable; -use util::triehash::ordered_trie_root; +use util::*; use header::Header; use client::BlockNumber; +use engine::{Engine, VerificationMode}; +use views::BlockView; + +#[derive(Debug)] +pub struct VerificationError { + pub block: Option, + pub error: VerificationErrorOption, +} + +impl VerificationError { + pub fn block(error: BlockVerificationError, block: Option) -> VerificationError { + VerificationError { + block: block, + error: VerificationErrorOption::Block(error), + } + } + pub fn transaction(error: TransactionVerificationError, block: Option) -> VerificationError { + VerificationError { + block: block, + error: VerificationErrorOption::Transaction(error), + } + } +} + +#[derive(Debug)] +pub enum VerificationErrorOption { + Transaction(TransactionVerificationError), + Block(BlockVerificationError), +} #[derive(Debug)] pub enum TransactionVerificationError { @@ -18,7 +43,6 @@ pub enum TransactionVerificationError { used: U256, limit: U256 }, - ExtraDataTooBig, InvalidSignature, InvalidTransactionFormat, } @@ -30,8 +54,12 @@ pub enum BlockVerificationError { limit: U256, }, InvalidBlockFormat, + ExtraDataTooBig { + required: U256, + got: U256, + }, InvalidUnclesHash { - expected: H256, + required: H256, got: H256, }, TooManyUncles, @@ -42,11 +70,18 @@ pub enum BlockVerificationError { InvalidStateRoot, InvalidGasUsed, InvalidTransactionsRoot { - expected: H256, + required: H256, got: H256, }, - InvalidDifficulty, - InvalidGasLimit, + InvalidDifficulty { + required: U256, + got: U256, + }, + InvalidGasLimit { + min: U256, + max: U256, + got: U256, + }, InvalidReceiptsStateRoot, InvalidTimestamp, InvalidLogBloom, @@ -93,17 +128,30 @@ pub fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_has let expected_root = ordered_trie_root(tx.iter().map(|r| r.as_raw().to_vec()).collect()); //TODO: get rid of vectors here if &expected_root != transactions_root { return Err(BlockVerificationError::InvalidTransactionsRoot { - expected: expected_root.clone(), + required: expected_root.clone(), got: transactions_root.clone(), }); } let expected_uncles = block.at(2).as_raw().sha3(); if &expected_uncles != uncles_hash { return Err(BlockVerificationError::InvalidUnclesHash { - expected: expected_uncles.clone(), + required: expected_uncles.clone(), got: uncles_hash.clone(), }); } Ok(()) } +pub fn verify_block_basic(bytes: &[u8], parent: &Header, engine: &mut Engine) -> Result<(), BlockVerificationError> { + let block = BlockView::new(bytes); + let header = block.header(); + try!(verify_header(&header)); + try!(verify_parent(&header, parent)); + try!(verify_block_integrity(bytes, &header.transactions_root, &header.uncles_hash)); + + Ok(()) +} + +pub fn verify_block_unordered(block: &[u8]) -> Result<(), BlockVerificationError> { + Ok(()) +}