From 373284ca0aaa2efa44824ed983294d7e94f942e1 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Sat, 9 Apr 2016 19:20:35 +0200 Subject: [PATCH] spec loading cleanup (#858) * spec loading cleanup in progress * changed engine field in json spec * refactored engine params * polishing spec loading refactor * fixed compiling json tests * fixed compiling parity * removed warnings * removed commented out code * fixed failing test * bringing back removed TODO in spec. --- ethcore/res/ethereum/frontier.json | 29 ++- ethcore/res/ethereum/frontier_like_test.json | 29 ++- ethcore/res/ethereum/frontier_test.json | 29 ++- ethcore/res/ethereum/homestead_test.json | 29 ++- ethcore/res/ethereum/morden.json | 29 ++- ethcore/res/ethereum/olympic.json | 29 ++- ethcore/res/null_homestead_morden.json | 19 +- ethcore/res/null_morden.json | 19 +- ethcore/src/block.rs | 26 +- ethcore/src/block_queue.rs | 6 +- ethcore/src/builtin.rs | 84 +++---- ethcore/src/client/client.rs | 7 +- ethcore/src/engine.rs | 36 ++- ethcore/src/env_info.rs | 37 +-- ethcore/src/ethereum/ethash.rs | 155 ++++++------ ethcore/src/ethereum/mod.rs | 23 +- ethcore/src/externalities.rs | 6 +- ethcore/src/json_tests/executive.rs | 29 +-- ethcore/src/json_tests/state.rs | 6 +- ethcore/src/log_entry.rs | 12 - ethcore/src/null_engine.rs | 39 +-- ethcore/src/pod_account.rs | 18 +- ethcore/src/pod_state.rs | 62 +---- ethcore/src/service.rs | 2 +- ethcore/src/spec/genesis.rs | 32 +-- ethcore/src/spec/mod.rs | 1 + ethcore/src/spec/seal.rs | 81 +++++++ ethcore/src/spec/spec.rs | 240 ++++++------------- ethcore/src/state.rs | 8 +- ethcore/src/tests/client.rs | 3 +- ethcore/src/tests/helpers.rs | 65 ++--- ethcore/src/verification/verification.rs | 8 +- json/src/blockchain/blockchain.rs | 10 +- json/src/blockchain/state.rs | 2 +- json/src/spec/account.rs | 16 +- json/src/spec/builtin.rs | 18 +- json/src/spec/engine.rs | 63 +++++ json/src/spec/ethash.rs | 75 ++++++ json/src/spec/genesis.rs | 27 +-- json/src/spec/mod.rs | 10 +- json/src/spec/params.rs | 43 ++-- json/src/spec/seal.rs | 73 ++++++ json/src/spec/spec.rs | 63 +++-- json/src/spec/state.rs | 44 ++++ json/src/uint.rs | 17 +- parity/main.rs | 2 +- 46 files changed, 957 insertions(+), 704 deletions(-) create mode 100644 ethcore/src/spec/seal.rs create mode 100644 json/src/spec/engine.rs create mode 100644 json/src/spec/ethash.rs create mode 100644 json/src/spec/seal.rs create mode 100644 json/src/spec/state.rs diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 41e69d24d..a8a26b1c3 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -1,24 +1,33 @@ { "name": "Frontier/Homestead", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/frontier_like_test.json b/ethcore/res/ethereum/frontier_like_test.json index 376b369c4..fec7c4371 100644 --- a/ethcore/res/ethereum/frontier_like_test.json +++ b/ethcore/res/ethereum/frontier_like_test.json @@ -1,24 +1,33 @@ { "name": "Frontier (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/frontier_test.json b/ethcore/res/ethereum/frontier_test.json index 92e8f5877..4f0284d14 100644 --- a/ethcore/res/ethereum/frontier_test.json +++ b/ethcore/res/ethereum/frontier_test.json @@ -1,24 +1,33 @@ { "name": "Frontier (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/homestead_test.json b/ethcore/res/ethereum/homestead_test.json index 0f0e630dd..962592404 100644 --- a/ethcore/res/ethereum/homestead_test.json +++ b/ethcore/res/ethereum/homestead_test.json @@ -1,24 +1,33 @@ { "name": "Homestead (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": 0, "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "tieBreakingGas": false, - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 2cf4785db..0723e2e9d 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -1,24 +1,33 @@ { "name": "Morden", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar": "" + } + } + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/olympic.json b/ethcore/res/ethereum/olympic.json index 0cc2e6a57..8ee9f1d66 100644 --- a/ethcore/res/ethereum/olympic.json +++ b/ethcore/res/ethereum/olympic.json @@ -1,24 +1,33 @@ { "name": "Olympic", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x08", + "blockReward": "0x14D1120D7B160000", + "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x0400", - "tieBreakingGas": false, "minGasLimit": "125000", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x08", - "blockReward": "0x14D1120D7B160000", - "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050", "networkID" : "0x0" }, "genesis": { - "nonce": "0x000000000000002a", + "seal": { + "ethereum": { + "nonce": "0x000000000000002a", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x20000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/null_homestead_morden.json b/ethcore/res/null_homestead_morden.json index abd3f4de9..145540853 100644 --- a/ethcore/res/null_homestead_morden.json +++ b/ethcore/res/null_homestead_morden.json @@ -1,24 +1,23 @@ { "name": "Morden", - "engineName": "NullEngine", + "engine": { + "Null": null + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/null_morden.json b/ethcore/res/null_morden.json index 86148d640..48631a7ce 100644 --- a/ethcore/res/null_morden.json +++ b/ethcore/res/null_morden.json @@ -1,24 +1,23 @@ { "name": "Morden", - "engineName": "NullEngine", + "engine": { + "Null": null + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index c5e1ca003..b8af3f733 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -497,16 +497,16 @@ mod tests { use tests::helpers::*; use super::*; use common::*; - use engine::*; #[test] fn open_block() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let b = b.close_and_lock(); @@ -516,19 +516,20 @@ mod tests { #[test] fn enact_block() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]).close_and_lock().seal(engine.deref(), vec![]).unwrap(); let orig_bytes = b.rlp_bytes(); let orig_db = b.drain(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap(); assert_eq!(e.rlp_bytes(), orig_bytes); @@ -541,12 +542,13 @@ mod tests { #[test] fn enact_block_with_uncle() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let mut open_block = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]); let mut uncle1_header = Header::new(); uncle1_header.extra_data = b"uncle1".to_vec(); @@ -561,7 +563,7 @@ mod tests { let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap(); let bytes = e.rlp_bytes(); diff --git a/ethcore/src/block_queue.rs b/ethcore/src/block_queue.rs index bc1da2f8f..bf11f5f1f 100644 --- a/ethcore/src/block_queue.rs +++ b/ethcore/src/block_queue.rs @@ -464,7 +464,7 @@ mod tests { fn get_test_queue() -> BlockQueue { let spec = get_test_spec(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected()) } @@ -472,7 +472,7 @@ mod tests { fn can_be_created() { // TODO better test let spec = Spec::new_test(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; let _ = BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected()); } @@ -533,7 +533,7 @@ mod tests { #[test] fn test_mem_limit() { let spec = get_test_spec(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; let mut config = BlockQueueConfig::default(); config.max_mem_use = super::MIN_MEM_LIMIT; // empty queue uses about 15000 let queue = BlockQueue::new(config, Arc::new(engine), IoChannel::disconnected()); diff --git a/ethcore/src/builtin.rs b/ethcore/src/builtin.rs index 2ee8e24b1..3c15dd1c2 100644 --- a/ethcore/src/builtin.rs +++ b/ethcore/src/builtin.rs @@ -18,6 +18,7 @@ use util::*; use crypto::sha2::Sha256; use crypto::ripemd160::Ripemd160; use crypto::digest::Digest; +use ethjson; /// Definition of a contract whose implementation is built-in. pub struct Builtin { @@ -46,13 +47,12 @@ impl Builtin { } /// Create a new object from a builtin-function name with a linear cost associated with input size. - pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Option { - new_builtin_exec(name).map(|b| { - let cost = Box::new(move|s: usize| -> U256 { - U256::from(base_cost) + U256::from(word_cost) * U256::from((s + 31) / 32) - }); - Self::new(cost, b) - }) + pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Builtin { + let cost = Box::new(move|s: usize| -> U256 { + U256::from(base_cost) + U256::from(word_cost) * U256::from((s + 31) / 32) + }); + + Self::new(cost, new_builtin_exec(name)) } /// Simple forwarder for cost. @@ -60,24 +60,15 @@ impl Builtin { /// Simple forwarder for execute. pub fn execute(&self, input: &[u8], output: &mut[u8]) { (*self.execute)(input, output); } +} - /// Create a builtin from JSON. - /// - /// JSON must be of the form `{ "name": "identity", "pricing": {"base": 10, "word": 20} }`. - pub fn from_json(json: &Json) -> Option { - // NICE: figure out a more convenient means of handing errors here. - if let Json::String(ref name) = json["name"] { - if let Json::Object(ref o) = json["pricing"] { - if let Json::Object(ref o) = o["linear"] { - if let Json::U64(ref word) = o["word"] { - if let Json::U64(ref base) = o["base"] { - return Self::from_named_linear(&name[..], *base as usize, *word as usize); - } - } - } +impl From for Builtin { + fn from(b: ethjson::spec::Builtin) -> Self { + match b.pricing { + ethjson::spec::Pricing::Linear(linear) => { + Self::from_named_linear(b.name.as_ref(), linear.base, linear.word) } } - None } } @@ -92,14 +83,14 @@ pub fn copy_to(src: &[u8], dest: &mut[u8]) { /// Create a new builtin executor according to `name`. /// TODO: turn in to a factory with dynamic registration. -pub fn new_builtin_exec(name: &str) -> Option> { +pub fn new_builtin_exec(name: &str) -> Box { match name { - "identity" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + "identity" => Box::new(move|input: &[u8], output: &mut[u8]| { for i in 0..min(input.len(), output.len()) { output[i] = input[i]; } - })), - "ecrecover" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "ecrecover" => Box::new(move|input: &[u8], output: &mut[u8]| { #[repr(packed)] #[derive(Debug)] struct InType { @@ -122,8 +113,8 @@ pub fn new_builtin_exec(name: &str) -> Option> { } } } - })), - "sha256" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "sha256" => Box::new(move|input: &[u8], output: &mut[u8]| { let mut sha = Sha256::new(); sha.input(input); if output.len() >= 32 { @@ -133,21 +124,23 @@ pub fn new_builtin_exec(name: &str) -> Option> { sha.result(ret.as_slice_mut()); copy_to(&ret, output); } - })), - "ripemd160" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "ripemd160" => Box::new(move|input: &[u8], output: &mut[u8]| { let mut sha = Ripemd160::new(); sha.input(input); let mut ret = H256::new(); sha.result(&mut ret.as_slice_mut()[12..32]); copy_to(&ret, output); - })), - _ => None + }), + _ => { + panic!("invalid builtin name {}", name); + } } } #[test] fn identity() { - let f = new_builtin_exec("identity").unwrap(); + let f = new_builtin_exec("identity"); let i = [0u8, 1, 2, 3]; let mut o2 = [255u8; 2]; @@ -167,7 +160,7 @@ fn identity() { #[test] fn sha256() { use rustc_serialize::hex::FromHex; - let f = new_builtin_exec("sha256").unwrap(); + let f = new_builtin_exec("sha256"); let i = [0u8; 0]; let mut o = [255u8; 32]; @@ -186,7 +179,7 @@ fn sha256() { #[test] fn ripemd160() { use rustc_serialize::hex::FromHex; - let f = new_builtin_exec("ripemd160").unwrap(); + let f = new_builtin_exec("ripemd160"); let i = [0u8; 0]; let mut o = [255u8; 32]; @@ -213,7 +206,7 @@ fn ecrecover() { let s = k.sign(&m).unwrap(); println!("Signed: {}", s);*/ - let f = new_builtin_exec("ecrecover").unwrap(); + let f = new_builtin_exec("ecrecover"); let i = FromHex::from_hex("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap(); let mut o = [255u8; 32]; @@ -260,9 +253,15 @@ fn ecrecover() { assert_eq!(&o[..], &(FromHex::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())[..]);*/ } +#[test] +#[should_panic] +fn from_unknown_linear() { + let _ = Builtin::from_named_linear("dw", 10, 20); +} + #[test] fn from_named_linear() { - let b = Builtin::from_named_linear("identity", 10, 20).unwrap(); + let b = Builtin::from_named_linear("identity", 10, 20); assert_eq!((*b.cost)(0), U256::from(10)); assert_eq!((*b.cost)(1), U256::from(30)); assert_eq!((*b.cost)(32), U256::from(30)); @@ -276,9 +275,14 @@ fn from_named_linear() { #[test] fn from_json() { - let text = r#"{"name": "identity", "pricing": {"linear": {"base": 10, "word": 20}}}"#; - let json = Json::from_str(text).unwrap(); - let b = Builtin::from_json(&json).unwrap(); + let b = Builtin::from(ethjson::spec::Builtin { + name: "identity".to_owned(), + pricing: ethjson::spec::Pricing::Linear(ethjson::spec::Linear { + base: 10, + word: 20, + }) + }); + assert_eq!((*b.cost)(0), U256::from(10)); assert_eq!((*b.cost)(1), U256::from(30)); assert_eq!((*b.cost)(32), U256::from(30)); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 554a839e1..781bea7fa 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -140,14 +140,15 @@ impl Client where V: Verifier { let mut state_path = path.to_path_buf(); state_path.push("state"); - let engine = Arc::new(try!(spec.to_engine())); let state_path_str = state_path.to_str().unwrap(); let mut state_db = journaldb::new(state_path_str, config.pruning); - if state_db.is_empty() && engine.spec().ensure_db_good(state_db.as_hashdb_mut()) { - state_db.commit(0, &engine.spec().genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); + if state_db.is_empty() && spec.ensure_db_good(state_db.as_hashdb_mut()) { + state_db.commit(0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); } + let engine = Arc::new(spec.engine); + let block_queue = BlockQueue::new(config.queue, engine.clone(), message_channel); let panic_handler = PanicHandler::new_in_arc(); panic_handler.forward_from(&block_queue); diff --git a/ethcore/src/engine.rs b/ethcore/src/engine.rs index 7698af529..74b9dff26 100644 --- a/ethcore/src/engine.rs +++ b/ethcore/src/engine.rs @@ -16,7 +16,7 @@ use common::*; use block::ExecutedBlock; -use spec::Spec; +use spec::CommonParams; use evm::Schedule; use evm::Factory; @@ -25,7 +25,7 @@ use evm::Factory; pub trait Engine : Sync + Send { /// The name of this engine. fn name(&self) -> &str; - /// The version of this engine. Should be of the form + /// The version of this engine. Should be of the form fn version(&self) -> SemanticVersion { SemanticVersion::new(0, 0, 0) } /// The number of additional header fields required for this engine. @@ -35,7 +35,7 @@ pub trait Engine : Sync + Send { fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } /// Get the general parameters of the chain. - fn spec(&self) -> &Spec; + fn params(&self) -> &CommonParams; /// Get current EVM factory fn vm_factory(&self) -> &Factory; @@ -43,29 +43,33 @@ pub trait Engine : Sync + Send { /// Get the EVM schedule for the given `env_info`. fn schedule(&self, env_info: &EnvInfo) -> Schedule; + /// Builtin-contracts we would like to see in the chain. + /// (In principle these are just hints for the engine since that has the last word on them.) + fn builtins(&self) -> &BTreeMap; + /// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`. - fn maximum_extra_data_size(&self) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) } + fn maximum_extra_data_size(&self) -> usize { self.params().maximum_extra_data_size } /// Maximum number of uncles a block is allowed to declare. fn maximum_uncle_count(&self) -> usize { 2 } /// The number of generations back that uncles can be. fn maximum_uncle_age(&self) -> usize { 6 } /// The nonce with which accounts begin. - fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) } + fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce } /// Block transformation functions, before the transactions. fn on_new_block(&self, _block: &mut ExecutedBlock) {} /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} - /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) + /// 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) + /// 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) + /// 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_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } @@ -94,23 +98,13 @@ pub trait Engine : Sync + Send { // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. /// Determine whether a particular address is a builtin contract. - fn is_builtin(&self, a: &Address) -> bool { self.spec().builtins.contains_key(a) } + fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) } /// Determine the code execution cost of the builtin contract with address `a`. /// Panics if `is_builtin(a)` is not true. - fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.spec().builtins.get(a).unwrap().cost(input.len()) } + fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.builtins().get(a).unwrap().cost(input.len()) } /// Execution the builtin contract `a` on `input` and return `output`. /// Panics if `is_builtin(a)` is not true. - fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.spec().builtins.get(a).unwrap().execute(input, output); } + fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.builtins().get(a).unwrap().execute(input, output); } // TODO: sealing stuff - though might want to leave this for later. - - /// Get a named parameter from the `spec()`'s `engine_params` item and convert to u64, or 0 if that fails. - fn u64_param(&self, name: &str) -> u64 { - self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a)) - } - - /// Get a named parameter from the `spec()`'s `engine_params` item and convert to U256, or 0 if that fails. - fn u256_param(&self, name: &str) -> U256 { - self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a)) - } } diff --git a/ethcore/src/env_info.rs b/ethcore/src/env_info.rs index 1a4e71c67..18b8af856 100644 --- a/ethcore/src/env_info.rs +++ b/ethcore/src/env_info.rs @@ -55,21 +55,6 @@ impl Default for EnvInfo { } } -impl FromJson for EnvInfo { - fn from_json(json: &Json) -> EnvInfo { - let current_number: u64 = xjson!(&json["currentNumber"]); - EnvInfo { - number: current_number, - author: xjson!(&json["currentCoinbase"]), - difficulty: xjson!(&json["currentDifficulty"]), - gas_limit: xjson!(&json["currentGasLimit"]), - timestamp: xjson!(&json["currentTimestamp"]), - last_hashes: (1..cmp::min(current_number + 1, 257)).map(|i| format!("{}", current_number - i).as_bytes().sha3()).collect(), - gas_used: x!(0), - } - } -} - impl From for EnvInfo { fn from(e: ethjson::vm::Env) -> Self { let number = e.number.into(); @@ -90,24 +75,20 @@ mod tests { extern crate rustc_serialize; use super::*; - use rustc_serialize::*; - use util::from_json::FromJson; use util::hash::*; + use util::numbers::U256; use std::str::FromStr; + use ethjson; #[test] fn it_serializes_form_json() { - let env_info = EnvInfo::from_json(&json::Json::from_str( -r#" - { - "currentCoinbase": "0x000000f00000000f000000000000f00000000f00", - "currentNumber": 1112339, - "currentDifficulty": 50000, - "currentGasLimit" : 40000, - "currentTimestamp" : 1100 - } -"# - ).unwrap()); + let env_info = EnvInfo::from(ethjson::vm::Env { + author: ethjson::hash::Address(Address::from_str("000000f00000000f000000000000f00000000f00").unwrap()), + number: ethjson::uint::Uint(U256::from(1_112_339)), + difficulty: ethjson::uint::Uint(U256::from(50_000)), + gas_limit: ethjson::uint::Uint(U256::from(40_000)), + timestamp: ethjson::uint::Uint(U256::from(1_100)) + }); assert_eq!(env_info.number, 1112339); assert_eq!(env_info.author, Address::from_str("000000f00000000f000000000000f00000000f00").unwrap()); diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index d53ab09de..9d4a87eed 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -19,42 +19,63 @@ extern crate ethash; use self::ethash::{quick_get_difficulty, EthashManager, H256 as EH256}; use common::*; use block::*; -use spec::*; +use spec::CommonParams; use engine::*; -use evm::Schedule; -use evm::Factory; +use evm::{Schedule, Factory}; +use ethjson; + +/// Ethash params. +#[derive(Debug, PartialEq)] +pub struct EthashParams { + /// Tie breaking gas. + pub tie_breaking_gas: bool, + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Minimum difficulty. + pub minimum_difficulty: U256, + /// Difficulty bound divisor. + pub difficulty_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// Block reward. + pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, +} + +impl From for EthashParams { + fn from(p: ethjson::spec::EthashParams) -> Self { + EthashParams { + tie_breaking_gas: p.tie_breaking_gas, + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + minimum_difficulty: p.minimum_difficulty.into(), + difficulty_bound_divisor: p.difficulty_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + block_reward: p.block_reward.into(), + registrar: p.registrar.into(), + } + } +} /// Engine using Ethash proof-of-work consensus algorithm, suitable for Ethereum /// mainnet chains in the Olympic, Frontier and Homestead eras. pub struct Ethash { - spec: Spec, + params: CommonParams, + ethash_params: EthashParams, + builtins: BTreeMap, pow: EthashManager, factory: Factory, - u64_params: RwLock>, - u256_params: RwLock>, } impl Ethash { - /// Create a new boxed instance of Ethash engine - pub fn new_boxed(spec: Spec) -> Box { - Box::new(Ethash { - spec: spec, - pow: EthashManager::new(), - // TODO [todr] should this return any specific factory? - factory: Factory::default(), - u64_params: RwLock::new(HashMap::new()), - u256_params: RwLock::new(HashMap::new()) - }) - } - - #[cfg(test)] - fn new_test(spec: Spec) -> Ethash { + /// Create a new instance of Ethash engine + pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap) -> Self { Ethash { - spec: spec, + params: params, + ethash_params: ethash_params, + builtins: builtins, pow: EthashManager::new(), factory: Factory::default(), - u64_params: RwLock::new(HashMap::new()), - u256_params: RwLock::new(HashMap::new()) } } } @@ -65,17 +86,23 @@ impl Engine for Ethash { // Two fields - mix fn seal_fields(&self) -> usize { 2 } + fn params(&self) -> &CommonParams { &self.params } + + fn builtins(&self) -> &BTreeMap { + &self.builtins + } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } - fn spec(&self) -> &Spec { &self.spec } fn vm_factory(&self) -> &Factory { &self.factory } fn schedule(&self, env_info: &EnvInfo) -> Schedule { - trace!(target: "client", "Creating schedule. param={:?}, fCML={}", self.spec().engine_params.get("frontierCompatibilityModeLimit"), self.u64_param("frontierCompatibilityModeLimit")); - if env_info.number < self.u64_param("frontierCompatibilityModeLimit") { + trace!(target: "client", "Creating schedule. fCML={}", self.params.frontier_compatibility_mode_limit); + + if env_info.number < self.params.frontier_compatibility_mode_limit { Schedule::new_frontier() } else { Schedule::new_homestead() @@ -86,7 +113,7 @@ impl Engine for Ethash { header.difficulty = self.calculate_difficuty(header, parent); header.gas_limit = { let gas_limit = parent.gas_limit; - let bound_divisor = self.u256_param("gasLimitBoundDivisor"); + let bound_divisor = self.ethash_params.gas_limit_bound_divisor; if gas_limit < gas_floor_target { min(gas_floor_target, gas_limit + gas_limit / bound_divisor - x!(1)) } else { @@ -100,7 +127,7 @@ impl Engine for Ethash { /// Apply the block reward on finalisation of the block. /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). fn on_close_block(&self, block: &mut ExecutedBlock) { - let reward = self.spec().engine_params.get("blockReward").map_or(U256::from(0u64), |a| decode(&a)); + let reward = self.ethash_params.block_reward; let fields = block.fields_mut(); // Bestow block reward @@ -125,7 +152,7 @@ impl Engine for Ethash { try!(UntrustedRlp::new(&header.seal[1]).as_val::()); // TODO: consider removing these lines. - let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap()); + let min_difficulty = self.ethash_params.minimum_difficulty; if header.difficulty < min_difficulty { return Err(From::from(BlockError::DifficultyOutOfBounds(OutOfBounds { min: Some(min_difficulty), max: None, found: header.difficulty }))) } @@ -170,7 +197,7 @@ impl Engine for Ethash { 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 gas_limit_divisor = self.ethash_params.gas_limit_bound_divisor; 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 { @@ -180,7 +207,7 @@ impl Engine for Ethash { } fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> { - if header.number() >= self.u64_param("frontierCompatibilityModeLimit") { + if header.number() >= self.params.frontier_compatibility_mode_limit { try!(t.check_low_s()); } Ok(()) @@ -189,16 +216,6 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } - - fn u64_param(&self, name: &str) -> u64 { - *self.u64_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a))) - } - - fn u256_param(&self, name: &str) -> U256 { - *self.u256_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a))) - } } #[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self @@ -209,10 +226,11 @@ impl Ethash { panic!("Can't calculate genesis block difficulty"); } - let min_difficulty = self.u256_param("minimumDifficulty"); - let difficulty_bound_divisor = self.u256_param("difficultyBoundDivisor"); - let duration_limit = self.u64_param("durationLimit"); - let frontier_limit = self.u64_param("frontierCompatibilityModeLimit"); + let min_difficulty = self.ethash_params.minimum_difficulty; + let difficulty_bound_divisor = self.ethash_params.difficulty_bound_divisor; + let duration_limit = self.ethash_params.duration_limit; + let frontier_limit = self.params.frontier_compatibility_mode_limit; + let mut target = if header.number < frontier_limit { if header.timestamp >= parent.timestamp + duration_limit { parent.difficulty - (parent.difficulty / difficulty_bound_divisor) @@ -283,16 +301,16 @@ mod tests { use block::*; use engine::*; use tests::helpers::*; - use super::{Ethash}; use super::super::new_morden; #[test] fn on_close_block() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let b = b.close(); @@ -301,11 +319,12 @@ mod tests { #[test] fn on_close_block_with_uncle() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let mut b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let mut uncle = Header::new(); @@ -320,27 +339,20 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = Ethash::new_boxed(new_morden()); + let engine = new_morden().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } - #[test] - fn can_return_params() { - let engine = Ethash::new_test(new_morden()); - assert!(engine.u64_param("durationLimit") > 0); - assert!(engine.u256_param("minimumDifficulty") > U256::zero()); - } - #[test] fn can_return_factory() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; engine.vm_factory(); } #[test] fn can_return_schedule() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: x!(0), @@ -368,7 +380,8 @@ mod tests { #[test] fn can_do_seal_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; + //let engine = Ethash::new_test(new_morden()); let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -382,7 +395,7 @@ mod tests { #[test] fn can_do_difficulty_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); @@ -397,7 +410,7 @@ mod tests { #[test] fn can_do_proof_of_work_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -413,7 +426,7 @@ mod tests { #[test] fn can_do_seal_unordered_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_unordered(&header, None); @@ -427,7 +440,7 @@ mod tests { #[test] fn can_do_seal256_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); let verify_result = engine.verify_block_unordered(&header, None); @@ -441,7 +454,7 @@ mod tests { #[test] fn can_do_proof_of_work_unordered_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::from("b251bd2e0283d0658f2cadfdc8ca619b5de94eca5742725e2e757dd13ed7503d")).to_vec(), rlp::encode(&H64::zero()).to_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -457,7 +470,7 @@ mod tests { #[test] fn can_verify_block_family_genesis_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let header: Header = Header::default(); let parent_header: Header = Header::default(); @@ -472,7 +485,7 @@ mod tests { #[test] fn can_verify_block_family_difficulty_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_number(2); let mut parent_header: Header = Header::default(); @@ -489,7 +502,7 @@ mod tests { #[test] fn can_verify_block_family_gas_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_number(2); header.set_difficulty(U256::from_str("0000000000000000000000000000000000000000000000000000000000020000").unwrap()); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 8c2ae6b37..4cc98ee2b 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -30,22 +30,22 @@ pub use self::denominations::*; use super::spec::*; /// Create a new Olympic chain spec. -pub fn new_olympic() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/olympic.json")) } +pub fn new_olympic() -> Spec { Spec::load(include_bytes!("../../res/ethereum/olympic.json")) } /// Create a new Frontier mainnet chain spec. -pub fn new_frontier() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier.json")) } +pub fn new_frontier() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier.json")) } /// Create a new Frontier chain spec as though it never changes to Homestead. -pub fn new_frontier_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_test.json")) } +pub fn new_frontier_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_test.json")) } /// Create a new Homestead chain spec as though it never changed from Frontier. -pub fn new_homestead_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/homestead_test.json")) } +pub fn new_homestead_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/homestead_test.json")) } /// Create a new Frontier main net chain spec without genesis accounts. -pub fn new_mainnet_like() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_like_test.json")) } +pub fn new_mainnet_like() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } /// Create a new Morden chain spec. -pub fn new_morden() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/morden.json")) } +pub fn new_morden() -> Spec { Spec::load(include_bytes!("../../res/ethereum/morden.json")) } #[cfg(test)] mod tests { @@ -57,11 +57,12 @@ mod tests { #[test] fn ensure_db_good() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let s = State::from_existing(db, genesis_header.state_root.clone(), engine.account_start_nonce()); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000001")), U256::from(1u64)); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000002")), U256::from(1u64)); @@ -79,7 +80,7 @@ mod tests { let genesis = morden.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap()); - let _ = morden.to_engine(); + let _ = morden.engine; } #[test] @@ -90,6 +91,6 @@ mod tests { let genesis = frontier.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap()); - let _ = frontier.to_engine(); + let _ = frontier.engine; } } diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 3f9d4ff08..24b5fe3a4 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -208,7 +208,7 @@ impl<'a> Ext for Externalities<'a> { }, OutputPolicy::Return(BytesRef::Flexible(ref mut vec), ref mut copy) => { handle_copy(copy); - + vec.clear(); vec.reserve(data.len()); unsafe { @@ -225,7 +225,7 @@ impl<'a> Ext for Externalities<'a> { false => Ok(*gas) } } - + handle_copy(copy); let mut code = vec![]; @@ -327,7 +327,7 @@ mod tests { fn new() -> Self { TestSetup { state: get_temp_state(), - engine: get_test_spec().to_engine().unwrap(), + engine: get_test_spec().engine, sub_state: Substate::new(false), env_info: get_test_env_info() } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 283394204..bd971efdc 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -27,33 +27,6 @@ use substate::*; use tests::helpers::*; use ethjson; -struct TestEngineFrontier { - vm_factory: Factory, - spec: Spec, - max_depth: usize -} - -impl TestEngineFrontier { - fn new(max_depth: usize, vm_type: VMType) -> TestEngineFrontier { - TestEngineFrontier { - vm_factory: Factory::new(vm_type), - spec: ethereum::new_frontier_test(), - max_depth: max_depth - } - } -} - -impl Engine for TestEngineFrontier { - fn name(&self) -> &str { "TestEngine" } - fn spec(&self) -> &Spec { &self.spec } - fn vm_factory(&self) -> &Factory { &self.vm_factory } - fn schedule(&self, _env_info: &EnvInfo) -> Schedule { - let mut schedule = Schedule::new_frontier(); - schedule.max_depth = self.max_depth; - schedule - } -} - #[derive(Debug, PartialEq)] struct CallCreate { data: Bytes, @@ -206,7 +179,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { let mut state = state_result.reference_mut(); state.populate_from(From::from(vm.pre_state.clone())); let info = From::from(vm.env); - let engine = TestEngineFrontier::new(1, vm_type.clone()); + let engine = TestEngine::new(1, Factory::new(vm_type.clone())); let params = ActionParams::from(vm.transaction); let mut substate = Substate::new(false); diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 1ed7dcadb..cf7f56ce1 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -30,9 +30,9 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { let tests = ethjson::state::Test::load(json_data).unwrap(); let mut failed = Vec::new(); let engine = match era { - ChainEra::Frontier => ethereum::new_mainnet_like(), - ChainEra::Homestead => ethereum::new_homestead_test(), - }.to_engine().unwrap(); + ChainEra::Frontier => ethereum::new_mainnet_like().engine, + ChainEra::Homestead => ethereum::new_homestead_test().engine + }; for (name, test) in tests.into_iter() { let mut fail = false; diff --git a/ethcore/src/log_entry.rs b/ethcore/src/log_entry.rs index e0443cc0d..2a7ca080f 100644 --- a/ethcore/src/log_entry.rs +++ b/ethcore/src/log_entry.rs @@ -76,18 +76,6 @@ impl From for LogEntry { } } -impl FromJson for LogEntry { - /// Convert given JSON object to a LogEntry. - fn from_json(json: &Json) -> LogEntry { - // TODO: check bloom. - LogEntry { - address: xjson!(&json["address"]), - topics: xjson!(&json["topics"]), - data: xjson!(&json["data"]), - } - } -} - /// Log localized in a blockchain. #[derive(Default, Debug, PartialEq, Clone)] pub struct LocalizedLogEntry { diff --git a/ethcore/src/null_engine.rs b/ethcore/src/null_engine.rs index 99231add4..505ff5bf6 100644 --- a/ethcore/src/null_engine.rs +++ b/ethcore/src/null_engine.rs @@ -14,26 +14,29 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::BTreeMap; +use util::hash::Address; +use builtin::Builtin; use engine::Engine; -use spec::Spec; -use evm::Schedule; -use evm::Factory; +use spec::CommonParams; +use evm::{Schedule, Factory}; use env_info::EnvInfo; /// An engine which does not provide any consensus mechanism. pub struct NullEngine { - spec: Spec, - factory: Factory + params: CommonParams, + builtins: BTreeMap, + factory: Factory, } impl NullEngine { /// Returns new instance of NullEngine with default VM Factory - pub fn new_boxed(spec: Spec) -> Box { - Box::new(NullEngine{ - spec: spec, - // TODO [todr] should this return any specific factory? + pub fn new(params: CommonParams, builtins: BTreeMap) -> Self { + NullEngine{ + params: params, + builtins: builtins, factory: Factory::default() - }) + } } } @@ -41,13 +44,21 @@ impl Engine for NullEngine { fn vm_factory(&self) -> &Factory { &self.factory } - - fn name(&self) -> &str { "NullEngine" } - fn spec(&self) -> &Spec { &self.spec } + fn name(&self) -> &str { + "NullEngine" + } + + fn params(&self) -> &CommonParams { + &self.params + } + + fn builtins(&self) -> &BTreeMap { + &self.builtins + } fn schedule(&self, env_info: &EnvInfo) -> Schedule { - if env_info.number < self.u64_param("frontierCompatibilityModeLimit") { + if env_info.number < self.params.frontier_compatibility_mode_limit { Schedule::new_frontier() } else { Schedule::new_homestead() diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs index 623211ae6..27ddc8a97 100644 --- a/ethcore/src/pod_account.rs +++ b/ethcore/src/pod_account.rs @@ -80,12 +80,22 @@ impl From for PodAccount { balance: a.balance.into(), nonce: a.nonce.into(), code: a.code.into(), - storage: a.storage.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| { + storage: a.storage.into_iter().map(|(key, value)| { let key: U256 = key.into(); let value: U256 = value.into(); - acc.insert(H256::from(key), H256::from(value)); - acc - }) + (H256::from(key), H256::from(value)) + }).collect() + } + } +} + +impl From for PodAccount { + fn from(a: ethjson::spec::Account) -> Self { + PodAccount { + balance: a.balance.map_or_else(U256::zero, Into::into), + nonce: a.nonce.map_or_else(U256::zero, Into::into), + code: vec![], + storage: BTreeMap::new() } } } diff --git a/ethcore/src/pod_state.rs b/ethcore/src/pod_state.rs index 7ebfed78b..d9d0cf764 100644 --- a/ethcore/src/pod_state.rs +++ b/ethcore/src/pod_state.rs @@ -46,33 +46,20 @@ impl PodState { pub fn drain(self) -> BTreeMap { self.0 } } -impl FromJson for PodState { - /// Translate the JSON object into a hash map of account information ready for insertion into State. - fn from_json(json: &Json) -> PodState { - PodState(json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut state, (address, acc)| { - let balance = acc.find("balance").map(&U256::from_json); - let nonce = acc.find("nonce").map(&U256::from_json); - let storage = acc.find("storage").map(&BTreeMap::from_json); - let code = acc.find("code").map(&Bytes::from_json); - if balance.is_some() || nonce.is_some() || storage.is_some() || code.is_some() { - state.insert(address_from_hex(address), PodAccount{ - balance: balance.unwrap_or_else(U256::zero), - nonce: nonce.unwrap_or_else(U256::zero), - storage: storage.unwrap_or_else(BTreeMap::new), - code: code.unwrap_or_else(Vec::new) - }); - } - state - })) +impl From for PodState { + fn from(s: ethjson::blockchain::State) -> PodState { + let state = s.into_iter().map(|(addr, acc)| (addr.into(), PodAccount::from(acc))).collect(); + PodState(state) } } -impl From for PodState { - fn from(s: ethjson::blockchain::State) -> PodState { - PodState(s.0.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| { - acc.insert(key.into(), PodAccount::from(value)); - acc - })) +impl From for PodState { + fn from(s: ethjson::spec::State) -> PodState { + let state: BTreeMap<_,_> = s.into_iter() + .filter(|pair| !pair.1.is_empty()) + .map(|(addr, acc)| (addr.into(), PodAccount::from(acc))) + .collect(); + PodState(state) } } @@ -85,30 +72,3 @@ impl fmt::Display for PodState { } } -#[cfg(test)] -mod tests { - extern crate rustc_serialize; - - use super::*; - use rustc_serialize::*; - use util::from_json::FromJson; - use util::hash::*; - - #[test] - fn it_serializes_form_json() { - let pod_state = PodState::from_json(&json::Json::from_str( -r#" - { - "0000000000000000000000000000000000000000": { - "balance": "1000", - "nonce": "100", - "storage": {}, - "code" : [] - } - } -"# - ).unwrap()); - - assert!(pod_state.get().get(&ZERO_ADDRESS).is_some()); - } -} diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 95a891198..38bd873b8 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -60,7 +60,7 @@ impl ClientService { panic_handler.forward_from(&net_service); info!("Starting {}", net_service.host_info()); - info!("Configured for {} using {} engine", spec.name, spec.engine_name); + info!("Configured for {} using {:?} engine", spec.name, spec.engine.name()); let client = try!(Client::new(config, spec, db_path, net_service.io().channel())); panic_handler.forward_from(client.deref()); let client_io = Arc::new(ClientIoHandler { diff --git a/ethcore/src/spec/genesis.rs b/ethcore/src/spec/genesis.rs index 686e8f6d1..b6c214fd6 100644 --- a/ethcore/src/spec/genesis.rs +++ b/ethcore/src/spec/genesis.rs @@ -16,26 +16,9 @@ use util::rlp::*; use util::numbers::{Uint, U256}; -use util::hash::{H64, Address, H256}; +use util::hash::{Address, H256}; use ethjson; - -/// Genesis seal type. -pub enum Seal { - /// Classic ethereum seal. - Ethereum { - /// Seal nonce. - nonce: H64, - /// Seal mix hash. - mix_hash: H256, - }, - /// Generic seal. - Generic { - /// Number of seal fields. - fields: usize, - /// Seal rlp. - rlp: Vec, - }, -} +use super::seal::Seal; /// Genesis components. pub struct Genesis { @@ -66,16 +49,7 @@ pub struct Genesis { impl From for Genesis { fn from(g: ethjson::spec::Genesis) -> Self { Genesis { - seal: match (g.nonce, g.mix_hash) { - (Some(nonce), Some(mix_hash)) => Seal::Ethereum { - nonce: nonce.into(), - mix_hash: mix_hash.into(), - }, - _ => Seal::Generic { - fields: g.seal_fields.unwrap(), - rlp: g.seal_rlp.unwrap().into(), - } - }, + seal: From::from(g.seal), difficulty: g.difficulty.into(), author: g.author.into(), timestamp: g.timestamp.into(), diff --git a/ethcore/src/spec/mod.rs b/ethcore/src/spec/mod.rs index b85165d89..356f3b219 100644 --- a/ethcore/src/spec/mod.rs +++ b/ethcore/src/spec/mod.rs @@ -17,6 +17,7 @@ //! Blockchain params. mod genesis; +mod seal; pub mod spec; pub use self::spec::*; diff --git a/ethcore/src/spec/seal.rs b/ethcore/src/spec/seal.rs new file mode 100644 index 000000000..600de701a --- /dev/null +++ b/ethcore/src/spec/seal.rs @@ -0,0 +1,81 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Spec seal. + +use util::rlp::*; +use util::hash::{H64, H256}; +use ethjson; + +/// Classic ethereum seal. +pub struct Ethereum { + /// Seal nonce. + pub nonce: H64, + /// Seal mix hash. + pub mix_hash: H256, +} + +impl Into for Ethereum { + fn into(self) -> Generic { + let mut s = RlpStream::new(); + s.append(&self.mix_hash); + s.append(&self.nonce); + Generic { + fields: 2, + rlp: s.out() + } + } +} + +/// Generic seal. +pub struct Generic { + /// Number of seal fields. + pub fields: usize, + /// Seal rlp. + pub rlp: Vec, +} + +/// Genesis seal type. +pub enum Seal { + /// Classic ethereum seal. + Ethereum(Ethereum), + /// Generic seal. + Generic(Generic), +} + +impl From for Seal { + fn from(s: ethjson::spec::Seal) -> Self { + match s { + ethjson::spec::Seal::Ethereum(eth) => Seal::Ethereum(Ethereum { + nonce: eth.nonce.into(), + mix_hash: eth.mix_hash.into() + }), + ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic { + fields: g.fields, + rlp: g.rlp.into() + }) + } + } +} + +impl Into for Seal { + fn into(self) -> Generic { + match self { + Seal::Generic(generic) => generic, + Seal::Ethereum(eth) => eth.into() + } + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f97d8c8dc..353232ef6 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -21,55 +21,51 @@ use engine::*; use pod_state::*; use null_engine::*; use account_db::*; +use super::genesis::Genesis; +use super::seal::Generic as GenericSeal; use ethereum; -use super::genesis::{Seal as GenesisSeal, Genesis}; +use ethjson; -/// Convert JSON value to equivalent RLP representation. -// TODO: handle container types. -fn json_to_rlp(json: &Json) -> Bytes { - match *json { - Json::Boolean(o) => encode(&(if o {1u64} else {0})).to_vec(), - Json::I64(o) => encode(&(o as u64)).to_vec(), - Json::U64(o) => encode(&o).to_vec(), - Json::String(ref s) if s.len() >= 2 && &s[0..2] == "0x" && U256::from_str(&s[2..]).is_ok() => { - encode(&U256::from_str(&s[2..]).unwrap()).to_vec() - }, - Json::String(ref s) => { - encode(s).to_vec() - }, - _ => panic!() - } +/// Parameters common to all engines. +#[derive(Debug, PartialEq, Clone)] +pub struct CommonParams { + /// Account start nonce. + pub account_start_nonce: U256, + /// Frontier compatibility mode limit. + pub frontier_compatibility_mode_limit: u64, + /// Maximum size of extra data. + pub maximum_extra_data_size: usize, + /// Network id. + pub network_id: U256, + /// Minimum gas limit. + pub min_gas_limit: U256, } -/// Convert JSON to a string->RLP map. -fn json_to_rlp_map(json: &Json) -> HashMap { - json.as_object().unwrap().iter().map(|(k, v)| (k, json_to_rlp(v))).fold(HashMap::new(), |mut acc, kv| { - acc.insert(kv.0.clone(), kv.1); - acc - }) +impl From for CommonParams { + fn from(p: ethjson::spec::Params) -> Self { + CommonParams { + account_start_nonce: p.account_start_nonce.into(), + frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(), + maximum_extra_data_size: p.maximum_extra_data_size.into(), + network_id: p.network_id.into(), + min_gas_limit: p.min_gas_limit.into(), + } + } } /// Parameters for a block chain; includes both those intrinsic to the design of the /// chain and those to be interpreted by the active chain engine. -#[derive(Debug)] pub struct Spec { /// User friendly spec name pub name: String, /// What engine are we using for this? - pub engine_name: String, + pub engine: Box, /// Known nodes on the network in enode format. pub nodes: Vec, - /// Network ID - pub network_id: U256, - /// Parameters concerning operation of the specific engine we're using. - /// Maps the parameter name to an RLP-encoded value. - pub engine_params: HashMap, - - /// Builtin-contracts we would like to see in the chain. - /// (In principle these are just hints for the engine since that has the last word on them.) - pub builtins: BTreeMap, + /// Parameters common to all engines. + pub params: CommonParams, /// The genesis block's parent hash field. pub parent_hash: H256, @@ -101,15 +97,41 @@ pub struct Spec { genesis_state: PodState, } -#[cfg_attr(feature="dev", allow(wrong_self_convention))] // because to_engine(self) should be to_engine(&self) +impl From for Spec { + fn from(s: ethjson::spec::Spec) -> Self { + let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect(); + let g = Genesis::from(s.genesis); + let seal: GenericSeal = g.seal.into(); + let params = CommonParams::from(s.params); + Spec { + name: s.name.into(), + params: params.clone(), + engine: Spec::engine(s.engine, params, builtins), + nodes: s.nodes.unwrap_or_else(Vec::new), + parent_hash: g.parent_hash, + transactions_root: g.transactions_root, + receipts_root: g.receipts_root, + author: g.author, + difficulty: g.difficulty, + gas_limit: g.gas_limit, + gas_used: g.gas_used, + timestamp: g.timestamp, + extra_data: g.extra_data, + seal_fields: seal.fields, + seal_rlp: seal.rlp, + state_root_memo: RwLock::new(g.state_root), + genesis_state: From::from(s.accounts) + } + } +} + impl Spec { - /// Convert this object into a boxed Engine of the right underlying type. - // TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. - pub fn to_engine(self) -> Result, Error> { - match self.engine_name.as_ref() { - "NullEngine" => Ok(NullEngine::new_boxed(self)), - "Ethash" => Ok(ethereum::Ethash::new_boxed(self)), - _ => Err(Error::UnknownEngineName(self.engine_name.clone())) + /// Convert engine spec into a boxed Engine of the right underlying type. + /// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. + fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap) -> Box { + match engine_spec { + ethjson::spec::Engine::Null => Box::new(NullEngine::new(params, builtins)), + ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)) } } @@ -125,7 +147,7 @@ impl Spec { pub fn nodes(&self) -> &Vec { &self.nodes } /// Get the configured Network ID. - pub fn network_id(&self) -> U256 { self.network_id } + pub fn network_id(&self) -> U256 { self.params.network_id } /// Get the header of the genesis block. pub fn genesis_header(&self) -> Header { @@ -168,49 +190,9 @@ impl Spec { ret.out() } - /// Overwrite the genesis components with the given JSON, assuming standard Ethereum test format. - pub fn overwrite_genesis(&mut self, genesis: &Json) { - let (seal_fields, seal_rlp) = { - if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() { - let mut s = RlpStream::new(); - s.append(&H256::from_json(&genesis["mixHash"])); - s.append(&H64::from_json(&genesis["nonce"])); - (2, s.out()) - } else { - // backup algo that will work with sealFields/sealRlp (and without). - ( - u64::from_json(&genesis["sealFields"]) as usize, - Bytes::from_json(&genesis["sealRlp"]) - ) - } - }; - - 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"]); - self.gas_used = U256::from_json(&genesis["gasUsed"]); - self.timestamp = u64::from_json(&genesis["timestamp"]); - self.extra_data = Bytes::from_json(&genesis["extraData"]); - self.seal_fields = seal_fields; - self.seal_rlp = seal_rlp; - self.state_root_memo = RwLock::new(genesis.find("stateRoot").and_then(|_| Some(H256::from_json(&genesis["stateRoot"])))); - } - /// Overwrite the genesis components. pub fn overwrite_genesis_params(&mut self, g: Genesis) { - let (seal_fields, seal_rlp) = match g.seal { - GenesisSeal::Generic { fields, rlp } => (fields, rlp), - GenesisSeal::Ethereum { nonce, mix_hash } => { - let mut s = RlpStream::new(); - s.append(&mix_hash); - s.append(&nonce); - (2, s.out()) - } - }; - + let seal: GenericSeal = g.seal.into(); self.parent_hash = g.parent_hash; self.transactions_root = g.transactions_root; self.receipts_root = g.receipts_root; @@ -220,8 +202,8 @@ impl Spec { self.gas_used = g.gas_used; self.timestamp = g.timestamp; self.extra_data = g.extra_data; - self.seal_fields = seal_fields; - self.seal_rlp = seal_rlp; + self.seal_fields = seal.fields; + self.seal_rlp = seal.rlp; self.state_root_memo = RwLock::new(g.state_root); } @@ -235,74 +217,7 @@ impl Spec { pub fn is_state_root_valid(&self) -> bool { self.state_root_memo.read().unwrap().clone().map_or(true, |sr| sr == self.genesis_state.root()) } -} -impl FromJson for Spec { - /// Loads a chain-specification from a json data structure - fn from_json(json: &Json) -> Spec { - // once we commit ourselves to some json parsing library (serde?) - // move it to proper data structure - let mut builtins = BTreeMap::new(); - let mut state = PodState::new(); - - if let Some(&Json::Object(ref accounts)) = json.find("accounts") { - for (address, acc) in accounts.iter() { - let addr = Address::from_str(address).unwrap(); - if let Some(ref builtin_json) = acc.find("builtin") { - if let Some(builtin) = Builtin::from_json(builtin_json) { - builtins.insert(addr.clone(), builtin); - } - } - } - state = xjson!(&json["accounts"]); - } - - let nodes = if let Some(&Json::Array(ref ns)) = json.find("nodes") { - ns.iter().filter_map(|n| if let Json::String(ref s) = *n { Some(s.clone()) } else {None}).collect() - } else { Vec::new() }; - - let genesis = &json["genesis"];//.as_object().expect("No genesis object in JSON"); - - let (seal_fields, seal_rlp) = { - if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() { - let mut s = RlpStream::new(); - s.append(&H256::from_str(&genesis["mixHash"].as_string().expect("mixHash not a string.")[2..]).expect("Invalid mixHash string value")); - s.append(&H64::from_str(&genesis["nonce"].as_string().expect("nonce not a string.")[2..]).expect("Invalid nonce string value")); - (2, s.out()) - } else { - // backup algo that will work with sealFields/sealRlp (and without). - ( - usize::from_str(&genesis["sealFields"].as_string().unwrap_or("0x")[2..]).expect("Invalid sealFields integer data"), - genesis["sealRlp"].as_string().unwrap_or("0x")[2..].from_hex().expect("Invalid sealRlp hex data") - ) - } - }; - - Spec { - name: json.find("name").map_or("unknown", |j| j.as_string().unwrap()).to_owned(), - engine_name: json["engineName"].as_string().unwrap().to_owned(), - engine_params: json_to_rlp_map(&json["params"]), - nodes: nodes, - network_id: U256::from_str(&json["params"]["networkID"].as_string().unwrap()[2..]).unwrap(), - builtins: builtins, - parent_hash: H256::from_str(&genesis["parentHash"].as_string().unwrap()[2..]).unwrap(), - author: Address::from_str(&genesis["author"].as_string().unwrap()[2..]).unwrap(), - difficulty: U256::from_str(&genesis["difficulty"].as_string().unwrap()[2..]).unwrap(), - 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, - seal_rlp: seal_rlp, - state_root_memo: RwLock::new(genesis.find("stateRoot").and_then(|_| genesis["stateRoot"].as_string()).map(|s| H256::from_str(&s[2..]).unwrap())), - } - } -} - -impl Spec { /// Ensure that the given state DB has the trie nodes in for the genesis state. pub fn ensure_db_good(&self, db: &mut HashDB) -> bool { if !db.contains(&self.state_root()) { @@ -321,21 +236,20 @@ impl Spec { } else { false } } - /// Create a new Spec from a JSON UTF-8 data resource `data`. - pub fn from_json_utf8(data: &[u8]) -> Spec { - Self::from_json_str(::std::str::from_utf8(data).unwrap()) - } - - /// Create a new Spec from a JSON string. - pub fn from_json_str(s: &str) -> Spec { - Self::from_json(&Json::from_str(s).expect("Json is invalid")) + /// Loads spec from json file. + pub fn load(reader: &[u8]) -> Self { + From::from(ethjson::spec::Spec::load(reader).expect("invalid json file")) } /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. - pub fn new_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_morden.json")) } + pub fn new_test() -> Spec { + Spec::load(include_bytes!("../../res/null_morden.json")) + } /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. - pub fn new_homestead_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_homestead_morden.json")) } + pub fn new_homestead_test() -> Spec { + Spec::load(include_bytes!("../../res/null_homestead_morden.json")) + } } #[cfg(test)] @@ -353,7 +267,5 @@ mod tests { assert_eq!(test_spec.state_root(), H256::from_str("f3f4696bbf3b3b07775128eb7a3763279a394e382130f27c21e70233e04946a9").unwrap()); let genesis = test_spec.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap()); - - let _ = test_spec.to_engine(); } } diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index fca578a09..e1fb6aad0 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -548,7 +548,7 @@ fn should_not_trace_call_transaction_to_builtin() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -573,7 +573,7 @@ fn should_not_trace_subcall_transaction_to_builtin() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -611,7 +611,7 @@ fn should_not_trace_callcode() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -651,7 +651,7 @@ fn should_not_trace_delegatecall() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); info.number = 0x789b0; - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call); diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 3b51feec6..539a33d10 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -40,8 +40,7 @@ fn returns_state_root_basic() { let client_result = generate_dummy_client(6); let client = client_result.reference(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; + let state_root = test_spec.genesis_header().state_root; assert!(client.state_data(&state_root).is_some()); } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index dc3068560..152ac0ef6 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -52,7 +52,7 @@ impl GuardedTempResult { pub struct TestEngine { factory: Factory, - spec: Spec, + engine: Box, max_depth: usize } @@ -60,18 +60,29 @@ impl TestEngine { pub fn new(max_depth: usize, factory: Factory) -> TestEngine { TestEngine { factory: factory, - spec: ethereum::new_frontier_test(), + engine: ethereum::new_frontier_test().engine, max_depth: max_depth } } } impl Engine for TestEngine { - fn name(&self) -> &str { "TestEngine" } - fn spec(&self) -> &Spec { &self.spec } + fn name(&self) -> &str { + "TestEngine" + } + + fn params(&self) -> &CommonParams { + self.engine.params() + } + + fn builtins(&self) -> &BTreeMap { + self.engine.builtins() + } + fn vm_factory(&self) -> &Factory { &self.factory } + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { let mut schedule = Schedule::new_frontier(); schedule.max_depth = self.max_depth; @@ -136,17 +147,17 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; - let mut rolling_hash = test_engine.spec().genesis_header().hash(); + let test_engine = &test_spec.engine; + let state_root = test_spec.genesis_header().state_root; + let mut rolling_hash = test_spec.genesis_header().hash(); let mut rolling_block_number = 1; let mut rolling_timestamp = 40; for _ in 0..block_number { let mut header = Header::new(); - header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + header.gas_limit = test_engine.params().min_gas_limit; + header.difficulty = U256::from(0x20000); header.timestamp = rolling_timestamp; header.number = rolling_block_number; header.parent_hash = rolling_hash; @@ -171,8 +182,9 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting_number: usize, block_number: usize) { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; + let test_engine = &test_spec.engine; + //let test_engine = test_spec.to_engine().unwrap(); + let state_root = test_spec.genesis_header().state_root; let mut rolling_hash = client.chain_info().best_block_hash; let mut rolling_block_number = starting_number as u64; let mut rolling_timestamp = timestamp_salt + starting_number as u64 * 10; @@ -180,8 +192,8 @@ pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting for _ in 0..block_number { let mut header = Header::new(); - header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + header.gas_limit = test_engine.params().min_gas_limit; + header.difficulty = U256::from(0x20000); header.timestamp = rolling_timestamp; header.number = rolling_block_number; header.parent_hash = rolling_hash; @@ -279,24 +291,23 @@ pub fn get_temp_state_in(path: &Path) -> State { pub fn get_good_dummy_block_seq(count: usize) -> Vec { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - get_good_dummy_block_fork_seq(1, count, &test_engine.spec().genesis_header().hash()) + get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash()) } pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); + let test_engine = &test_spec.engine; let mut rolling_timestamp = start_number as u64 * 10; let mut parent = *parent_hash; let mut r = Vec::new(); for i in start_number .. start_number + count + 1 { let mut block_header = Header::new(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); + block_header.gas_limit = test_engine.params().min_gas_limit; block_header.difficulty = U256::from(i).mul(U256([0, 1, 0, 0])); block_header.timestamp = rolling_timestamp; block_header.number = i as u64; block_header.parent_hash = parent; - block_header.state_root = test_engine.spec().genesis_header().state_root; + block_header.state_root = test_spec.genesis_header().state_root; parent = block_header.hash(); rolling_timestamp = rolling_timestamp + 10; @@ -310,13 +321,13 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h 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()); + let test_engine = &test_spec.engine; + block_header.gas_limit = test_engine.params().min_gas_limit; + block_header.difficulty = U256::from(0x20000); 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; + block_header.parent_hash = test_spec.genesis_header().hash(); + block_header.state_root = test_spec.genesis_header().state_root; create_test_block(&block_header) } @@ -324,12 +335,12 @@ pub fn get_good_dummy_block() -> Bytes { 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()); + let test_engine = &test_spec.engine; + block_header.gas_limit = test_engine.params().min_gas_limit; + block_header.difficulty = U256::from(0x20000); block_header.timestamp = 40; block_header.number = 1; - block_header.parent_hash = test_engine.spec().genesis_header().hash(); + block_header.parent_hash = test_spec.genesis_header().hash(); block_header.state_root = x!(0xbad); create_test_block(&block_header) diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index bd0ce426f..a3cc21032 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -183,7 +183,7 @@ fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> { if header.gas_used > header.gas_limit { return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit), min: None, found: header.gas_used }))); } - let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap()); + let min_gas_limit = engine.params().min_gas_limit; if header.gas_limit < min_gas_limit { return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: header.gas_limit }))); } @@ -336,12 +336,10 @@ mod tests { // Test against morden let mut good = Header::new(); let spec = Spec::new_test(); - let engine = spec.to_engine().unwrap(); + let engine = &spec.engine; - let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap()); - let min_difficulty = decode(engine.spec().engine_params.get("minimumDifficulty").unwrap()); + let min_gas_limit = engine.params().min_gas_limit; good.gas_limit = min_gas_limit; - good.difficulty = min_difficulty; good.timestamp = 40; good.number = 10; diff --git a/json/src/blockchain/blockchain.rs b/json/src/blockchain/blockchain.rs index 98392b983..5d8933a07 100644 --- a/json/src/blockchain/blockchain.rs +++ b/json/src/blockchain/blockchain.rs @@ -21,7 +21,7 @@ use hash::H256; use blockchain::state::State; use blockchain::header::Header; use blockchain::block::Block; -use spec::Genesis; +use spec::{Genesis, Seal, Ethereum}; /// Blockchain deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -54,10 +54,10 @@ impl BlockChain { /// Returns spec compatible genesis struct. pub fn genesis(&self) -> Genesis { Genesis { - nonce: Some(self.genesis_block.nonce.clone()), - mix_hash: Some(self.genesis_block.mix_hash.clone()), - seal_fields: None, - seal_rlp: None, + seal: Seal::Ethereum(Ethereum { + nonce: self.genesis_block.nonce.clone(), + mix_hash: self.genesis_block.mix_hash.clone(), + }), difficulty: self.genesis_block.difficulty, author: self.genesis_block.author.clone(), timestamp: self.genesis_block.timestamp, diff --git a/json/src/blockchain/state.rs b/json/src/blockchain/state.rs index 7779bd861..5aa4b09a3 100644 --- a/json/src/blockchain/state.rs +++ b/json/src/blockchain/state.rs @@ -22,7 +22,7 @@ use blockchain::account::Account; /// Blockchain test state deserializer. #[derive(Debug, PartialEq, Deserialize, Clone)] -pub struct State(pub BTreeMap); +pub struct State(BTreeMap); impl IntoIterator for State { type Item = as IntoIterator>::Item; diff --git a/json/src/spec/account.rs b/json/src/spec/account.rs index 1440b1bdc..6ad142df3 100644 --- a/json/src/spec/account.rs +++ b/json/src/spec/account.rs @@ -22,9 +22,19 @@ use spec::builtin::Builtin; /// Spec account. #[derive(Debug, PartialEq, Deserialize)] pub struct Account { - builtin: Option, - balance: Option, - nonce: Option, + /// Builtin contract. + pub builtin: Option, + /// Balance. + pub balance: Option, + /// Nonce. + pub nonce: Option, +} + +impl Account { + /// Returns true if account does not have nonce and balance. + pub fn is_empty(&self) -> bool { + self.balance.is_none() && self.nonce.is_none() + } } #[cfg(test)] diff --git a/json/src/spec/builtin.rs b/json/src/spec/builtin.rs index 21f8a2ac1..454199883 100644 --- a/json/src/spec/builtin.rs +++ b/json/src/spec/builtin.rs @@ -17,14 +17,16 @@ //! Spec builtin deserialization. /// Linear pricing. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub struct Linear { - base: u64, - word: u64, + /// Base price. + pub base: usize, + /// Price for word. + pub word: usize, } /// Pricing variants. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub enum Pricing { /// Linear pricing. #[serde(rename="linear")] @@ -32,10 +34,12 @@ pub enum Pricing { } /// Spec builtin. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub struct Builtin { - name: String, - pricing: Pricing, + /// Builtin name. + pub name: String, + /// Builtin pricing. + pub pricing: Pricing, } #[cfg(test)] diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs new file mode 100644 index 000000000..6b56d7b71 --- /dev/null +++ b/json/src/spec/engine.rs @@ -0,0 +1,63 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Engine deserialization. + +use serde::{Deserializer, Error}; +use serde::de::Visitor; +use spec::Ethash; + +/// Engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub enum Engine { + /// Null engine. + Null, + /// Ethash engine. + Ethash(Ethash), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::Engine; + + #[test] + fn engine_deserialization() { + let s = r#"{ + "Null": null + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + assert_eq!(Engine::Null, deserialized); + + let s = r#"{ + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }"#; + + let _deserialized: Engine = serde_json::from_str(s).unwrap(); + } +} + diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs new file mode 100644 index 000000000..85f855dde --- /dev/null +++ b/json/src/spec/ethash.rs @@ -0,0 +1,75 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Ethash params deserialization. + +use uint::Uint; +use hash::Address; + +/// Ethash params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct EthashParams { + /// Tie breaking gas. + #[serde(rename="tieBreakingGas")] + pub tie_breaking_gas: bool, + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Minimum difficulty. + #[serde(rename="minimumDifficulty")] + pub minimum_difficulty: Uint, + /// Difficulty bound divisor. + #[serde(rename="difficultyBoundDivisor")] + pub difficulty_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Block reward. + #[serde(rename="blockReward")] + pub block_reward: Uint, + /// Namereg contract address. + pub registrar: Address, +} + +/// Ethash engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Ethash { + /// Ethash params. + pub params: EthashParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::ethash::Ethash; + + #[test] + fn ethash_deserialization() { + let s = r#"{ + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + }"#; + + let _deserialized: Ethash = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/genesis.rs b/json/src/spec/genesis.rs index a2b484397..272e7bcc2 100644 --- a/json/src/spec/genesis.rs +++ b/json/src/spec/genesis.rs @@ -17,27 +17,15 @@ //! Spec genesis deserialization. use uint::Uint; -use hash::{H64, Address, H256}; +use hash::{Address, H256}; use bytes::Bytes; +use spec::Seal; /// Spec genesis. #[derive(Debug, PartialEq, Deserialize)] pub struct Genesis { - // old seal - /// Seal nonce. - pub nonce: Option, - #[serde(rename="mixHash")] - /// Seal mix hash. - pub mix_hash: Option, - - // new seal // TODO: consider moving it to a separate seal structure - #[serde(rename="sealFields")] - /// Number of seal fields. - pub seal_fields: Option, - #[serde(rename="sealRlp")] - /// Seal rlp. - pub seal_rlp: Option, - + /// Seal. + pub seal: Seal, /// Difficulty. pub difficulty: Uint, /// Block author. @@ -77,7 +65,12 @@ mod tests { let s = r#"{ "nonce": "0x0000000000000042", "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 8783563d1..72ea2a42e 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -21,9 +21,17 @@ pub mod builtin; pub mod genesis; pub mod params; pub mod spec; +pub mod seal; +pub mod engine; +pub mod state; +pub mod ethash; pub use self::account::Account; -pub use self::builtin::Builtin; +pub use self::builtin::{Builtin, Pricing, Linear}; pub use self::genesis::Genesis; pub use self::params::Params; pub use self::spec::Spec; +pub use self::seal::{Seal, Ethereum, Generic}; +pub use self::engine::Engine; +pub use self::state::State; +pub use self::ethash::{Ethash, EthashParams}; diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index e55f7fc48..2370368ba 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -17,34 +17,25 @@ //! Spec params deserialization. use uint::Uint; -use hash::Address; /// Spec params. #[derive(Debug, PartialEq, Deserialize)] pub struct Params { + /// Account start nonce. #[serde(rename="accountStartNonce")] - account_start_nonce: Uint, + pub account_start_nonce: Uint, + /// Homestead transition block number. #[serde(rename="frontierCompatibilityModeLimit")] - frontier_compatibility_mode_limit: Uint, + pub frontier_compatibility_mode_limit: Uint, + /// Maximum size of extra data. #[serde(rename="maximumExtraDataSize")] - maximum_extra_data_size: Uint, - #[serde(rename="tieBreakingGas")] - tie_breaking_gas: bool, - #[serde(rename="minGasLimit")] - min_gas_limit: Uint, - #[serde(rename="gasLimitBoundDivisor")] - gas_limit_bound_divisor: Uint, - #[serde(rename="minimumDifficulty")] - minimum_difficulty: Uint, - #[serde(rename="difficultyBoundDivisor")] - difficulty_bound_divisor: Uint, - #[serde(rename="durationLimit")] - duration_limit: Uint, - #[serde(rename="blockReward")] - block_reward: Uint, - registrar: Address, + pub maximum_extra_data_size: Uint, + /// Network id. #[serde(rename="networkID")] - network_id: Uint, + pub network_id: Uint, + /// Minimum gas limit. + #[serde(rename="minGasLimit")] + pub min_gas_limit: Uint, } #[cfg(test)] @@ -55,19 +46,13 @@ mod tests { #[test] fn params_deserialization() { let s = r#"{ - "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, + "networkID" : "0x1", "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", - "networkID" : "0x1" + "accountStartNonce": "0x00" }"#; + let _deserialized: Params = serde_json::from_str(s).unwrap(); // TODO: validate all fields } diff --git a/json/src/spec/seal.rs b/json/src/spec/seal.rs new file mode 100644 index 000000000..a1a53819a --- /dev/null +++ b/json/src/spec/seal.rs @@ -0,0 +1,73 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Spec seal deserialization. + +use hash::{H64, H256}; +use bytes::Bytes; + +/// Ethereum seal. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Ethereum { + /// Seal nonce. + pub nonce: H64, + /// Seal mix hash. + #[serde(rename="mixHash")] + pub mix_hash: H256, +} + +/// Generic seal. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Generic { + /// Number of fields. + pub fields: usize, + /// Their rlp. + pub rlp: Bytes, +} + +/// Seal variants. +#[derive(Debug, PartialEq, Deserialize)] +pub enum Seal { + /// Ethereum seal. + #[serde(rename="ethereum")] + Ethereum(Ethereum), + /// Generic seal. + #[serde(rename="generic")] + Generic(Generic), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::Seal; + + #[test] + fn builtin_deserialization() { + let s = r#"[{ + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + },{ + "generic": { + "fields": 1, + "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + } + }]"#; + let _deserialized: Vec = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/spec/spec.rs b/json/src/spec/spec.rs index 2dd4ac486..ad73ffbf3 100644 --- a/json/src/spec/spec.rs +++ b/json/src/spec/spec.rs @@ -16,21 +16,33 @@ //! Spec deserialization. -use std::collections::BTreeMap; -use hash::Address; -use spec::account::Account; -use spec::params::Params; -use spec::genesis::Genesis; +use std::io::Read; +use serde_json; +use serde_json::Error; +use spec::{Params, Genesis, Engine, State}; /// Spec deserialization. #[derive(Debug, PartialEq, Deserialize)] pub struct Spec { - name: String, - #[serde(rename="engineName")] - engine_name: String, // TODO: consider making it an enum - params: Params, - genesis: Genesis, - accounts: BTreeMap, + /// Spec name. + pub name: String, + /// Engine. + pub engine: Engine, + /// Spec params. + pub params: Params, + /// Genesis header. + pub genesis: Genesis, + /// Genesis state. + pub accounts: State, + /// Boot nodes. + pub nodes: Option>, +} + +impl Spec { + /// Loads test from json. + pub fn load(reader: R) -> Result where R: Read { + serde_json::from_reader(reader) + } } #[cfg(test)] @@ -42,25 +54,34 @@ mod tests { fn spec_deserialization() { let s = r#"{ "name": "Morden", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/json/src/spec/state.rs b/json/src/spec/state.rs new file mode 100644 index 000000000..bde3d6dd8 --- /dev/null +++ b/json/src/spec/state.rs @@ -0,0 +1,44 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Blockchain test state deserializer. + +use std::collections::BTreeMap; +use hash::Address; +use spec::{Account, Builtin}; + +/// Blockchain test state deserializer. +#[derive(Debug, PartialEq, Deserialize)] +pub struct State(BTreeMap); + +impl State { + /// Returns all builtins. + pub fn builtins(&self) -> BTreeMap { + self.0 + .iter() + .filter_map(|ref pair| pair.1.builtin.clone().map(|b| (pair.0.clone(), b.clone()))) + .collect() + } +} + +impl IntoIterator for State { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/json/src/uint.rs b/json/src/uint.rs index 2ebdca910..e77001461 100644 --- a/json/src/uint.rs +++ b/json/src/uint.rs @@ -23,7 +23,7 @@ use util::numbers::{U256, Uint as U}; /// Lenient uint json deserialization for test json files. #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct Uint(U256); +pub struct Uint(pub U256); impl Into for Uint { fn into(self) -> U256 { @@ -37,9 +37,15 @@ impl Into for Uint { } } +impl Into for Uint { + fn into(self) -> usize { + // TODO: clean it after util conversions refactored. + u64::from(self.0) as usize + } +} impl Into for Uint { fn into(self) -> u8 { - >::into(self) as u8 + u64::from(self.0) as u8 } } @@ -55,6 +61,10 @@ struct UintVisitor; impl Visitor for UintVisitor { type Value = Uint; + fn visit_u64(&mut self, value: u64) -> Result where E: Error { + Ok(Uint(U256::from(value))) + } + fn visit_str(&mut self, value: &str) -> Result where E: Error { let value = match value.len() { 0 => U256::from(0), @@ -83,12 +93,13 @@ mod test { #[test] fn uint_deserialization() { - let s = r#"["0xa", "10", "", "0x"]"#; + let s = r#"["0xa", "10", "", "0x", 0]"#; let deserialized: Vec = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, vec![ Uint(U256::from(10)), Uint(U256::from(10)), Uint(U256::from(0)), + Uint(U256::from(0)), Uint(U256::from(0)) ]); } diff --git a/parity/main.rs b/parity/main.rs index d9382e645..8ee9c6f63 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -476,7 +476,7 @@ impl Configuration { "frontier" | "homestead" | "mainnet" => ethereum::new_frontier(), "morden" | "testnet" => ethereum::new_morden(), "olympic" => ethereum::new_olympic(), - f => Spec::from_json_utf8(contents(f).unwrap_or_else(|_| { + f => Spec::load(contents(f).unwrap_or_else(|_| { die!("{}: Couldn't read chain specification file. Sure it exists?", f) }).as_ref()), }