diff --git a/src/block.rs b/src/block.rs index b1207ce53..5837ce2c9 100644 --- a/src/block.rs +++ b/src/block.rs @@ -142,7 +142,7 @@ impl<'x, 'y> OpenBlock<'x, 'y> { /// that the header itself is actually valid. pub fn push_uncle(&mut self, valid_uncle_header: Header) -> Result<(), BlockError> { if self.block.uncles.len() >= self.engine.maximum_uncle_count() { - return Err(BlockError::TooManyUncles); + return Err(BlockError::TooManyUncles(OutOfBounds{min: 0, max: self.engine.maximum_uncle_count(), found: self.block.uncles.len()})); } // TODO: check number // TODO: check not a direct ancestor (use last_hashes for that) diff --git a/src/client.rs b/src/client.rs index b914dba19..018f99b6f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,6 +5,7 @@ use error::*; use header::BlockNumber; use spec::Spec; use engine::Engine; +use queue::BlockQueue; /// General block status pub enum BlockStatus { @@ -18,9 +19,6 @@ pub enum BlockStatus { Unknown, } -/// Result of import block operation. -pub type ImportResult = Result<(), ImportError>; - /// Information about the blockchain gthered together. pub struct BlockChainInfo { /// Blockchain difficulty. @@ -95,28 +93,30 @@ pub trait BlockChainClient : Sync { /// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue. pub struct Client { - chain: Arc, + chain: Arc>, _engine: Arc>, + queue: BlockQueue, } impl Client { pub fn new(spec: Spec, path: &Path) -> Result { - let chain = Arc::new(BlockChain::new(&spec.genesis_block(), path)); + let chain = Arc::new(RwLock::new(BlockChain::new(&spec.genesis_block(), path))); let engine = Arc::new(try!(spec.to_engine())); Ok(Client { chain: chain.clone(), - _engine: engine, + _engine: engine.clone(), + queue: BlockQueue::new(chain.clone(), engine.clone()), }) } } impl BlockChainClient for Client { fn block_header(&self, hash: &H256) -> Option { - self.chain.block(hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()) + self.chain.read().unwrap().block(hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()) } fn block_body(&self, hash: &H256) -> Option { - self.chain.block(hash).map(|bytes| { + self.chain.read().unwrap().block(hash).map(|bytes| { let rlp = Rlp::new(&bytes); let mut body = RlpStream::new(); body.append_raw(rlp.at(1).as_raw(), 1); @@ -126,34 +126,34 @@ impl BlockChainClient for Client { } fn block(&self, hash: &H256) -> Option { - self.chain.block(hash) + self.chain.read().unwrap().block(hash) } fn block_status(&self, hash: &H256) -> BlockStatus { - if self.chain.is_known(&hash) { BlockStatus::InChain } else { BlockStatus::Unknown } + if self.chain.read().unwrap().is_known(&hash) { BlockStatus::InChain } else { BlockStatus::Unknown } } fn block_header_at(&self, n: BlockNumber) -> Option { - self.chain.block_hash(n).and_then(|h| self.block_header(&h)) + self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_header(&h)) } fn block_body_at(&self, n: BlockNumber) -> Option { - self.chain.block_hash(n).and_then(|h| self.block_body(&h)) + self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_body(&h)) } fn block_at(&self, n: BlockNumber) -> Option { - self.chain.block_hash(n).and_then(|h| self.block(&h)) + self.chain.read().unwrap().block_hash(n).and_then(|h| self.block(&h)) } fn block_status_at(&self, n: BlockNumber) -> BlockStatus { - match self.chain.block_hash(n) { + match self.chain.read().unwrap().block_hash(n) { Some(h) => self.block_status(&h), None => BlockStatus::Unknown } } fn tree_route(&self, from: &H256, to: &H256) -> Option { - self.chain.tree_route(from.clone(), to.clone()) + self.chain.read().unwrap().tree_route(from.clone(), to.clone()) } fn state_data(&self, _hash: &H256) -> Option { @@ -165,17 +165,7 @@ impl BlockChainClient for Client { } fn import_block(&mut self, bytes: &[u8]) -> ImportResult { - //TODO: verify block - { - let block = BlockView::new(bytes); - let header = block.header_view(); - let hash = header.sha3(); - if self.chain.is_known(&hash) { - return Err(ImportError::AlreadyInChain); - } - } - self.chain.insert_block(bytes); - Ok(()) + self.queue.import_block(bytes) } fn queue_status(&self) -> BlockQueueStatus { @@ -188,12 +178,13 @@ impl BlockChainClient for Client { } fn chain_info(&self) -> BlockChainInfo { + let chain = self.chain.read().unwrap(); BlockChainInfo { - total_difficulty: self.chain.best_block_total_difficulty(), - pending_total_difficulty: self.chain.best_block_total_difficulty(), - genesis_hash: self.chain.genesis_hash(), - best_block_hash: self.chain.best_block_hash(), - best_block_number: From::from(self.chain.best_block_number()) + total_difficulty: chain.best_block_total_difficulty(), + pending_total_difficulty: chain.best_block_total_difficulty(), + genesis_hash: chain.genesis_hash(), + best_block_hash: chain.best_block_hash(), + best_block_number: From::from(chain.best_block_number()) } } } diff --git a/src/engine.rs b/src/engine.rs index 64f85e079..aed9fe2f3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -33,11 +33,18 @@ pub trait Engine : Sync + Send { fn on_new_block(&self, _block: &mut Block) {} fn on_close_block(&self, _block: &mut Block) {} - /// Verify that `header` is valid. - /// `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<(), Error> { Ok(()) } + // TODO: consider including State in the params for verification functions. + /// Phase 1 quick block verification. Only does checks that are cheap. `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. + fn verify_block_basic(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } + + /// Phase 2 verification. Perform costly checks such as transaction signatures. `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. + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } + + /// Phase 3 verification. Check block information against parent and uncles. `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. + fn verify_block_final(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } /// Additional verification for transactions in blocks. // TODO: Add flags for which bits of the transaction to check. diff --git a/src/error.rs b/src/error.rs index d975f15b6..c18782502 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ //! General error types for use in ethcore. use util::*; +use header::BlockNumber; #[derive(Debug)] pub struct Mismatch { @@ -17,19 +18,47 @@ pub struct OutOfBounds { #[derive(Debug)] pub enum BlockError { - TooManyUncles, + TooManyUncles(OutOfBounds), UncleWrongGeneration, ExtraDataOutOfBounds(OutOfBounds), InvalidSealArity(Mismatch), + TooMuchGasUsed(OutOfBounds), + InvalidUnclesHash(Mismatch), + UncleTooOld(OutOfBounds), + UncleIsBrother(OutOfBounds), + UncleInChain(H256), + UncleParentNotInChain(H256), + InvalidStateRoot, + InvalidGasUsed, + InvalidTransactionsRoot(Mismatch), + InvalidDifficulty(Mismatch), + InvalidGasLimit(OutOfBounds), + InvalidReceiptsStateRoot, + InvalidTimestamp(OutOfBounds), + InvalidLogBloom, + InvalidBlockNonce, + InvalidParentHash(Mismatch), + InvalidNumber(OutOfBounds), + UnknownParent(H256), + UnknownUncleParent(H256), } #[derive(Debug)] pub enum ImportError { - Bad(BlockError), + Bad(Error), AlreadyInChain, AlreadyQueued, } +impl From for ImportError { + fn from(err: Error) -> ImportError { + ImportError::Bad(err) + } +} + +/// Result of import block operation. +pub type ImportResult = Result<(), ImportError>; + #[derive(Debug)] /// General error type which should be capable of representing all errors in ethcore. pub enum Error { diff --git a/src/ethereum/ethash.rs b/src/ethereum/ethash.rs index 828441fd6..c30a855a0 100644 --- a/src/ethereum/ethash.rs +++ b/src/ethereum/ethash.rs @@ -43,6 +43,83 @@ impl Engine for Ethash { fields.state.add_balance(u.author(), &(reward * U256::from((8 + u.number() - current_number) / 8))); } } + + + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap()); + if header.difficulty < min_difficulty { + return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: min_difficulty, found: header.difficulty }))) + } + let min_gas_limit = decode(self.spec().engine_params.get("minGasLimit").unwrap()); + if header.gas_limit < min_gas_limit { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: min_gas_limit, max: From::from(0), found: header.gas_limit }))); + } + let maximum_extra_data_size = self.maximum_extra_data_size(); + if header.number != 0 && header.extra_data.len() > maximum_extra_data_size { + return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: 0, max: maximum_extra_data_size, found: header.extra_data.len() }))); + } + // TODO: Verify seal (quick) + Ok(()) + } + + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // TODO: Verify seal (full) + Ok(()) + } + + fn verify_block_final(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // Check difficulty is correct given the two timestamps. + let expected_difficulty = self.calculate_difficuty(header, parent); + if header.difficulty != expected_difficulty { + return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: expected_difficulty, found: header.difficulty }))) + } + let gas_limit_divisor = decode(self.spec().engine_params.get("gasLimitBoundDivisor").unwrap()); + 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: min_gas, max: max_gas, found: header.gas_limit }))); + } + Ok(()) + } + + fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), Error> { Ok(()) } +} + +impl Ethash { + fn calculate_difficuty(&self, header: &Header, parent: &Header) -> U256 { + const EXP_DIFF_PERIOD: u64 = 100000; + if header.number == 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: u64 = 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) / 10; + if diff_inc <= 1 { + parent.difficulty + parent.difficulty / From::from(2048) * From::from(1 - diff_inc) + } + else { + parent.difficulty - parent.difficulty / From::from(2048) * From::from(max(diff_inc - 1, 99)) + } + }; + target = max(min_difficulty, target); + let period = ((parent.number + 1) / EXP_DIFF_PERIOD) as usize; + if period > 1 { + target = max(min_difficulty, target + (U256::from(1) << (period - 2))); + } + target + } } #[test] @@ -57,3 +134,5 @@ fn on_close_block() { let b = b.close(); assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244f40000").unwrap()); } + +// TODO: difficulty test diff --git a/src/lib.rs b/src/lib.rs index 1b9c055df..f5e85c6c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,4 +106,6 @@ pub mod extras; pub mod client; pub mod sync; pub mod block; +pub mod verification; +pub mod queue; pub mod ethereum; diff --git a/src/queue.rs b/src/queue.rs index ea212aaf1..721960259 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,16 +1,24 @@ -use std::sync::Arc; use util::*; use blockchain::BlockChain; -use client::{QueueStatus, ImportResult}; use views::{BlockView}; +use verification::*; +use error::*; +use engine::Engine; /// A queue of blocks. Sits between network or other I/O and the BlockChain. /// Sorts them ready for blockchain insertion. -pub struct BlockQueue; +pub struct BlockQueue { + bc: Arc>, + engine: Arc>, +} impl BlockQueue { /// Creates a new queue instance. - pub fn new() -> BlockQueue { + pub fn new(bc: Arc>, engine: Arc>) -> BlockQueue { + BlockQueue { + bc: bc, + engine: engine, + } } /// Clear the queue and stop verification activity. @@ -18,18 +26,16 @@ impl BlockQueue { } /// Add a block to the queue. - pub fn import_block(&mut self, bytes: &[u8], bc: &mut BlockChain) -> ImportResult { - //TODO: verify block - { - let block = BlockView::new(bytes); - let header = block.header_view(); - let hash = header.sha3(); - if self.chain.is_known(&hash) { - return ImportResult::Bad; - } + pub fn import_block(&mut self, bytes: &[u8]) -> ImportResult { + let header = BlockView::new(bytes).header(); + if self.bc.read().unwrap().is_known(&header.hash()) { + return Err(ImportError::AlreadyInChain); } - bc.insert_block(bytes); - ImportResult::Queued(QueueStatus::Known) + try!(verify_block_basic(bytes, self.engine.deref().deref())); + try!(verify_block_unordered(bytes, self.engine.deref().deref())); + try!(verify_block_final(bytes, self.engine.deref().deref(), self.bc.read().unwrap().deref())); + self.bc.write().unwrap().insert_block(bytes); + Ok(()) } } diff --git a/src/sync/tests.rs b/src/sync/tests.rs index 6a045fa1e..bc0e171d2 100644 --- a/src/sync/tests.rs +++ b/src/sync/tests.rs @@ -1,13 +1,7 @@ -use std::collections::{HashMap, VecDeque}; -use util::bytes::Bytes; -use util::hash::{H256, FixedHash}; -use util::uint::{U256}; -use util::sha3::Hashable; -use util::rlp::{self, Rlp, RlpStream, View, Stream}; -use util::network::{PeerId, PacketId}; -use util::error::UtilError; -use client::{BlockChainClient, BlockStatus, TreeRoute, BlockQueueStatus, BlockChainInfo, ImportResult}; +use util::*; +use client::{BlockChainClient, BlockStatus, TreeRoute, BlockQueueStatus, BlockChainInfo}; use header::{Header as BlockHeader, BlockNumber}; +use error::*; use sync::io::SyncIo; use sync::chain::ChainSync; diff --git a/src/verification.rs b/src/verification.rs new file mode 100644 index 000000000..be885162a --- /dev/null +++ b/src/verification.rs @@ -0,0 +1,154 @@ +/// Block and transaction verification functions +/// +/// Block verification is done in 3 steps +/// 1. Quick verification upon adding to the block queue +/// 2. Signatures verification done in the queue. +/// 3. Final verification against the blockchain done before enactment. + +use common::*; +use engine::Engine; +use blockchain::BlockChain; + +/// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block +pub fn verify_block_basic(bytes: &[u8], engine: &Engine) -> Result<(), Error> { + let block = BlockView::new(bytes); + let header = block.header(); + try!(verify_header(&header)); + try!(verify_block_integrity(bytes, &header.transactions_root, &header.uncles_hash)); + try!(engine.verify_block_basic(&header, Some(bytes))); + for u in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::
()) { + try!(verify_header(&u)); + try!(engine.verify_block_basic(&u, None)); + } + Ok(()) +} + +/// Phase 2 verification. Perform costly checks such as transaction signatures and block nonce for ethash. +/// Still operates on a individual block +/// TODO: return cached transactions, header hash. +pub fn verify_block_unordered(bytes: &[u8], engine: &Engine) -> Result<(), Error> { + let block = BlockView::new(bytes); + let header = block.header(); + try!(engine.verify_block_unordered(&header, Some(bytes))); + for u in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::
()) { + try!(engine.verify_block_unordered(&u, None)); + } + Ok(()) +} + +/// Phase 3 verification. Check block information against parent and uncles. +pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Result<(), Error> { + let block = BlockView::new(bytes); + let header = block.header(); + let parent = try!(bc.block_header(&header.parent_hash).ok_or::(From::from(BlockError::UnknownParent(header.parent_hash.clone())))); + try!(verify_parent(&header, &parent)); + try!(engine.verify_block_final(&header, &parent, Some(bytes))); + + let num_uncles = Rlp::new(bytes).at(2).item_count(); + if num_uncles != 0 { + if num_uncles > engine.maximum_uncle_count() { + return Err(From::from(BlockError::TooManyUncles(OutOfBounds { min: 0, max: engine.maximum_uncle_count(), found: num_uncles }))); + } + + let mut excluded = HashSet::new(); + excluded.insert(header.hash()); + let mut hash = header.parent_hash.clone(); + excluded.insert(hash.clone()); + for _ in 0..6 { + match bc.block_details(&hash) { + Some(details) => { + excluded.insert(details.parent.clone()); + let b = bc.block(&hash).unwrap(); + excluded.extend(BlockView::new(&b).uncle_hashes()); + hash = details.parent; + } + None => break + } + } + + for uncle in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::
()) { + let uncle_parent = try!(bc.block_header(&uncle.parent_hash).ok_or::(From::from(BlockError::UnknownUncleParent(uncle.parent_hash.clone())))); + if excluded.contains(&uncle_parent.hash()) { + return Err(From::from(BlockError::UncleInChain(uncle_parent.hash()))) + } + + // m_currentBlock.number() - uncle.number() m_cB.n - uP.n() + // 1 2 + // 2 + // 3 + // 4 + // 5 + // 6 7 + // (8 Invalid) + + let depth = if header.number > uncle.number { header.number - uncle.number } else { 0 }; + if depth > 6 { + return Err(From::from(BlockError::UncleTooOld(OutOfBounds { min: header.number - depth, max: header.number - 1, found: uncle.number }))); + } + else if depth < 1 { + return Err(From::from(BlockError::UncleIsBrother(OutOfBounds { min: header.number - depth, max: header.number - 1, found: uncle.number }))); + } + + // cB + // cB.p^1 1 depth, valid uncle + // cB.p^2 ---/ 2 + // cB.p^3 -----/ 3 + // cB.p^4 -------/ 4 + // cB.p^5 ---------/ 5 + // cB.p^6 -----------/ 6 + // cB.p^7 -------------/ + // cB.p^8 + let mut expected_uncle_parent = header.parent_hash.clone(); + for _ in 0..depth { + expected_uncle_parent = bc.block_details(&expected_uncle_parent).unwrap().parent; + } + if expected_uncle_parent != uncle_parent.hash() { + return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash()))); + } + + try!(engine.verify_block_final(&uncle, &uncle_parent, Some(bytes))); + } + } + Ok(()) +} + +/// Check basic header parameters. +fn verify_header(header: &Header) -> Result<(), Error> { + if header.number > From::from(BlockNumber::max_value()) { + return Err(From::from(BlockError::InvalidNumber(OutOfBounds { max: From::from(BlockNumber::max_value()), min: 0, found: header.number }))) + } + if header.gas_used > header.gas_limit { + return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: header.gas_limit, min: From::from(0), found: header.gas_used }))); + } + Ok(()) +} + +/// Check header parameters agains parent header. +fn verify_parent(header: &Header, parent: &Header) -> 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() }))) + } + if header.timestamp <= parent.timestamp { + return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: u64::max_value(), min: parent.timestamp + 1, found: header.timestamp }))) + } + if header.number <= parent.number { + return Err(From::from(BlockError::InvalidNumber(OutOfBounds { max: BlockNumber::max_value(), min: parent.number + 1, found: header.number }))); + } + Ok(()) +} + +/// Verify block data against header: transactions root and uncles hash. +fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &H256) -> Result<(), Error> { + let block = Rlp::new(block); + let tx = block.at(1); + 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(From::from(BlockError::InvalidTransactionsRoot(Mismatch { expected: expected_root.clone(), found: transactions_root.clone() }))) + } + let expected_uncles = &block.at(2).as_raw().sha3(); + if expected_uncles != uncles_hash { + return Err(From::from(BlockError::InvalidUnclesHash(Mismatch { expected: expected_uncles.clone(), found: uncles_hash.clone() }))) + } + Ok(()) +} +