diff --git a/Cargo.toml b/Cargo.toml index 872e1e675..eef261317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ ethash = { path = "ethash" } num_cpus = "0.2" clippy = "0.0.37" crossbeam = "0.1.5" +lazy_static = "0.1" [features] jit = ["evmjit"] diff --git a/res/ethereum/frontier.json b/res/ethereum/frontier.json index 9cb456ce8..43be680d2 100644 --- a/res/ethereum/frontier.json +++ b/res/ethereum/frontier.json @@ -3,7 +3,7 @@ "engineName": "Ethash", "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0xDBBA0", + "frontierCompatibilityModeLimit": "0xdbba0", "maximumExtraDataSize": "0x20", "tieBreakingGas": false, "minGasLimit": "0x1388", diff --git a/res/ethereum/homestead_test.json b/res/ethereum/homestead_test.json index b11ef9740..1fb5dff80 100644 --- a/res/ethereum/homestead_test.json +++ b/res/ethereum/homestead_test.json @@ -3,7 +3,7 @@ "engineName": "Ethash", "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0", + "frontierCompatibilityModeLimit": 0, "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "tieBreakingGas": false, diff --git a/res/ethereum/morden.json b/res/ethereum/morden.json index 32fed0cab..cdcf8c7dc 100644 --- a/res/ethereum/morden.json +++ b/res/ethereum/morden.json @@ -3,7 +3,7 @@ "engineName": "Ethash", "params": { "accountStartNonce": "0x0100000", - "frontierCompatibilityModeLimit": "0xDBBA0", + "frontierCompatibilityModeLimit": "0xdbba0", "maximumExtraDataSize": "0x20", "tieBreakingGas": false, "minGasLimit": "0x1388", diff --git a/src/block_queue.rs b/src/block_queue.rs index 5ce649710..206388042 100644 --- a/src/block_queue.rs +++ b/src/block_queue.rs @@ -292,12 +292,68 @@ mod tests { use util::*; use spec::*; use block_queue::*; + use tests::helpers::*; + use error::*; + + fn get_test_queue() -> BlockQueue { + let spec = get_test_spec(); + let engine = spec.to_engine().unwrap(); + BlockQueue::new(Arc::new(engine), IoChannel::disconnected()) + } #[test] - fn test_block_queue() { + fn can_be_created() { // TODO better test let spec = Spec::new_test(); let engine = spec.to_engine().unwrap(); let _ = BlockQueue::new(Arc::new(engine), IoChannel::disconnected()); } + + #[test] + fn can_import_blocks() { + let mut queue = get_test_queue(); + if let Err(e) = queue.import_block(get_good_dummy_block()) { + panic!("error importing block that is valid by definition({:?})", e); + } + } + + #[test] + fn returns_error_for_duplicates() { + let mut queue = get_test_queue(); + if let Err(e) = queue.import_block(get_good_dummy_block()) { + panic!("error importing block that is valid by definition({:?})", e); + } + + let duplicate_import = queue.import_block(get_good_dummy_block()); + match duplicate_import { + Err(e) => { + match e { + ImportError::AlreadyQueued => {}, + _ => { panic!("must return AlreadyQueued error"); } + } + } + Ok(_) => { panic!("must produce error"); } + } + } + + #[test] + fn returns_error_for_verified_duplicates() { + let mut queue = get_test_queue(); + if let Err(e) = queue.import_block(get_good_dummy_block()) { + panic!("error importing block that is valid by definition({:?})", e); + } + queue.drain(10); + queue.flush(); + + let duplicate_import = queue.import_block(get_good_dummy_block()); + match duplicate_import { + Err(e) => { + match e { + ImportError::AlreadyQueued => {}, + _ => { panic!("must return AlreadyQueued error"); } + } + } + Ok(_) => { panic!("must produce error"); } + } + } } diff --git a/src/blockchain.rs b/src/blockchain.rs index 753b83587..2610407ac 100644 --- a/src/blockchain.rs +++ b/src/blockchain.rs @@ -763,4 +763,44 @@ mod tests { assert_eq!(bc.best_block_hash(), b1_hash); } } + + #[test] + fn can_contain_arbitrary_block_sequence() { + let bc_result = generate_dummy_blockchain(50); + let bc = bc_result.reference(); + assert_eq!(bc.best_block_number(), 49); + } + + #[test] + fn can_collect_garbage() { + let bc_result = generate_dummy_blockchain(3000); + let bc = bc_result.reference(); + + assert_eq!(bc.best_block_number(), 2999); + let best_hash = bc.best_block_hash(); + let mut block_header = bc.block_header(&best_hash); + + while !block_header.is_none() { + block_header = bc.block_header(&block_header.unwrap().parent_hash); + } + assert!(bc.cache_size().blocks > 1024 * 1024); + + bc.collect_garbage(true); + + assert!(bc.cache_size().blocks < 1024 * 1024); + } + + #[test] + fn can_contain_arbitrary_block_sequence_with_extra() { + let bc_result = generate_dummy_blockchain_with_extra(25); + let bc = bc_result.reference(); + assert_eq!(bc.best_block_number(), 24); + } + + #[test] + fn can_contain_only_genesis_block() { + let bc_result = generate_dummy_empty_blockchain(); + let bc = bc_result.reference(); + assert_eq!(bc.best_block_number(), 0); + } } diff --git a/src/ethereum/ethash.rs b/src/ethereum/ethash.rs index 6419dd59c..f28ea31c6 100644 --- a/src/ethereum/ethash.rs +++ b/src/ethereum/ethash.rs @@ -59,9 +59,14 @@ impl Engine for Ethash { } fn schedule(&self, env_info: &EnvInfo) -> Schedule { + trace!(target: "client", "Creating schedule. param={:?}, fCML={}", self.spec().engine_params.get("frontierCompatibilityModeLimit"), self.u64_param("frontierCompatibilityModeLimit")); match env_info.number < self.u64_param("frontierCompatibilityModeLimit") { - true => Schedule::new_frontier(), - _ => Schedule::new_homestead(), + true => { + Schedule::new_frontier() + }, + _ => { + Schedule::new_homestead() + }, } } @@ -178,12 +183,13 @@ impl Ethash { } } else { + trace!(target: "ethash", "Calculating difficulty parent.difficulty={}, header.timestamp={}, parent.timestamp={}", parent.difficulty, header.timestamp, parent.timestamp); + //block_diff = parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99) 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)) + } else { + parent.difficulty - parent.difficulty / From::from(2048) * From::from(min(diff_inc - 1, 99)) } }; target = max(min_difficulty, target); diff --git a/src/lib.rs b/src/lib.rs index 8dd02b3bc..e084635dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,8 @@ extern crate evmjit; #[macro_use] extern crate ethcore_util as util; extern crate crossbeam; +#[macro_use] +extern crate lazy_static; // NOTE: Add doc parser exception for these pub declarations. diff --git a/src/spec.rs b/src/spec.rs index 348fe3205..d51246b23 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -16,7 +16,7 @@ pub fn gzip64res_to_json(source: &[u8]) -> Json { Json::from_str(&s).expect("Json is invalid") } -/// Convert JSON value to equivlaent RLP representation. +/// Convert JSON value to equivalent RLP representation. // TODO: handle container types. fn json_to_rlp(json: &Json) -> Bytes { match *json { @@ -77,6 +77,10 @@ pub struct Spec { pub gas_used: U256, /// TODO [Gav Wood] Please document me pub timestamp: u64, + /// Transactions root of the genesis block. Should be SHA3_NULL_RLP. + pub transactions_root: H256, + /// Receipts root of the genesis block. Should be SHA3_NULL_RLP. + pub receipts_root: H256, /// TODO [arkpar] Please document me pub extra_data: Bytes, /// TODO [Gav Wood] Please document me @@ -120,11 +124,11 @@ impl Spec { timestamp: self.timestamp, number: 0, author: self.author.clone(), - transactions_root: SHA3_NULL_RLP.clone(), + transactions_root: self.transactions_root.clone(), uncles_hash: RlpStream::new_list(0).out().sha3(), extra_data: self.extra_data.clone(), state_root: self.state_root().clone(), - receipts_root: SHA3_NULL_RLP.clone(), + receipts_root: self.receipts_root.clone(), log_bloom: H2048::new().clone(), gas_used: self.gas_used.clone(), gas_limit: self.gas_limit.clone(), @@ -172,6 +176,8 @@ impl Spec { }; self.parent_hash = H256::from_json(&genesis["parentHash"]); + self.transactions_root = genesis.find("transactionsTrie").and_then(|_| Some(H256::from_json(&genesis["transactionsTrie"]))).unwrap_or(SHA3_NULL_RLP.clone()); + self.receipts_root = genesis.find("receiptTrie").and_then(|_| Some(H256::from_json(&genesis["receiptTrie"]))).unwrap_or(SHA3_NULL_RLP.clone()); self.author = Address::from_json(&genesis["coinbase"]); self.difficulty = U256::from_json(&genesis["difficulty"]); self.gas_limit = U256::from_json(&genesis["gasLimit"]); @@ -248,6 +254,8 @@ impl FromJson for Spec { gas_limit: U256::from_str(&genesis["gasLimit"].as_string().unwrap()[2..]).unwrap(), gas_used: U256::from(0u8), timestamp: u64::from_str(&genesis["timestamp"].as_string().unwrap()[2..]).unwrap(), + transactions_root: SHA3_NULL_RLP.clone(), + receipts_root: SHA3_NULL_RLP.clone(), extra_data: genesis["extraData"].as_string().unwrap()[2..].from_hex().unwrap(), genesis_state: state, seal_fields: seal_fields, diff --git a/src/tests/chain.rs b/src/tests/chain.rs index 84ca35058..e260deddc 100644 --- a/src/tests/chain.rs +++ b/src/tests/chain.rs @@ -1,3 +1,6 @@ +use std::env; +use log::{LogLevelFilter}; +use env_logger::LogBuilder; use super::test_common::*; use client::{BlockChainClient,Client}; use pod_state::*; @@ -10,7 +13,22 @@ pub enum ChainEra { Homestead, } +lazy_static! { + static ref LOG_DUMMY: bool = { + let mut builder = LogBuilder::new(); + builder.filter(None, LogLevelFilter::Info); + + if env::var("RUST_LOG").is_ok() { + builder.parse(&env::var("RUST_LOG").unwrap()); + } + + builder.init().unwrap(); + true + }; +} + pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { + let _ = LOG_DUMMY.deref(); let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid"); let mut failed = Vec::new(); @@ -35,10 +53,13 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { spec.set_genesis_state(s); spec.overwrite_genesis(test.find("genesisBlockHeader").unwrap()); assert!(spec.is_state_root_valid()); + let genesis_hash = spec.genesis_header().hash(); + assert_eq!(genesis_hash, H256::from_json(&test.find("genesisBlockHeader").unwrap()["hash"])); let temp = RandomTempPath::new(); { let client = Client::new(spec, temp.as_path(), IoChannel::disconnected()).unwrap(); + assert_eq!(client.chain_info().best_block_hash, genesis_hash); for (b, is_valid) in blocks.into_iter() { if Block::is_good(&b) { let _ = client.import_block(b.clone()); diff --git a/src/tests/client.rs b/src/tests/client.rs index f6d603f43..56b6e7db0 100644 --- a/src/tests/client.rs +++ b/src/tests/client.rs @@ -2,49 +2,6 @@ use client::{BlockChainClient,Client}; use super::test_common::*; use super::helpers::*; -fn get_good_dummy_block() -> Bytes { - let mut block_header = Header::new(); - let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); - block_header.timestamp = 40; - block_header.number = 1; - block_header.parent_hash = test_engine.spec().genesis_header().hash(); - block_header.state_root = test_engine.spec().genesis_header().state_root; - - create_test_block(&block_header) -} - -fn get_bad_state_dummy_block() -> Bytes { - let mut block_header = Header::new(); - let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); - block_header.timestamp = 40; - block_header.number = 1; - block_header.parent_hash = test_engine.spec().genesis_header().hash(); - block_header.state_root = x!(0xbad); - - create_test_block(&block_header) -} - - -fn get_test_client_with_blocks(blocks: Vec) -> Arc { - let dir = RandomTempPath::new(); - let client = Client::new(get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); - for block in &blocks { - if let Err(_) = client.import_block(block.clone()) { - panic!("panic importing block which is well-formed"); - } - } - client.flush_queue(); - client.import_verified_blocks(&IoChannel::disconnected()); - client -} - - #[test] fn created() { let dir = RandomTempPath::new(); @@ -86,7 +43,8 @@ fn query_none_block() { #[test] fn query_bad_block() { - let client = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); + let client_result = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); + let client = client_result.reference(); let bad_block:Option = client.block_header_at(1); assert!(bad_block.is_none()); @@ -95,7 +53,8 @@ fn query_bad_block() { #[test] fn returns_chain_info() { let dummy_block = get_good_dummy_block(); - let client = get_test_client_with_blocks(vec![dummy_block.clone()]); + let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); + let client = client_result.reference(); let block = BlockView::new(&dummy_block); let info = client.chain_info(); assert_eq!(info.best_block_hash, block.header().hash()); @@ -103,7 +62,8 @@ fn returns_chain_info() { #[test] fn imports_block_sequence() { - let client = generate_dummy_client(6); + let client_result = generate_dummy_client(6); + let client = client_result.reference(); let block = client.block_header_at(5).unwrap(); assert!(!block.is_empty()); @@ -111,7 +71,8 @@ fn imports_block_sequence() { #[test] fn can_collect_garbage() { - let client = generate_dummy_client(100); + let client_result = generate_dummy_client(100); + let client = client_result.reference(); client.tick(); assert!(client.cache_info().blocks < 100 * 1024); } \ No newline at end of file diff --git a/src/tests/helpers.rs b/src/tests/helpers.rs index a566392cc..f87138795 100644 --- a/src/tests/helpers.rs +++ b/src/tests/helpers.rs @@ -4,6 +4,7 @@ use super::test_common::*; use std::path::PathBuf; use spec::*; use std::fs::{remove_dir_all}; +use blockchain::{BlockChain}; pub struct RandomTempPath { @@ -32,6 +33,18 @@ impl Drop for RandomTempPath { } } +#[allow(dead_code)] +pub struct GuardedTempResult { + result: T, + temp: RandomTempPath +} + +impl GuardedTempResult { + pub fn reference(&self) -> &T { + &self.result + } +} + pub fn get_test_spec() -> Spec { Spec::new_test() } @@ -44,7 +57,36 @@ pub fn create_test_block(header: &Header) -> Bytes { rlp.out() } -pub fn generate_dummy_client(block_number: usize) -> Arc { +fn create_unverifiable_block_header(order: u32, parent_hash: H256) -> Header { + let mut header = Header::new(); + header.gas_limit = x!(0); + header.difficulty = x!(order * 100); + header.timestamp = (order * 10) as u64; + header.number = order as u64; + header.parent_hash = parent_hash; + header.state_root = H256::zero(); + + header +} + +fn create_unverifiable_block_with_extra(order: u32, parent_hash: H256, extra: Option) -> Bytes { + let mut header = create_unverifiable_block_header(order, parent_hash); + header.extra_data = match extra { + Some(extra_data) => extra_data, + None => { + let base = (order & 0x000000ff) as u8; + let generated: Vec = vec![base + 1, base + 2, base + 3]; + generated + } + }; + create_test_block(&header) +} + +fn create_unverifiable_block(order: u32, parent_hash: H256) -> Bytes { + create_test_block(&create_unverifiable_block_header(order, parent_hash)) +} + +pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult> { let dir = RandomTempPath::new(); let client = Client::new(get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); @@ -76,5 +118,90 @@ pub fn generate_dummy_client(block_number: usize) -> Arc { } client.flush_queue(); client.import_verified_blocks(&IoChannel::disconnected()); - client -} \ No newline at end of file + + GuardedTempResult::> { + temp: dir, + result: client + } +} + +pub fn get_test_client_with_blocks(blocks: Vec) -> GuardedTempResult> { + let dir = RandomTempPath::new(); + let client = Client::new(get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + for block in &blocks { + if let Err(_) = client.import_block(block.clone()) { + panic!("panic importing block which is well-formed"); + } + } + client.flush_queue(); + client.import_verified_blocks(&IoChannel::disconnected()); + + GuardedTempResult::> { + temp: dir, + result: client + } +} + +pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { + let temp = RandomTempPath::new(); + let bc = BlockChain::new(&create_unverifiable_block(0, H256::zero()), temp.as_path()); + for block_order in 1..block_number { + bc.insert_block(&create_unverifiable_block(block_order, bc.best_block_hash())); + } + + GuardedTempResult:: { + temp: temp, + result: bc + } +} + +pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempResult { + let temp = RandomTempPath::new(); + let bc = BlockChain::new(&create_unverifiable_block(0, H256::zero()), temp.as_path()); + for block_order in 1..block_number { + bc.insert_block(&create_unverifiable_block_with_extra(block_order, bc.best_block_hash(), None)); + } + + GuardedTempResult:: { + temp: temp, + result: bc + } +} + +pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { + let temp = RandomTempPath::new(); + let bc = BlockChain::new(&create_unverifiable_block(0, H256::zero()), temp.as_path()); + + GuardedTempResult:: { + temp: temp, + result: bc + } +} + +pub fn get_good_dummy_block() -> Bytes { + let mut block_header = Header::new(); + let test_spec = get_test_spec(); + let test_engine = test_spec.to_engine().unwrap(); + block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); + block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + block_header.timestamp = 40; + block_header.number = 1; + block_header.parent_hash = test_engine.spec().genesis_header().hash(); + block_header.state_root = test_engine.spec().genesis_header().state_root; + + create_test_block(&block_header) +} + +pub fn get_bad_state_dummy_block() -> Bytes { + let mut block_header = Header::new(); + let test_spec = get_test_spec(); + let test_engine = test_spec.to_engine().unwrap(); + block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); + block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + block_header.timestamp = 40; + block_header.number = 1; + block_header.parent_hash = test_engine.spec().genesis_header().hash(); + block_header.state_root = x!(0xbad); + + create_test_block(&block_header) +}