diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json new file mode 100644 index 000000000..ad23b461f --- /dev/null +++ b/ethcore/res/authority_round.json @@ -0,0 +1,42 @@ +{ + "name": "TestAuthorityRound", + "engine": { + "AuthorityRound": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "stepDuration": "1", + "authorities" : [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69" + }, + "genesis": { + "seal": { + "generic": { + "fields": 1, + "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "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 } } } }, + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + } +} diff --git a/ethcore/res/test_authority.json b/ethcore/res/basic_authority.json similarity index 98% rename from ethcore/res/test_authority.json rename to ethcore/res/basic_authority.json index 1ab482863..51276d487 100644 --- a/ethcore/res/test_authority.json +++ b/ethcore/res/basic_authority.json @@ -1,5 +1,5 @@ { - "name": "TestAuthority", + "name": "TestBasicAuthority", "engine": { "BasicAuthority": { "params": { diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bf612852e..e35c27224 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -531,6 +531,7 @@ impl Client { /// Import transactions from the IO queue pub fn import_queued_transactions(&self, transactions: &[Bytes]) -> usize { + trace!(target: "external_tx", "Importing queued"); let _timer = PerfTimer::new("import_queued_transactions"); self.queue_transactions.fetch_sub(transactions.len(), AtomicOrdering::SeqCst); let txs = transactions.iter().filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()).collect(); @@ -538,6 +539,11 @@ impl Client { results.len() } + /// Used by PoA to try sealing on period change. + pub fn update_sealing(&self) { + self.miner.update_sealing(self) + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. @@ -1149,7 +1155,9 @@ impl BlockChainClient for Client { } fn queue_transactions(&self, transactions: Vec) { - if self.queue_transactions.load(AtomicOrdering::Relaxed) > MAX_TX_QUEUE_SIZE { + let queue_size = self.queue_transactions.load(AtomicOrdering::Relaxed); + trace!(target: "external_tx", "Queue size: {}", queue_size); + if queue_size > MAX_TX_QUEUE_SIZE { debug!("Ignoring {} transactions: queue is full", transactions.len()); } else { let len = transactions.len(); diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a12d94ae4..4e0a94345 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -118,6 +118,16 @@ impl TestBlockChainClient { /// Creates new test client with specified extra data for each block pub fn new_with_extra_data(extra_data: Bytes) -> Self { let spec = Spec::new_test(); + TestBlockChainClient::new_with_spec_and_extra(spec, extra_data) + } + + /// Create test client with custom spec. + pub fn new_with_spec(spec: Spec) -> Self { + TestBlockChainClient::new_with_spec_and_extra(spec, Bytes::new()) + } + + /// Create test client with custom spec and extra data. + pub fn new_with_spec_and_extra(spec: Spec, extra_data: Bytes) -> Self { let mut client = TestBlockChainClient { blocks: RwLock::new(HashMap::new()), numbers: RwLock::new(HashMap::new()), diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs new file mode 100644 index 000000000..abc324e68 --- /dev/null +++ b/ethcore/src/engines/authority_round.rs @@ -0,0 +1,394 @@ +// 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 std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::sync::Weak; +use std::time::{UNIX_EPOCH, Duration}; +use util::*; +use ethkey::verify_address; +use rlp::{UntrustedRlp, View, encode, decode}; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use header::Header; +use error::{Error, BlockError}; +use evm::Schedule; +use ethjson; +use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; +use service::ClientIoMessage; +use transaction::SignedTransaction; +use env_info::EnvInfo; +use builtin::Builtin; + +/// `AuthorityRound` params. +#[derive(Debug, PartialEq)] +pub struct AuthorityRoundParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Time to wait before next block or authority switching. + pub step_duration: Duration, + /// Valid authorities. + pub authorities: Vec
, + /// Number of authorities. + pub authority_n: usize, +} + +impl From for AuthorityRoundParams { + fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { + AuthorityRoundParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + step_duration: Duration::from_secs(p.step_duration.into()), + authority_n: p.authorities.len(), + authorities: p.authorities.into_iter().map(Into::into).collect::>(), + } + } +} + +/// Engine using `AuthorityRound` proof-of-work consensus algorithm, suitable for Ethereum +/// mainnet chains in the Olympic, Frontier and Homestead eras. +pub struct AuthorityRound { + params: CommonParams, + our_params: AuthorityRoundParams, + builtins: BTreeMap, + transistion_service: IoService, + message_channel: Mutex>>, + step: AtomicUsize +} + +trait AsMillis { + fn as_millis(&self) -> u64; +} + +impl AsMillis for Duration { + fn as_millis(&self) -> u64 { + self.as_secs()*1_000 + (self.subsec_nanos()/1_000_000) as u64 + } +} + +impl AuthorityRound { + /// Create a new instance of AuthorityRound engine. + pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap) -> Arc { + let initial_step = (unix_now().as_secs() / our_params.step_duration.as_secs()) as usize; + let engine = Arc::new( + AuthorityRound { + params: params, + our_params: our_params, + builtins: builtins, + transistion_service: IoService::::start().expect("Error creating engine timeout service"), + message_channel: Mutex::new(None), + step: AtomicUsize::new(initial_step), + }); + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; + engine.transistion_service.register_handler(Arc::new(handler)).expect("Error registering engine timeout service"); + engine + } + + fn step(&self) -> usize { + self.step.load(AtomicOrdering::SeqCst) + } + + fn remaining_step_duration(&self) -> Duration { + self.our_params.step_duration * (self.step() as u32 + 1) - unix_now() + } + + fn step_proposer(&self, step: usize) -> &Address { + let ref p = self.our_params; + p.authorities.get(step%p.authority_n).unwrap() + } + + fn is_step_proposer(&self, step: usize, address: &Address) -> bool { + self.step_proposer(step) == address + } +} + +fn unix_now() -> Duration { + UNIX_EPOCH.elapsed().expect("Valid time has to be set in your system.") +} + +struct TransitionHandler { + engine: Weak, +} + +#[derive(Clone)] +struct BlockArrived; + +const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; + +impl IoHandler for TransitionHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + engine.step.fetch_add(1, AtomicOrdering::SeqCst); + if let Some(ref channel) = *engine.message_channel.try_lock().expect("Could not acquire message channel work.") { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), + Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), + } + } + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()).expect("Failed to restart consensus step timer.") + } + } + } +} + +impl Engine for AuthorityRound { + fn name(&self) -> &str { "AuthorityRound" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Two fields - consensus step and the corresponding proposer signature. + 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 { hash_map!["signature".to_owned() => "TODO".to_owned()] } + + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + Schedule::new_homestead() + } + + fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { + header.set_difficulty(parent.difficulty().clone()); + header.set_gas_limit({ + let gas_limit = parent.gas_limit().clone(); + 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 - 1.into()) + } else { + max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) + } + }); + } + + /// 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) {} + + fn is_sealer(&self, author: &Address) -> Option { + let ref p = self.our_params; + Some(p.authorities.contains(author)) + } + + /// 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> { + let header = block.header(); + let step = self.step(); + if self.is_step_proposer(step, header.author()) { + if let Some(ap) = accounts { + // Account should be permanently unlocked, otherwise sealing will fail. + if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { + return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + } else { + trace!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); + } + } else { + trace!(target: "poa", "generate_seal: FAIL: accounts not provided"); + } + } + None + } + + /// Check the number of seal fields. + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + if header.seal().len() != self.seal_fields() { + trace!(target: "poa", "verify_block_basic: wrong number of seal fields"); + Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: header.seal().len() } + ))) + } else { + Ok(()) + } + } + + /// Check if the signature belongs to the correct proposer. + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let step = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); + if step <= self.step() { + let sig = try!(UntrustedRlp::new(&header.seal()[1]).as_val::()); + let ok_sig = try!(verify_address(self.step_proposer(step), &sig.into(), &header.bare_hash())); + if ok_sig { + Ok(()) + } else { + trace!(target: "poa", "verify_block_unordered: invalid seal signature"); + try!(Err(BlockError::InvalidSeal)) + } + } else { + trace!(target: "poa", "verify_block_unordered: block from the future"); + try!(Err(BlockError::InvalidSeal)) + } + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // Don't 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 if parent is from a previous step. + let parent_step = try!(UntrustedRlp::new(&parent.seal()[0]).as_val::()); + let step = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); + if step <= parent_step { + try!(Err(BlockError::DoubleVote(header.author().clone()))); + } + + // 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().clone() - parent.gas_limit().clone() / gas_limit_divisor; + let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / 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().clone() }))); + } + Ok(()) + } + + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> 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 + } + + fn register_message_channel(&self, message_channel: IoChannel) { + let mut guard = self.message_channel.try_lock().unwrap(); + *guard = Some(message_channel); + } +} + +impl Header { + /// Get the none field of the header. + pub fn signature(&self) -> H520 { + decode(&self.seal()[0]) + } +} + +#[cfg(test)] +mod tests { + use common::*; + use rlp::encode; + use block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + use std::thread::sleep; + use std::time::{Duration, UNIX_EPOCH}; + + #[test] + fn has_valid_metadata() { + let engine = Spec::new_test_round().engine; + assert!(!engine.name().is_empty()); + assert!(engine.version().major >= 1); + } + + #[test] + fn can_return_schedule() { + let engine = Spec::new_test_round().engine; + let schedule = engine.schedule(&EnvInfo { + number: 10000000, + author: 0.into(), + timestamp: 0, + difficulty: 0.into(), + last_hashes: Arc::new(vec![]), + gas_used: 0.into(), + gas_limit: 0.into(), + }); + + assert!(schedule.stack_limit > 0); + } + + #[test] + fn verification_fails_on_short_seal() { + let engine = Spec::new_test_round().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 = Spec::new_test_round().engine; + let mut header: Header = Header::default(); + header.set_seal(vec![encode(&H520::default()).to_vec()]); + + let verify_result = engine.verify_block_unordered(&header, None); + assert!(verify_result.is_err()); + } + + #[test] + fn can_generate_seal() { + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr, "1".into()).unwrap(); + + let spec = Spec::new_test_round(); + let engine = &*spec.engine; + let genesis_header = spec.genesis_header(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = b.close_and_lock(); + let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + assert!(b.try_seal(engine, seal).is_ok()); + } + + #[test] + fn proposer_switching() { + let mut header: Header = Header::default(); + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("0".sha3(), "0").unwrap(); + + header.set_author(addr); + + let signature = tap.sign_with_password(addr, "0".into(), header.bare_hash()).unwrap(); + let timestamp = UNIX_EPOCH.elapsed().unwrap().as_secs(); + let step = timestamp + timestamp % 2 + 1; + header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + + let engine = Spec::new_test_round().engine; + + // Too early. + assert!(engine.verify_block_seal(&header).is_err()); + + sleep(Duration::from_millis(2000)); + + // Right step. + assert!(engine.verify_block_seal(&header).is_ok()); + } +} diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 815d2b43a..8656fa944 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -181,13 +181,6 @@ impl Engine for BasicAuthority { } } -impl Header { - /// Get the none field of the header. - pub fn signature(&self) -> H520 { - ::rlp::decode(&self.seal()[0]) - } -} - #[cfg(test)] mod tests { use util::*; @@ -201,7 +194,7 @@ mod tests { /// Create a new test chain spec with `BasicAuthority` consensus engine. fn new_test_authority() -> Spec { - let bytes: &[u8] = include_bytes!("../../res/test_authority.json"); + let bytes: &[u8] = include_bytes!("../../res/basic_authority.json"); Spec::load(bytes).expect("invalid chain spec") } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 250529dad..b8797305b 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -19,10 +19,12 @@ mod null_engine; mod instant_seal; mod basic_authority; +mod authority_round; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; +pub use self::authority_round::AuthorityRound; use util::*; use account_provider::AccountProvider; @@ -32,6 +34,8 @@ use env_info::EnvInfo; use error::Error; use spec::CommonParams; use evm::Schedule; +use io::IoChannel; +use service::ClientIoMessage; use header::Header; use transaction::SignedTransaction; @@ -137,5 +141,7 @@ pub trait Engine : Sync + Send { self.builtins().get(a).expect("attempted to execute nonexistent builtin").execute(input, output); } + /// Add a channel for communication with Client which can be used for sealing. + fn register_message_channel(&self, _message_channel: IoChannel) {} // TODO: sealing stuff - though might want to leave this for later. } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 04a0920fa..98ec42466 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -164,6 +164,8 @@ pub enum BlockError { UnknownParent(H256), /// Uncle parent given is unknown. UnknownUncleParent(H256), + /// The same author issued different votes at the same step. + DoubleVote(H160) } impl fmt::Display for BlockError { @@ -197,6 +199,7 @@ impl fmt::Display for BlockError { RidiculousNumber(ref oob) => format!("Implausible block number. {}", oob), UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), + DoubleVote(ref address) => format!("Author {} issued to many blocks.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index c7f40418c..cb3ca29e1 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -155,8 +155,9 @@ mod blockchain; mod types; mod factory; -#[cfg(test)] -mod tests; +//#[cfg(test)] +#[allow(missing_docs)] +pub mod tests; #[cfg(test)] #[cfg(feature="json-tests")] mod json_tests; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index d36869a74..23dbe113a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -267,6 +267,11 @@ impl Miner { } } + /// Creates new instance of miner with accounts and with given spec. + pub fn with_spec_and_accounts(spec: &Spec, accounts: Option>) -> Miner { + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, accounts) + } + /// Creates new instance of miner without accounts, but with given spec. pub fn with_spec(spec: &Spec) -> Miner { Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) @@ -429,6 +434,7 @@ impl Miner { let last_request = *self.sealing_block_last_request.lock(); let should_disable_sealing = !self.forced_sealing() && !has_local_transactions + && !self.seals_internally && best_block > last_request && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; @@ -475,6 +481,7 @@ impl Miner { if !block.transactions().is_empty() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_block(sealed.rlp_bytes()).is_ok() { + trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } } @@ -773,7 +780,7 @@ impl MinerService for Miner { chain: &MiningBlockChainClient, transactions: Vec ) -> Vec> { - + trace!(target: "external_tx", "Importing external transactions"); let results = { let mut transaction_queue = self.transaction_queue.lock(); self.add_transactions_to_queue( diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 00ba981b9..36b5e7157 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -48,6 +48,8 @@ pub enum ClientIoMessage { FeedBlockChunk(H256, Bytes), /// Take a snapshot for the block with given number. TakeSnapshot(u64), + /// Trigger sealing update (useful for internal sealing). + UpdateSealing, } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -111,6 +113,8 @@ impl ClientService { }); try!(io_service.register_handler(client_io)); + spec.engine.register_message_channel(io_service.channel()); + let stop_guard = ::devtools::StopGuard::new(); run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share()); @@ -213,8 +217,11 @@ impl IoHandler for ClientIoHandler { if let Err(e) = res { debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e); } - - } + }, + ClientIoMessage::UpdateSealing => { + trace!(target: "authorityround", "message: UpdateSealing"); + self.client.update_sealing() + }, _ => {} // ignore other messages } } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 46e99c12e..26318b673 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -18,7 +18,7 @@ use util::*; use builtin::Builtin; -use engines::{Engine, NullEngine, InstantSeal, BasicAuthority}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound}; use pod_state::*; use account_db::*; use header::{BlockNumber, Header}; @@ -144,6 +144,7 @@ impl Spec { ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), + ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins), } } @@ -280,6 +281,12 @@ impl Spec { pub fn new_test_instant() -> Self { Spec::load(include_bytes!("../../res/instant_seal.json") as &[u8]).expect("instant_seal.json is invalid") } + + /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). + /// Accounts with secrets "1".sha3() and "2".sha3() are the authorities. + pub fn new_test_round() -> Self { + Spec::load(include_bytes!("../../res/authority_round.json") as &[u8]).expect("authority_round.json is invalid") + } } #[cfg(test)] diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 7b952b30c..f19874341 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -23,7 +23,6 @@ use state_db::StateDB; use block::{OpenBlock, Drain}; use blockchain::{BlockChain, Config as BlockChainConfig}; use builtin::Builtin; -use state::*; use evm::Schedule; use engines::Engine; use env_info::EnvInfo; @@ -338,6 +337,7 @@ pub fn get_temp_state_db() -> GuardedTempResult { } } +#[cfg(test)] pub fn get_temp_state() -> GuardedTempResult { let temp = RandomTempPath::new(); let journal_db = get_temp_state_db_in(temp.as_path()); @@ -354,6 +354,7 @@ pub fn get_temp_state_db_in(path: &Path) -> StateDB { StateDB::new(journal_db, 5 * 1024 * 1024) } +#[cfg(test)] pub fn get_temp_state_in(path: &Path) -> State { let journal_db = get_temp_state_db_in(path); State::new(journal_db, U256::from(0), Default::default()) diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index 4157e486d..86266c1d0 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . pub mod helpers; +#[cfg(test)] mod client; #[cfg(feature="ipc")] mod rpc; diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs new file mode 100644 index 000000000..3d73ef1ef --- /dev/null +++ b/json/src/spec/authority_round.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 . + +//! Authority params deserialization. + +use uint::Uint; +use hash::Address; + +/// Authority params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct AuthorityRoundParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="stepDuration")] + pub step_duration: Uint, + /// Valid authorities + pub authorities: Vec
, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct AuthorityRound { + /// Ethash params. + pub params: AuthorityRoundParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::authority_round::AuthorityRound; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "stepDuration": "0x02", + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 5c8f88758..850eb2502 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -18,6 +18,7 @@ use spec::Ethash; use spec::BasicAuthority; +use spec::AuthorityRound; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -30,6 +31,8 @@ pub enum Engine { Ethash(Ethash), /// BasicAuthority engine. BasicAuthority(BasicAuthority), + /// AuthorityRound engine. + AuthorityRound(AuthorityRound), } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index f6c856b13..f9de30170 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -26,6 +26,7 @@ pub mod engine; pub mod state; pub mod ethash; pub mod basic_authority; +pub mod authority_round; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -37,3 +38,4 @@ pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; +pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; diff --git a/util/io/src/service.rs b/util/io/src/service.rs index da2e653ec..cd9d0bc50 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -56,6 +56,7 @@ pub enum IoMessage where Message: Send + Clone + Sized { handler_id: HandlerId, token: TimerToken, delay: u64, + once: bool, }, RemoveTimer { handler_id: HandlerId, @@ -92,12 +93,24 @@ impl IoContext where Message: Send + Clone + Sync + 'static { } } - /// Register a new IO timer. 'IoHandler::timeout' will be called with the token. + /// Register a new recurring IO timer. 'IoHandler::timeout' will be called with the token. pub fn register_timer(&self, token: TimerToken, ms: u64) -> Result<(), IoError> { try!(self.channel.send_io(IoMessage::AddTimer { token: token, delay: ms, handler_id: self.handler, + once: false, + })); + Ok(()) + } + + /// Register a new IO timer once. 'IoHandler::timeout' will be called with the token. + pub fn register_timer_once(&self, token: TimerToken, ms: u64) -> Result<(), IoError> { + try!(self.channel.send_io(IoMessage::AddTimer { + token: token, + delay: ms, + handler_id: self.handler, + once: true, })); Ok(()) } @@ -163,6 +176,7 @@ impl IoContext where Message: Send + Clone + Sync + 'static { struct UserTimer { delay: u64, timeout: Timeout, + once: bool, } /// Root IO handler. Manages user handlers, messages and IO timers. @@ -235,8 +249,14 @@ impl Handler for IoManager where Message: Send + Clone + Sync let handler_index = token.0 / TOKENS_PER_HANDLER; let token_id = token.0 % TOKENS_PER_HANDLER; if let Some(handler) = self.handlers.read().get(handler_index) { - if let Some(timer) = self.timers.read().get(&token.0) { - event_loop.timeout(token, Duration::from_millis(timer.delay)).expect("Error re-registering user timer"); + let maybe_timer = self.timers.read().get(&token.0).cloned(); + if let Some(timer) = maybe_timer { + if timer.once { + self.timers.write().remove(&token_id); + event_loop.clear_timeout(&timer.timeout); + } else { + event_loop.timeout(token, Duration::from_millis(timer.delay)).expect("Error re-registering user timer"); + } self.worker_channel.push(Work { work_type: WorkType::Timeout, token: token_id, handler: handler.clone(), handler_id: handler_index }); self.work_ready.notify_all(); } @@ -264,10 +284,10 @@ impl Handler for IoManager where Message: Send + Clone + Sync event_loop.clear_timeout(&timer.timeout); } }, - IoMessage::AddTimer { handler_id, token, delay } => { + IoMessage::AddTimer { handler_id, token, delay, once } => { let timer_id = token + handler_id * TOKENS_PER_HANDLER; let timeout = event_loop.timeout(Token(timer_id), Duration::from_millis(delay)).expect("Error registering user timer"); - self.timers.write().insert(timer_id, UserTimer { delay: delay, timeout: timeout }); + self.timers.write().insert(timer_id, UserTimer { delay: delay, timeout: timeout, once: once }); }, IoMessage::RemoveTimer { handler_id, token } => { let timer_id = token + handler_id * TOKENS_PER_HANDLER;