diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index a8a26b1c3..a57ad8ed8 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit": "0x118c30" } } }, "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x1" diff --git a/ethcore/res/ethereum/frontier_like_test.json b/ethcore/res/ethereum/frontier_like_test.json index fec7c4371..ca31927e7 100644 --- a/ethcore/res/ethereum/frontier_like_test.json +++ b/ethcore/res/ethereum/frontier_like_test.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit": "0x118c30" } } }, "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x1" diff --git a/ethcore/res/ethereum/frontier_test.json b/ethcore/res/ethereum/frontier_test.json index 4f0284d14..3964d33ad 100644 --- a/ethcore/res/ethereum/frontier_test.json +++ b/ethcore/res/ethereum/frontier_test.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit": "0xffffffffffffffff" } } }, "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x1" diff --git a/ethcore/res/ethereum/homestead_test.json b/ethcore/res/ethereum/homestead_test.json index 962592404..a0e3df275 100644 --- a/ethcore/res/ethereum/homestead_test.json +++ b/ethcore/res/ethereum/homestead_test.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit": 0 } } }, "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": 0, "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x1" diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 0723e2e9d..1d1dec0b7 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar": "" + "registrar": "", + "frontierCompatibilityModeLimit": "0x789b0" } } }, "params": { "accountStartNonce": "0x0100000", - "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x2" diff --git a/ethcore/res/ethereum/olympic.json b/ethcore/res/ethereum/olympic.json index 8ee9f1d66..1e0c210b5 100644 --- a/ethcore/res/ethereum/olympic.json +++ b/ethcore/res/ethereum/olympic.json @@ -3,19 +3,18 @@ "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x08", "blockReward": "0x14D1120D7B160000", - "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050" + "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050", + "frontierCompatibilityModeLimit": "0xffffffffffffffff" } } }, "params": { "accountStartNonce": "0x00", - "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x0400", "minGasLimit": "125000", "networkID" : "0x0" diff --git a/ethcore/res/null_morden.json b/ethcore/res/null_morden.json index 48631a7ce..c820579d0 100644 --- a/ethcore/res/null_morden.json +++ b/ethcore/res/null_morden.json @@ -5,7 +5,6 @@ }, "params": { "accountStartNonce": "0x0100000", - "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x2" diff --git a/ethcore/res/null_homestead_morden.json b/ethcore/res/test_authority.json similarity index 74% rename from ethcore/res/null_homestead_morden.json rename to ethcore/res/test_authority.json index 145540853..1ab482863 100644 --- a/ethcore/res/null_homestead_morden.json +++ b/ethcore/res/test_authority.json @@ -1,20 +1,25 @@ { - "name": "Morden", + "name": "TestAuthority", "engine": { - "Null": null + "BasicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + } + } }, "params": { "accountStartNonce": "0x0100000", - "frontierCompatibilityModeLimit": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "networkID" : "0x2" + "networkID" : "0x69" }, "genesis": { "seal": { - "ethereum": { - "nonce": "0x00006d6f7264656e", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + "generic": { + "fields": 1, + "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" } }, "difficulty": "0x20000", @@ -29,6 +34,6 @@ "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, - "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } } } diff --git a/ethcore/src/basic_authority.rs b/ethcore/src/basic_authority.rs new file mode 100644 index 000000000..af0bcb207 --- /dev/null +++ b/ethcore/src/basic_authority.rs @@ -0,0 +1,297 @@ +// 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 . + +//! A blockchain engine that supports a basic, non-BFT proof-of-authority. + +use common::*; +use util::keys::store::AccountProvider; +use block::*; +use spec::{CommonParams, Spec}; +use engine::*; +use evm::{Schedule, Factory}; +use ethjson; + +/// BasicAuthority params. +#[derive(Debug, PartialEq)] +pub struct BasicAuthorityParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// Valid signatories. + pub authorities: HashSet
, +} + +impl From for BasicAuthorityParams { + fn from(p: ethjson::spec::BasicAuthorityParams) -> Self { + BasicAuthorityParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + authorities: p.authorities.into_iter().map(Into::into).collect::>(), + } + } +} + +/// Engine using BasicAuthority proof-of-work consensus algorithm, suitable for Ethereum +/// mainnet chains in the Olympic, Frontier and Homestead eras. +pub struct BasicAuthority { + params: CommonParams, + our_params: BasicAuthorityParams, + builtins: BTreeMap, + factory: Factory, +} + +impl BasicAuthority { + /// Create a new instance of BasicAuthority engine + pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap) -> Self { + BasicAuthority { + params: params, + our_params: our_params, + builtins: builtins, + factory: Factory::default(), + } + } +} + +impl Engine for BasicAuthority { + fn name(&self) -> &str { "BasicAuthority" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + // One field - the signature + fn seal_fields(&self) -> usize { 1 } + + 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 { hash_map!["signature".to_owned() => "TODO".to_owned()] } + + fn vm_factory(&self) -> &Factory { &self.factory } + + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + Schedule::new_homestead() + } + + fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256) { + header.difficulty = parent.difficulty; + header.gas_limit = { + let gas_limit = parent.gas_limit; + let bound_divisor = self.our_params.gas_limit_bound_divisor; + if gas_limit < gas_floor_target { + min(gas_floor_target, gas_limit + gas_limit / bound_divisor - x!(1)) + } else { + max(gas_floor_target, gas_limit - gas_limit / bound_divisor + x!(1)) + } + }; + header.note_dirty(); +// info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number, header.difficulty, header.gas_limit); + } + + /// 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) {} + + /// Attempt to seal the block internally. + /// + /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will + /// be returned. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + if let Some(ap) = accounts { + // check to see if author is contained in self.our_params.authorities + if self.our_params.authorities.contains(block.header().author()) { + if let Ok(secret) = ap.account_secret(block.header().author()) { + return Some(block.header().author_seal(&secret)); + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + } + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: block author {} isn't one of the authorized accounts {:?}", block.header().author(), self.our_params.authorities); + } + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + } + None + } + + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // check the seal fields. + // TODO: pull this out into common code. + if header.seal.len() != self.seal_fields() { + return Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: header.seal.len() } + ))); + } + Ok(()) + } + + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // check the signature is legit. + let sig = try!(UntrustedRlp::new(&header.seal[0]).as_val::()); + let signer = Address::from(try!(ec::recover(&sig, &header.bare_hash())).sha3()); + if !self.our_params.authorities.contains(&signer) { + return try!(Err(BlockError::InvalidSeal)); + } + Ok(()) + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // we should not calculate difficulty for genesis blocks + if header.number() == 0 { + return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); + } + + // Check difficulty is correct given the two timestamps. + if header.difficulty() != parent.difficulty() { + return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() }))) + } + let gas_limit_divisor = self.our_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 { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit }))); + } + Ok(()) + } + + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> { + try!(t.check_low_s()); + Ok(()) + } + + fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { + t.sender().map(|_|()) // Perform EC recovery and cache sender + } +} + +impl Header { + /// Get the none field of the header. + pub fn signature(&self) -> H520 { + decode(&self.seal()[0]) + } + + /// Generate a seal for the block with the given `secret`. + pub fn author_seal(&self, secret: &Secret) -> Vec { + vec![encode(&ec::sign(secret, &self.bare_hash()).unwrap_or(Signature::new())).to_vec()] + } + + /// Set the nonce and mix hash fields of the header. + pub fn sign(&mut self, secret: &Secret) { + self.seal = self.author_seal(secret); + } +} + +/// Create a new test chain spec with BasicAuthority consensus engine. +pub fn new_test_authority() -> Spec { Spec::load(include_bytes!("../res/test_authority.json")) } + +#[cfg(test)] +mod tests { + use super::*; + use common::*; + use block::*; + use engine::*; + use tests::helpers::*; + use util::keys::{TestAccountProvider, TestAccount}; + + #[test] + fn has_valid_metadata() { + let engine = new_test_authority().engine; + assert!(!engine.name().is_empty()); + assert!(engine.version().major >= 1); + } + + #[test] + fn can_return_factory() { + let engine = new_test_authority().engine; + engine.vm_factory(); + } + + #[test] + fn can_return_schedule() { + let engine = new_test_authority().engine; + let schedule = engine.schedule(&EnvInfo { + number: 10000000, + author: x!(0), + timestamp: 0, + difficulty: x!(0), + last_hashes: vec![], + gas_used: x!(0), + gas_limit: x!(0) + }); + + assert!(schedule.stack_limit > 0); + } + + #[test] + fn can_do_seal_verification_fail() { + let engine = new_test_authority().engine; + let header: Header = Header::default(); + + let verify_result = engine.verify_block_basic(&header, None); + + match verify_result { + Err(Error::Block(BlockError::InvalidSealArity(_))) => {}, + Err(_) => { panic!("should be block seal-arity mismatch error (got {:?})", verify_result); }, + _ => { panic!("Should be error, got Ok"); }, + } + } + + #[test] + fn can_do_signature_verification_fail() { + let engine = new_test_authority().engine; + let mut header: Header = Header::default(); + header.set_seal(vec![rlp::encode(&Signature::zero()).to_vec()]); + + let verify_result = engine.verify_block_unordered(&header, None); + + match verify_result { + Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {}, + Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); }, + _ => { panic!("Should be error, got Ok"); }, + } + } + + #[test] + fn can_do_signature_verification() { + let secret = "".sha3(); + let addr = KeyPair::from_secret("".sha3()).unwrap().address(); + + let engine = new_test_authority().engine; + let mut header: Header = Header::default(); + header.set_author(addr); + header.sign(&secret); + + assert!(engine.verify_block_unordered(&header, None).is_ok()); + } + + #[test] + fn can_generate_seal() { + let addr = KeyPair::from_secret("".sha3()).unwrap().address(); + let accounts = hash_map![addr => TestAccount{unlocked: true, password: Default::default(), secret: "".sha3()}]; + let tap = TestAccountProvider::new(accounts); + + let spec = new_test_authority(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); + let mut db_result = get_temp_journal_db(); + let mut db = db_result.take(); + 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, addr.clone(), x!(3141562), vec![]); + let b = b.close_and_lock(); + let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + + assert!(b.try_seal(engine.deref(), seal).is_ok()); + } +} diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 782a20f52..17b0e7071 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -89,7 +89,7 @@ pub struct ExecutedBlock { /// A set of references to `ExecutedBlock` fields that are publicly accessible. pub struct BlockRefMut<'a> { /// Block header. - pub header: &'a Header, + pub header: &'a mut Header, /// Block transactions. pub transactions: &'a Vec, /// Block uncles. @@ -133,7 +133,7 @@ impl ExecutedBlock { /// Get a structure containing individual references to all public fields. pub fn fields_mut(&mut self) -> BlockRefMut { BlockRefMut { - header: &self.base.header, + header: &mut self.base.header, transactions: &self.base.transactions, uncles: &self.base.uncles, state: &mut self.state, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 19802a93c..ed5460ffd 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -42,8 +42,7 @@ use env_info::EnvInfo; use executive::{Executive, Executed, TransactOptions, contract_address}; use receipt::LocalizedReceipt; pub use blockchain::CacheSize as BlockChainCacheSize; -use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase, Filter as - TracedbFilter}; +use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase, Filter as TracedbFilter}; use trace; /// General block status diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index ebfb2a791..90dd78015 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -34,9 +34,10 @@ use std::collections::HashSet; use util::bytes::Bytes; use util::hash::{Address, H256, H2048}; use util::numbers::U256; +use util::keys::store::AccountProvider; use blockchain::TreeRoute; use block_queue::BlockQueueInfo; -use block::{ClosedBlock, LockedBlock, SealedBlock}; +use block::{ExecutedBlock, ClosedBlock, LockedBlock, SealedBlock}; use header::{BlockNumber, Header}; use transaction::{LocalizedTransaction, SignedTransaction}; use log_entry::LocalizedLogEntry; @@ -134,6 +135,9 @@ pub trait BlockChainClient : Sync + Send { /// Makes a non-persistent transaction call. fn call(&self, t: &SignedTransaction) -> Result; + /// Attempt to seal the block internally. See `Engine`. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { self.engine().generate_seal(block, accounts) } + /// Executes a function providing it with a reference to an engine. fn engine(&self) -> &Engine; diff --git a/ethcore/src/engine.rs b/ethcore/src/engine.rs index 74b9dff26..344144c6e 100644 --- a/ethcore/src/engine.rs +++ b/ethcore/src/engine.rs @@ -15,10 +15,10 @@ // along with Parity. If not, see . use common::*; +use util::keys::store::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; -use evm::Schedule; -use evm::Factory; +use evm::{Schedule, Factory}; /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// Provides hooks into each of the major parts of block import. @@ -61,6 +61,14 @@ pub trait Engine : Sync + Send { /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} + /// Attempt to seal the block internally. + /// + /// If `Some` is returned, then you get a valid seal. + /// + /// This operation is synchronous and may (quite reasonably) not be available, in which None will + /// be returned. + fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { None } + /// 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(()) } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index d74e225f2..922d72700 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -137,6 +137,8 @@ pub enum BlockError { MismatchedH256SealElement(Mismatch), /// Proof-of-work aspect of seal, which we assume is a 256-bit value, is invalid. InvalidProofOfWork(OutOfBounds), + /// Some low-level aspect of the seal in incorrect. + InvalidSeal, /// Gas limit header field is invalid. InvalidGasLimit(OutOfBounds), /// Receipts trie root header field is invalid. diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 9d4a87eed..11b13c0c1 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -27,8 +27,6 @@ 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. @@ -41,18 +39,20 @@ pub struct EthashParams { pub block_reward: U256, /// Namereg contract address. pub registrar: Address, + /// Homestead transition block number. + pub frontier_compatibility_mode_limit: u64, } 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(), + frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(), } } } @@ -100,9 +100,9 @@ impl Engine for Ethash { } fn schedule(&self, env_info: &EnvInfo) -> Schedule { - trace!(target: "client", "Creating schedule. fCML={}", self.params.frontier_compatibility_mode_limit); + trace!(target: "client", "Creating schedule. fCML={}", self.ethash_params.frontier_compatibility_mode_limit); - if env_info.number < self.params.frontier_compatibility_mode_limit { + if env_info.number < self.ethash_params.frontier_compatibility_mode_limit { Schedule::new_frontier() } else { Schedule::new_homestead() @@ -207,7 +207,7 @@ impl Engine for Ethash { } fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> { - if header.number() >= self.params.frontier_compatibility_mode_limit { + if header.number() >= self.ethash_params.frontier_compatibility_mode_limit { try!(t.check_low_s()); } Ok(()) @@ -229,7 +229,7 @@ impl Ethash { 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 frontier_limit = self.ethash_params.frontier_compatibility_mode_limit; let mut target = if header.number < frontier_limit { if header.timestamp >= parent.timestamp + duration_limit { diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index a7b9125fc..43b4c5099 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -89,6 +89,7 @@ extern crate bloomchain; #[cfg(test)] extern crate ethcore_devtools as devtools; #[cfg(feature = "jit" )] extern crate evmjit; +pub mod basic_authority; pub mod block; pub mod block_queue; pub mod client; diff --git a/ethcore/src/null_engine.rs b/ethcore/src/null_engine.rs index 505ff5bf6..58ae5baa9 100644 --- a/ethcore/src/null_engine.rs +++ b/ethcore/src/null_engine.rs @@ -57,11 +57,7 @@ impl Engine for NullEngine { &self.builtins } - fn schedule(&self, env_info: &EnvInfo) -> Schedule { - if env_info.number < self.params.frontier_compatibility_mode_limit { - Schedule::new_frontier() - } else { - Schedule::new_homestead() - } + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + Schedule::new_homestead() } } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 353232ef6..743f1bd9b 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -24,6 +24,7 @@ use account_db::*; use super::genesis::Genesis; use super::seal::Generic as GenericSeal; use ethereum; +use basic_authority::BasicAuthority; use ethjson; /// Parameters common to all engines. @@ -31,8 +32,6 @@ use ethjson; 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. @@ -45,7 +44,6 @@ 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(), @@ -131,7 +129,8 @@ impl Spec { 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)) + ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), + ethjson::spec::Engine::BasicAuthority(basic_authority) => Box::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), } } @@ -241,15 +240,10 @@ impl Spec { 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. + /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus. 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 { - Spec::load(include_bytes!("../../res/null_homestead_morden.json")) - } } #[cfg(test)] diff --git a/json/src/spec/basic_authority.rs b/json/src/spec/basic_authority.rs new file mode 100644 index 000000000..967fb024a --- /dev/null +++ b/json/src/spec/basic_authority.rs @@ -0,0 +1,59 @@ +// 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 BasicAuthorityParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Valid authorities + pub authorities: Vec
, +} + +/// Ethash engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct BasicAuthority { + /// Ethash params. + pub params: BasicAuthorityParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::basic_authority::BasicAuthority; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: BasicAuthority = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index e5f5dc718..7d81aaa5a 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -19,6 +19,7 @@ use serde::Deserializer; use serde::de::Visitor; use spec::Ethash; +use spec::BasicAuthority; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -27,6 +28,8 @@ pub enum Engine { Null, /// Ethash engine. Ethash(Ethash), + /// BasicAuthority engine. + BasicAuthority(BasicAuthority), } #[cfg(test)] @@ -46,13 +49,13 @@ mod tests { let s = r#"{ "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit" : "0x" } } }"#; diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs index 85f855dde..254ffff9b 100644 --- a/json/src/spec/ethash.rs +++ b/json/src/spec/ethash.rs @@ -22,9 +22,6 @@ 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, @@ -42,6 +39,9 @@ pub struct EthashParams { pub block_reward: Uint, /// Namereg contract address. pub registrar: Address, + /// Homestead transition block number. + #[serde(rename="frontierCompatibilityModeLimit")] + pub frontier_compatibility_mode_limit: Uint, } /// Ethash engine deserialization. @@ -60,13 +60,13 @@ mod tests { fn ethash_deserialization() { let s = r#"{ "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit" : "0x42" } }"#; diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 72ea2a42e..f6c856b13 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -25,6 +25,7 @@ pub mod seal; pub mod engine; pub mod state; pub mod ethash; +pub mod basic_authority; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -35,3 +36,4 @@ pub use self::seal::{Seal, Ethereum, Generic}; pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; +pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 2370368ba..8a953bb89 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -24,9 +24,6 @@ pub struct Params { /// Account start nonce. #[serde(rename="accountStartNonce")] pub account_start_nonce: Uint, - /// Homestead transition block number. - #[serde(rename="frontierCompatibilityModeLimit")] - pub frontier_compatibility_mode_limit: Uint, /// Maximum size of extra data. #[serde(rename="maximumExtraDataSize")] pub maximum_extra_data_size: Uint, diff --git a/json/src/spec/spec.rs b/json/src/spec/spec.rs index ad73ffbf3..fbb206c40 100644 --- a/json/src/spec/spec.rs +++ b/json/src/spec/spec.rs @@ -57,13 +57,13 @@ mod tests { "engine": { "Ethash": { "params": { - "tieBreakingGas": false, "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "frontierCompatibilityModeLimit" : "0x" } } }, diff --git a/miner/src/miner.rs b/miner/src/miner.rs index 701016024..66545c8e1 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -18,6 +18,7 @@ use rayon::prelude::*; use std::sync::atomic::AtomicBool; use util::*; +use util::keys::store::{AccountService, AccountProvider}; use ethcore::views::{BlockView, HeaderView}; use ethcore::client::{BlockChainClient, BlockId}; use ethcore::block::{ClosedBlock, IsBlock}; @@ -38,6 +39,8 @@ pub struct Miner { gas_floor_target: RwLock, author: RwLock
, extra_data: RwLock, + + accounts: RwLock>>, // TODO: this is horrible since AccountService already contains a single RwLock field. refactor. } impl Default for Miner { @@ -51,6 +54,7 @@ impl Default for Miner { gas_floor_target: RwLock::new(U256::zero()), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), + accounts: RwLock::new(None), } } } @@ -67,6 +71,22 @@ impl Miner { gas_floor_target: RwLock::new(U256::zero()), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), + accounts: RwLock::new(None), + }) + } + + /// Creates new instance of miner + pub fn with_accounts(force_sealing: bool, accounts: Arc) -> Arc { + Arc::new(Miner { + transaction_queue: Mutex::new(TransactionQueue::new()), + force_sealing: force_sealing, + sealing_enabled: AtomicBool::new(force_sealing), + sealing_block_last_request: Mutex::new(0), + sealing_work: Mutex::new(UsingQueue::new(5)), + gas_floor_target: RwLock::new(U256::zero()), + author: RwLock::new(Address::default()), + extra_data: RwLock::new(Vec::new()), + accounts: RwLock::new(Some(accounts)), }) } @@ -142,6 +162,30 @@ impl Miner { queue.remove_invalid(&hash, &fetch_account); } if let Some(block) = b { + if !block.transactions().is_empty() { + trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal."); + // block with transactions - see if we can seal immediately. + let a = self.accounts.read().unwrap(); + let s = chain.generate_seal(block.block(), match a.deref() { + &Some(ref x) => Some(x.deref() as &AccountProvider), + &None => None, + }); + if let Some(seal) = s { + trace!(target: "miner", "prepare_sealing: managed internal seal. importing..."); + if let Ok(sealed) = chain.try_seal(block.lock(), seal) { + if let Ok(_) = chain.import_block(sealed.rlp_bytes()) { + trace!(target: "miner", "prepare_sealing: sealed internally and imported. leaving."); + } else { + warn!("prepare_sealing: ERROR: could not import internally sealed block. WTF?"); + } + } else { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + } + return; + } else { + trace!(target: "miner", "prepare_sealing: unable to generate seal internally"); + } + } if sealing_work.peek_last_ref().map_or(true, |pb| pb.block().fields().header.hash() != block.block().fields().header.hash()) { trace!(target: "miner", "Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash()); sealing_work.push(block); diff --git a/parity/main.rs b/parity/main.rs index 185cf3cbd..4fa18bd4d 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -146,7 +146,7 @@ fn execute_client(conf: Configuration) { let client = service.client(); // Miner - let miner = Miner::new(conf.args.flag_force_sealing); + let miner = Miner::with_accounts(conf.args.flag_force_sealing, account_service.clone()); miner.set_author(conf.author()); miner.set_gas_floor_target(conf.gas_floor_target()); miner.set_extra_data(conf.extra_data()); diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 327765d89..5137c5665 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -20,13 +20,14 @@ use std::sync::{Arc, RwLock}; use jsonrpc_core::IoHandler; use util::hash::{Address, H256, FixedHash}; use util::numbers::{Uint, U256}; +use util::keys::{TestAccount, TestAccountProvider}; use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId}; use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethminer::ExternalMiner; use v1::{Eth, EthClient}; -use v1::tests::helpers::{TestAccount, TestAccountProvider, TestSyncProvider, Config, TestMinerService}; +use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); diff --git a/rpc/src/v1/tests/helpers/mod.rs b/rpc/src/v1/tests/helpers/mod.rs index 0e60e179e..9b321af98 100644 --- a/rpc/src/v1/tests/helpers/mod.rs +++ b/rpc/src/v1/tests/helpers/mod.rs @@ -16,10 +16,8 @@ //! Test rpc services. -mod account_provider; mod sync_provider; mod miner_service; -pub use self::account_provider::{TestAccount, TestAccountProvider}; pub use self::sync_provider::{Config, TestSyncProvider}; pub use self::miner_service::{TestMinerService}; diff --git a/rpc/src/v1/tests/personal.rs b/rpc/src/v1/tests/personal.rs index 458669b42..10cac9790 100644 --- a/rpc/src/v1/tests/personal.rs +++ b/rpc/src/v1/tests/personal.rs @@ -16,9 +16,9 @@ use std::sync::Arc; use jsonrpc_core::IoHandler; -use v1::tests::helpers::{TestAccount, TestAccountProvider}; -use v1::{PersonalClient, Personal}; use util::numbers::*; +use util::keys::{TestAccount, TestAccountProvider}; +use v1::{PersonalClient, Personal}; use std::collections::*; fn accounts_provider() -> Arc { @@ -49,7 +49,6 @@ fn accounts() { assert_eq!(io.handle_request(request), Some(response.to_owned())); } - #[test] fn new_account() { let (test_provider, io) = setup(); diff --git a/sync/src/chain.rs b/sync/src/chain.rs index e5c4a7fc5..14809fdc4 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1484,7 +1484,7 @@ mod tests { } fn dummy_sync_with_peer(peer_latest_hash: H256) -> ChainSync { - let mut sync = ChainSync::new(SyncConfig::default(), Miner::new(false)); + let mut sync = ChainSync::new(SyncConfig::default(), Arc::new(Miner::default())); sync.peers.insert(0, PeerInfo { protocol_version: 0, diff --git a/util/src/common.rs b/util/src/common.rs index b2a06c4b9..a4ba41f82 100644 --- a/util/src/common.rs +++ b/util/src/common.rs @@ -24,6 +24,20 @@ pub use vector::*; pub use numbers::*; pub use sha3::*; +#[macro_export] +macro_rules! hash_map { + ( $( $x:expr => $y:expr ),* ) => { + vec![ $( ($x, $y) ),* ].into_iter().collect::>() + } +} + +#[macro_export] +macro_rules! hash_mapx { + ( $( $x:expr => $y:expr ),* ) => { + vec![ $( ( From::from($x), From::from($y) ) ),* ].into_iter().collect::>() + } +} + #[macro_export] macro_rules! map { ( $( $x:expr => $y:expr ),* ) => { diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs index b9c9dcb08..5d718affc 100644 --- a/util/src/keys/mod.rs +++ b/util/src/keys/mod.rs @@ -19,3 +19,6 @@ pub mod directory; pub mod store; mod geth_import; +mod test_account_provider; + +pub use self::test_account_provider::{TestAccount, TestAccountProvider}; diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index ddd22bb6c..a879a4cd2 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -31,6 +31,8 @@ const KEY_LENGTH_AES: u32 = KEY_LENGTH/2; const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize; const KEY_LENGTH_AES_USIZE: usize = KEY_LENGTH_AES as usize; +// TODO: this file needs repotting into several separate files. + /// Encrypted hash-map, each request should contain password pub trait EncryptedHashMap { /// Returns existing value for the key, if any @@ -87,10 +89,12 @@ pub trait AccountProvider : Send + Sync { fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>; /// Creates account fn new_account(&self, pass: &str) -> Result; - /// Returns secret for unlocked account + /// Returns secret for unlocked `account`. fn account_secret(&self, account: &Address) -> Result; - /// Returns secret for unlocked account - fn sign(&self, account: &Address, message: &H256) -> Result; + /// Returns signature when unlocked `account` signs `message`. + fn sign(&self, account: &Address, message: &H256) -> Result { + self.account_secret(account).and_then(|s| crypto::ec::sign(&s, message).map_err(|_| SigningError::InvalidSecret)) + } } /// Thread-safe accounts management diff --git a/rpc/src/v1/tests/helpers/account_provider.rs b/util/src/keys/test_account_provider.rs similarity index 90% rename from rpc/src/v1/tests/helpers/account_provider.rs rename to util/src/keys/test_account_provider.rs index cace69658..4ce28ab67 100644 --- a/rpc/src/v1/tests/helpers/account_provider.rs +++ b/util/src/keys/test_account_provider.rs @@ -19,9 +19,9 @@ use std::sync::RwLock; use std::collections::HashMap; use std::io; -use util::hash::{Address, H256, FixedHash}; -use util::crypto::{Secret, Signature, KeyPair}; -use util::keys::store::{AccountProvider, SigningError, EncryptedHashMapError}; +use hash::{Address, FixedHash}; +use crypto::{Secret, KeyPair}; +use super::store::{AccountProvider, SigningError, EncryptedHashMapError}; /// Account mock. #[derive(Clone)] @@ -98,10 +98,5 @@ impl AccountProvider for TestAccountProvider { .ok_or(SigningError::NoAccount) .map(|acc| acc.secret.clone()) } - - fn sign(&self, _account: &Address, _message: &H256) -> Result { - unimplemented!() - } - }