From 8da38fa98b8c88fee4242e988f405b7657d49d77 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 19 Aug 2016 17:18:30 +0200 Subject: [PATCH 001/382] intro simple seal bft engine --- ethcore/res/bft.json | 39 +++++ ethcore/src/engines/bft.rs | 289 +++++++++++++++++++++++++++++++++++++ ethcore/src/engines/mod.rs | 4 + 3 files changed, 332 insertions(+) create mode 100644 ethcore/res/bft.json create mode 100644 ethcore/src/engines/bft.rs diff --git a/ethcore/res/bft.json b/ethcore/res/bft.json new file mode 100644 index 00000000000..24bd386b275 --- /dev/null +++ b/ethcore/res/bft.json @@ -0,0 +1,39 @@ +{ + "name": "TestBFT", + "engine": { + "BFT": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + } + } + }, + "params": { + "accountStartNonce": "0x0100000", + "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/src/engines/bft.rs b/ethcore/src/engines/bft.rs new file mode 100644 index 00000000000..aae6854d2f2 --- /dev/null +++ b/ethcore/src/engines/bft.rs @@ -0,0 +1,289 @@ +// 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 account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; + +/// `BFT` params. +#[derive(Debug, PartialEq)] +pub struct BFTParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// Validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Precommit step votes. + precommits: HashMap> +} + +impl From for BFTParams { + fn from(p: ethjson::spec::BFTParams) -> Self { + let val = p.validators.into_iter().map(Into::into).collect::>(); + BFTParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + validators: auth, + validator_n: val.len() + } + } +} + +/// Engine using `BFT` consensus algorithm, suitable for EVM chain. +pub struct BFT { + params: CommonParams, + our_params: BFTParams, + builtins: BTreeMap, +} + +impl BFT { + /// Create a new instance of BFT engine + pub fn new(params: CommonParams, our_params: BFTParams, builtins: BTreeMap) -> Self { + BFT { + params: params, + our_params: our_params, + builtins: builtins, + } + } + + fn check_precommit(&self, bare_hash: &H256, signature: &H520) -> result::Result<(), Error> { + let signer = Address::from(try!(ec::recover(&sig, bare_hash)).sha3()); + if !self.our_params.validators.contains(&signer) { + return try!(Err(BlockError::InvalidSeal)); + } + Ok(()) + } + + fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } + + fn signatures_seal(&self, signatures: &HashSet) -> Vec { + signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() + } +} + +impl Engine for BFT { + fn name(&self) -> &str { "BFT" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Possibly signatures of all validators. + fn seal_fields(&self) -> usize { self.our_params.validator_n } + + 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.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 - 1.into()) + } else { + max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) + } + }; + header.note_dirty(); + } + + /// 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 using all available signatures. + /// + /// None is returned if not enough signatures can be collected. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + let hash = block.bare_hash(); + let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let threshold = self.supermajority(); + match (signatures.len(), accounts) { + (threshold-1, Some(ap)) => { + // account should be pernamently unlocked, otherwise signing will fail + if let Ok(signature) = ap.sign(*block.header().author(), hash) { + *signatures.insert(signature); + Some(self.signatures_seal(signatures)); + } else { + trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); + None + } + }, + (0..threshold, _) => None, + (threshold.., _) => Some(block.header().seal), + } + } + + 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> { + let hash = header.bare_hash(); + let threshold = self.supermajority(); + let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + if signatures.len() > threshold { return Ok(()) } + // Count all valid precommits. + for seal_field in header.seal { + let sig = try!(UntrustedRlp::new(seal_field).as_val::()); + if !signatures.contains(sig) || self.check_precommit(hash, sig).is_ok() { + trace!(target: "bft", "verify_block_unordered: new validator vote found"); + *signatures.insert(sig); + if signatures.len() > threshold { return Ok(()) } + } + } + Err(BlockError::InvalidSeal) + } + + 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]) + } +} + +#[cfg(test)] +mod tests { + use common::*; + use block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + + /// Create a new test chain spec with `BFT` consensus engine. + fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/test_authority.json")) } + + #[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_schedule() { + let engine = new_test_authority().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 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_generate_seal() { + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("".sha3(), "").unwrap(); + tap.unlock_account_permanently(addr, "".into()).unwrap(); + + 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()).unwrap(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let vm_factory = Default::default(); + let b = OpenBlock::new(engine, &vm_factory, 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()); + } +} diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index e7738fbaa6c..2f3c0d18907 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -113,6 +113,10 @@ pub trait Engine : Sync + Send { header.note_dirty(); } + /// Handle any potential consensus messages; + /// updating consensus state and potentially issuing a new one. + fn handle_message(&self, sender: Address, message: Bytes) -> Option> { None } + // 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. From 74939a43d6a1f12cbe0c80195af4a6911e5d0dbf Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 21 Aug 2016 15:27:39 +0200 Subject: [PATCH 002/382] fix types and lifetimes --- ethcore/src/engines/bft.rs | 65 +++++++++++++++++++++----------------- ethcore/src/engines/mod.rs | 2 ++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/bft.rs b/ethcore/src/engines/bft.rs index aae6854d2f2..2131800b7fb 100644 --- a/ethcore/src/engines/bft.rs +++ b/ethcore/src/engines/bft.rs @@ -25,7 +25,7 @@ use evm::Schedule; use ethjson; /// `BFT` params. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct BFTParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, @@ -36,7 +36,7 @@ pub struct BFTParams { /// Number of validators. pub validator_n: usize, /// Precommit step votes. - precommits: HashMap> + precommits: RwLock>> } impl From for BFTParams { @@ -45,8 +45,9 @@ impl From for BFTParams { BFTParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), duration_limit: p.duration_limit.into(), - validators: auth, - validator_n: val.len() + validator_n: val.len(), + validators: val, + precommits: RwLock::new(HashMap::new()) } } } @@ -68,17 +69,27 @@ impl BFT { } } - fn check_precommit(&self, bare_hash: &H256, signature: &H520) -> result::Result<(), Error> { - let signer = Address::from(try!(ec::recover(&sig, bare_hash)).sha3()); - if !self.our_params.validators.contains(&signer) { - return try!(Err(BlockError::InvalidSeal)); + fn add_precommit(&self, bare_hash: &H256, signature: &Signature) { + if let Some(mut precommits) = self.our_params.precommits.write().get_mut(bare_hash) { + precommits.insert(signature.clone()); + } else { + let mut new = HashSet::new(); + new.insert(signature.clone()); + assert!(self.our_params.precommits.write().insert(bare_hash.clone(), new).is_none()); + } + } + + fn check_precommit(&self, bare_hash: &H256, signature: &Signature) -> result::Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + match self.our_params.validators.contains(&signer) { + false => try!(Err(BlockError::InvalidSeal)), + true => Ok(()), } - Ok(()) } fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } - fn signatures_seal(&self, signatures: &HashSet) -> Vec { + fn signatures_seal(&self, signatures: &HashSet) -> Vec { signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() } } @@ -121,22 +132,23 @@ impl Engine for BFT { /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - let hash = block.bare_hash(); - let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let hash = block.header().bare_hash(); + let guard = self.our_params.precommits.read(); + let signatures = guard.get(&hash).unwrap_or(return None); let threshold = self.supermajority(); match (signatures.len(), accounts) { - (threshold-1, Some(ap)) => { + (v, Some(ap)) if v == threshold-1 => { // account should be pernamently unlocked, otherwise signing will fail if let Ok(signature) = ap.sign(*block.header().author(), hash) { - *signatures.insert(signature); - Some(self.signatures_seal(signatures)); + self.add_precommit(&hash, &signature.into()); + Some(self.signatures_seal(signatures)) } else { trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); None } }, - (0..threshold, _) => None, - (threshold.., _) => Some(block.header().seal), + (v, _) if v < threshold => None, + _ => Some(block.header().seal.clone()), } } @@ -154,18 +166,19 @@ impl Engine for BFT { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { let hash = header.bare_hash(); let threshold = self.supermajority(); - let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let guard = self.our_params.precommits.read(); + let mut signatures = guard.get(&hash).unwrap_or(try!(Err(BlockError::InvalidSeal))); if signatures.len() > threshold { return Ok(()) } // Count all valid precommits. - for seal_field in header.seal { - let sig = try!(UntrustedRlp::new(seal_field).as_val::()); - if !signatures.contains(sig) || self.check_precommit(hash, sig).is_ok() { + for seal_field in header.seal() { + let sig = try!(UntrustedRlp::new(&seal_field).as_val::()); + if !signatures.contains(&sig) || self.check_precommit(&hash, &sig).is_ok() { trace!(target: "bft", "verify_block_unordered: new validator vote found"); - *signatures.insert(sig); + self.add_precommit(&hash, &sig); if signatures.len() > threshold { return Ok(()) } } } - Err(BlockError::InvalidSeal) + try!(Err(BlockError::InvalidSeal)) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -197,12 +210,6 @@ impl Engine for BFT { } } -impl Header { - /// Get the none field of the header. - pub fn signature(&self) -> H520 { - decode(&self.seal()[0]) - } -} #[cfg(test)] mod tests { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 2f3c0d18907..e52db90fb78 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 bft; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; +pub use self::bft::BFT; use common::*; use account_provider::AccountProvider; From a20a0de48f95fdd884688114e7ed6ee92c6a5786 Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 21 Aug 2016 15:28:40 +0200 Subject: [PATCH 003/382] add spec --- ethcore/src/spec/spec.rs | 3 +- json/src/spec/bft.rs | 59 ++++++++++++++++++++++++++++++++++++++++ json/src/spec/engine.rs | 3 ++ json/src/spec/mod.rs | 2 ++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 json/src/spec/bft.rs diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index a0c32d51abb..c3db5d4f19e 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -17,7 +17,7 @@ //! Parameters for a block chain. use common::*; -use engines::{Engine, NullEngine, InstantSeal, BasicAuthority}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, BFT}; use pod_state::*; use account_db::*; use super::genesis::Genesis; @@ -139,6 +139,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::BFT(bft) => Arc::new(BFT::new(params, From::from(bft.params), builtins)), } } diff --git a/json/src/spec/bft.rs b/json/src/spec/bft.rs new file mode 100644 index 00000000000..a5a34c55063 --- /dev/null +++ b/json/src/spec/bft.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 BFTParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Valid authorities + pub validators: Vec
, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct BFT { + /// Ethash params. + pub params: BFTParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::bft::BFT; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: BFT = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 3813b1756ef..7b32efa5130 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::BFT; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -30,6 +31,8 @@ pub enum Engine { Ethash(Ethash), /// BasicAuthority engine. BasicAuthority(BasicAuthority), + /// Byzantine Fault Tolerant engine. + BFT(BFT) } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index f6c856b1336..c8a6f8bf5bd 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 bft; 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::bft::{BFT, BFTParams}; From 2f5aeda44fb9d2b92cb41e3a668d834819366246 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 13:19:23 +0200 Subject: [PATCH 004/382] reusable voting on hashes --- ethcore/src/engines/mod.rs | 4 +- ethcore/src/engines/signed_vote.rs | 101 +++++++++++++++++++++++++++++ ethcore/src/error.rs | 12 ++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 ethcore/src/engines/signed_vote.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index e52db90fb78..47a12435d84 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -20,13 +20,15 @@ mod null_engine; mod instant_seal; mod basic_authority; mod bft; +mod signed_vote; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::bft::BFT; +pub use self::signed_vote::VoteError; -use common::*; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs new file mode 100644 index 00000000000..694b7cc9bd6 --- /dev/null +++ b/ethcore/src/engines/signed_vote.rs @@ -0,0 +1,101 @@ +// 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 . + +//! Voting on hashes, where each vote has to come from a set of public keys. + +use common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; + +/// Signed voting on hashes. +#[derive(Debug)] +pub struct SignedVote { + /// Voter public keys. + pub voters: HashSet
, + /// Number of voters. + pub voter_n: usize, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>>, + /// Winner hash, set after enough votes are reached. + winner: RwLock> +} + +#[derive(Debug)] +pub enum VoteError { + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(voters: HashSet
, threshold: usize) -> Self { + SignedVote { + voter_n: voters.len(), + voters: voters, + threshold: threshold, + votes: RwLock::new(HashMap::new()), + winner: RwLock::new(None) + } + } + + pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { + if !self.can_vote(&bare_hash, signature).is_ok() { return false; } + let n = if let Some(mut old) = self.votes.write().get_mut(&bare_hash) { + old.insert(signature.clone()); + old.len() + } else { + let mut new = HashSet::new(); + new.insert(signature.clone()); + assert!(self.votes.write().insert(bare_hash.clone(), new).is_none()); + 1 + }; + if self.is_won(n) { + let mut guard = self.winner.write(); + *guard = Some(bare_hash); + } + true + } + + fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + fn is_won(&self, valid_votes: usize) -> bool { + valid_votes > self.threshold + } + + pub fn winner(&self) -> Option { self.winner.read().clone() } +} + +#[cfg(test)] +mod tests { + use common::{HashSet, Address}; + use engines::signed_vote::SignedVote; + #[test] + fn simple_vote() { + let voters: HashSet<_> = vec![Address::default()].into_iter().collect(); + let vote = SignedVote::new(voters, 2); + } +} diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 44930373298..aed7773ae13 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -24,6 +24,7 @@ use client::Error as ClientError; use ipc::binary::{BinaryConvertError, BinaryConvertable}; use types::block_import_error::BlockImportError; use snapshot::Error as SnapshotError; +use engines::VoteError; pub use types::executed::{ExecutionError, CallError}; @@ -238,6 +239,8 @@ pub enum Error { Snappy(::util::snappy::InvalidInput), /// Snapshot error. Snapshot(SnapshotError), + /// Consensus vote error. + Vote(VoteError), } impl fmt::Display for Error { @@ -258,6 +261,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), + Error::Vote(ref err) => f.write_str("Bad vote."), } } } @@ -361,6 +365,14 @@ impl From for Error { } } +impl From for Error { + fn from(err: VoteError) -> Error { + match err { + other => Error::Vote(other), + } + } +} + impl From> for Error where Error: From { fn from(err: Box) -> Error { Error::from(*err) From 89011dcc34e5255efd830f808d4e6bcb5f10b3a9 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 17:33:04 +0200 Subject: [PATCH 005/382] fix locking patterns, add simple test --- ethcore/src/engines/signed_vote.rs | 84 ++++++++++++++++++++---------- ethcore/src/error.rs | 3 +- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index 694b7cc9bd6..b6facca3616 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -16,13 +16,7 @@ //! Voting on hashes, where each vote has to come from a set of public keys. -use common::*; -use account_provider::AccountProvider; -use block::*; -use spec::CommonParams; -use engines::Engine; -use evm::Schedule; -use ethjson; +use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; /// Signed voting on hashes. #[derive(Debug)] @@ -39,16 +33,20 @@ pub struct SignedVote { winner: RwLock> } +/// Voting errors. #[derive(Debug)] pub enum VoteError { + /// Voter is not in the voters set. UnauthorisedVoter } impl SignedVote { /// Create a new instance of BFT engine pub fn new(voters: HashSet
, threshold: usize) -> Self { + let voters_n = voters.len(); + assert!(voters_n > threshold); SignedVote { - voter_n: voters.len(), + voter_n: voters_n, voters: voters, threshold: threshold, votes: RwLock::new(HashMap::new()), @@ -56,19 +54,23 @@ impl SignedVote { } } + /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { if !self.can_vote(&bare_hash, signature).is_ok() { return false; } - let n = if let Some(mut old) = self.votes.write().get_mut(&bare_hash) { - old.insert(signature.clone()); - old.len() - } else { - let mut new = HashSet::new(); - new.insert(signature.clone()); - assert!(self.votes.write().insert(bare_hash.clone(), new).is_none()); - 1 - }; - if self.is_won(n) { - let mut guard = self.winner.write(); + let mut guard = self.votes.try_write().unwrap(); + let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); + if !set.insert(signature.clone()) { return false; } +// let n = if let Some(mut old) = guard.get_mut(&bare_hash) { +// if !old.insert(signature.clone()) { return false; } +// old.len() +// } else { +// let mut new = HashSet::new(); +// new.insert(signature.clone()); +// assert!(guard.insert(bare_hash.clone(), new).is_none()); +// 1 +// }; + if set.len() >= self.threshold { + let mut guard = self.winner.try_write().unwrap(); *guard = Some(bare_hash); } true @@ -82,20 +84,48 @@ impl SignedVote { } } - fn is_won(&self, valid_votes: usize) -> bool { - valid_votes > self.threshold - } - - pub fn winner(&self) -> Option { self.winner.read().clone() } + /// Some winner if voting threshold was reached. + pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } } #[cfg(test)] mod tests { - use common::{HashSet, Address}; + use common::*; use engines::signed_vote::SignedVote; + use account_provider::AccountProvider; + #[test] fn simple_vote() { - let voters: HashSet<_> = vec![Address::default()].into_iter().collect(); - let vote = SignedVote::new(voters, 2); + let tap = AccountProvider::transient_provider(); + let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr1, "1".into()).unwrap(); + + let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); + tap.unlock_account_permanently(addr2, "2".into()).unwrap(); + + let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); + tap.unlock_account_permanently(addr3, "3".into()).unwrap(); + + let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); + let vote = SignedVote::new(voters.into(), 1); + assert!(vote.winner().is_none()); + let header = Header::default(); + let bare_hash = header.bare_hash(); + + // Unapproved voter. + let signature = tap.sign(addr3, bare_hash).unwrap(); + assert!(!vote.vote(bare_hash, &signature.into())); + assert!(vote.winner().is_none()); + // First good vote. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(vote.vote(bare_hash, &signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + // Voting again is ineffective. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(!vote.vote(bare_hash, &signature.into())); + // Second valid vote. + let signature = tap.sign(addr2, bare_hash).unwrap(); + assert!(vote.vote(bare_hash, &signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index aed7773ae13..ccc926ce6ff 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -261,7 +261,8 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Vote(ref err) => f.write_str("Bad vote."), + Error::Vote(ref err) => + f.write_fmt(format_args!("Bad vote: {:?}", err)), } } } From 3515a72fa08b64b4d37de61ff18ca64e0d47ee22 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 20:00:41 +0200 Subject: [PATCH 006/382] proposal vote collector --- ethcore/src/engines/propose_collect.rs | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 ethcore/src/engines/propose_collect.rs diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs new file mode 100644 index 00000000000..84dee0bd797 --- /dev/null +++ b/ethcore/src/engines/propose_collect.rs @@ -0,0 +1,130 @@ +// 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 . + +//! Voting on a hash, where each vote has to come from a set of addresses. + +use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable, AtomicBool}; + +/// Collect votes on a hash. +#[derive(Debug)] +pub struct ProposeCollect { + /// Proposed hash. + pub hash: H256, + /// Allowed voter addresses. + pub voters: HashSet
, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>, + /// Was enough votes reached. + is_won: AtomicBool +} + +/// Voting errors. +#[derive(Debug)] +pub enum VoteError { + /// Voter is not in the voters set. + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { + assert!(voters.len() > threshold); + SignedVote { + hash: hash, + voters: voters, + threshold: threshold, + votes: RwLock::new(HashSet::new()), + is_won: AtomicBool::new(false) + } + } + + /// Vote on hash using the signed hash, true if vote counted. + pub fn vote(&self, signature: &Signature) -> bool { + if self.votes.contains(signature) { return false; } + if !self.can_vote(signature).is_ok() { return false; } + self.votes.try_write().unwrap().insert(signature); + true + } + + fn can_vote(&self, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, self.hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + /// Some winner if voting threshold was reached. + pub fn winner(&self) -> Option { + let threshold_checker = || match self.votes.len() >= threshold { + true => { self.is_won.store(true, Ordering::Relaxed); true }, + false => false, + }; + match self.is_won || threshold_checker() { + true => Some(self.hash), + false => None, + } + } + + /// Get signatures backing given hash. + pub fn votes(&self) -> HashSet { + self.votes.try_read().unwrap().clone() + } +} + +#[cfg(test)] +mod tests { + use common::*; + use engines::propose_collect::ProposeCollect; + use account_provider::AccountProvider; + + #[test] + fn simple_propose_collect() { + let tap = AccountProvider::transient_provider(); + let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr1, "1".into()).unwrap(); + + let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); + tap.unlock_account_permanently(addr2, "2".into()).unwrap(); + + let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); + tap.unlock_account_permanently(addr3, "3".into()).unwrap(); + + let header = Header::default(); + let bare_hash = header.bare_hash(); + let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); + let vote = ProposeCollect::new(bare_hash, voters.into(), 1); + assert!(vote.winner().is_none()); + + // Unapproved voter. + let signature = tap.sign(addr3, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + assert!(vote.winner().is_none()); + // First good vote. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + // Voting again is ineffective. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + // Second valid vote. + let signature = tap.sign(addr2, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + } +} From 3aa862c9c2609b16a5847ae8485627ad8fc5604a Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 12:58:40 +0200 Subject: [PATCH 007/382] add test, start tendermint --- ethcore/src/engines/mod.rs | 8 +- ethcore/src/engines/propose_collect.rs | 25 +-- ethcore/src/engines/signed_vote.rs | 15 +- ethcore/src/engines/tendermint.rs | 284 +++++++++++++++++++++++++ ethcore/src/spec/spec.rs | 4 +- json/src/spec/engine.rs | 4 +- json/src/spec/mod.rs | 4 +- json/src/spec/tendermint.rs | 59 +++++ 8 files changed, 370 insertions(+), 33 deletions(-) create mode 100644 ethcore/src/engines/tendermint.rs create mode 100644 json/src/spec/tendermint.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 47a12435d84..fc45edbc944 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -19,14 +19,16 @@ mod null_engine; mod instant_seal; mod basic_authority; -mod bft; +mod tendermint; mod signed_vote; +mod propose_collect; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; -pub use self::bft::BFT; -pub use self::signed_vote::VoteError; +pub use self::tendermint::Tendermint; +pub use self::signed_vote::{SignedVote, VoteError}; +pub use self::propose_collect::{ProposeCollect}; use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; use account_provider::AccountProvider; diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index 84dee0bd797..f09618c50fa 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -16,7 +16,9 @@ //! Voting on a hash, where each vote has to come from a set of addresses. -use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable, AtomicBool}; +use std::sync::atomic::{AtomicBool, Ordering}; +use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use engines::VoteError; /// Collect votes on a hash. #[derive(Debug)] @@ -33,18 +35,11 @@ pub struct ProposeCollect { is_won: AtomicBool } -/// Voting errors. -#[derive(Debug)] -pub enum VoteError { - /// Voter is not in the voters set. - UnauthorisedVoter -} - -impl SignedVote { +impl ProposeCollect { /// Create a new instance of BFT engine pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { assert!(voters.len() > threshold); - SignedVote { + ProposeCollect { hash: hash, voters: voters, threshold: threshold, @@ -55,14 +50,14 @@ impl SignedVote { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, signature: &Signature) -> bool { - if self.votes.contains(signature) { return false; } + if self.votes.try_read().unwrap().contains(signature) { return false; } if !self.can_vote(signature).is_ok() { return false; } - self.votes.try_write().unwrap().insert(signature); + self.votes.try_write().unwrap().insert(signature.clone()); true } fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, self.hash)).sha3()); + let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(VoteError::UnauthorisedVoter)), true => Ok(()), @@ -71,11 +66,11 @@ impl SignedVote { /// Some winner if voting threshold was reached. pub fn winner(&self) -> Option { - let threshold_checker = || match self.votes.len() >= threshold { + let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { true => { self.is_won.store(true, Ordering::Relaxed); true }, false => false, }; - match self.is_won || threshold_checker() { + match self.is_won.load(Ordering::Relaxed) || threshold_checker() { true => Some(self.hash), false => None, } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index b6facca3616..d3381112a98 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -60,15 +60,7 @@ impl SignedVote { let mut guard = self.votes.try_write().unwrap(); let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); if !set.insert(signature.clone()) { return false; } -// let n = if let Some(mut old) = guard.get_mut(&bare_hash) { -// if !old.insert(signature.clone()) { return false; } -// old.len() -// } else { -// let mut new = HashSet::new(); -// new.insert(signature.clone()); -// assert!(guard.insert(bare_hash.clone(), new).is_none()); -// 1 -// }; + // Set the winner if threshold is reached. if set.len() >= self.threshold { let mut guard = self.winner.try_write().unwrap(); *guard = Some(bare_hash); @@ -86,6 +78,11 @@ impl SignedVote { /// Some winner if voting threshold was reached. pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } + + /// Get signatures backing given hash. + pub fn votes(&self, bare_hash: &H256) -> Option> { + self.votes.try_read().unwrap().get(bare_hash).cloned() + } } #[cfg(test)] diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs new file mode 100644 index 00000000000..cdc0aaf3f77 --- /dev/null +++ b/ethcore/src/engines/tendermint.rs @@ -0,0 +1,284 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::{Engine, ProposeCollect}; +use evm::Schedule; +use ethjson; + +/// `Tendermint` params. +#[derive(Debug)] +pub struct TendermintParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// List of validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Consensus round. + r: u64, + /// Consensus step. + s: Step, + /// Used to swith proposer. + proposer_nonce: usize +} + +#[derive(Debug)] +enum Step { + Propose, + Prevote(ProposeCollect), + Precommit(ProposeCollect), + Commit +} + +impl From for TendermintParams { + fn from(p: ethjson::spec::TendermintParams) -> Self { + let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val_n = val.len(); + TendermintParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + validators: val, + validator_n: val_n, + r: 0, + s: Step::Propose, + proposer_nonce: 0 + } + } +} + +/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. +pub struct Tendermint { + params: CommonParams, + our_params: TendermintParams, + builtins: BTreeMap, +} + +impl Tendermint { + /// Create a new instance of Tendermint engine + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Self { + Tendermint { + params: params, + our_params: our_params, + builtins: builtins, + } + } + + fn proposer(&self) -> Address { + let ref p = self.our_params; + p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() + } +} + +impl Engine for Tendermint { + fn name(&self) -> &str { "Tendermint" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Possibly signatures of all validators. + fn seal_fields(&self) -> usize { self.our_params.validator_n } + + 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.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 - 1.into()) + } else { + max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) + } + }; + header.note_dirty(); + } + + /// 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 using all available signatures. + /// + /// None is returned if not enough signatures can be collected. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + accounts.and_then(|ap| { + let header = block.header(); + if header.author() == &self.proposer() { + ap.sign(*header.author(), header.bare_hash()) + .ok() + .and_then(|signature| Some(vec![encode(&(&*signature as &[u8])).to_vec()])) + } else { + None + } + }) + } + + fn handle_message(&self, sender: Address, message: Bytes) -> Option> { + match message[0] { + 0 => println!("0"), + _ => println!("unknown"), + } + //let sig: Signature = message.into(); + 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> { + 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 + } +} + + +#[cfg(test)] +mod tests { + use common::*; + use block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + + /// Create a new test chain spec with `Tendermint` consensus engine. + fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/bft.json")) } + + #[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_schedule() { + let engine = new_test_authority().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 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_generate_seal() { + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("".sha3(), "").unwrap(); + tap.unlock_account_permanently(addr, "".into()).unwrap(); + + 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()).unwrap(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let vm_factory = Default::default(); + let b = OpenBlock::new(engine, &vm_factory, 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 handle_message() { + false; + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c3db5d4f19e..5f7995abb57 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -17,7 +17,7 @@ //! Parameters for a block chain. use common::*; -use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, BFT}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, Tendermint}; use pod_state::*; use account_db::*; use super::genesis::Genesis; @@ -139,7 +139,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::BFT(bft) => Arc::new(BFT::new(params, From::from(bft.params), builtins)), + ethjson::spec::Engine::Tendermint(tendermint) => Arc::new(Tendermint::new(params, From::from(tendermint.params), builtins)), } } diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 7b32efa5130..7285f65761a 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -18,7 +18,7 @@ use spec::Ethash; use spec::BasicAuthority; -use spec::BFT; +use spec::Tendermint; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -32,7 +32,7 @@ pub enum Engine { /// BasicAuthority engine. BasicAuthority(BasicAuthority), /// Byzantine Fault Tolerant engine. - BFT(BFT) + Tendermint(Tendermint) } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index c8a6f8bf5bd..0ad51e63f33 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -26,7 +26,7 @@ pub mod engine; pub mod state; pub mod ethash; pub mod basic_authority; -pub mod bft; +pub mod tendermint; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -38,4 +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::bft::{BFT, BFTParams}; +pub use self::tendermint::{Tendermint, TendermintParams}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs new file mode 100644 index 00000000000..c3294810c6b --- /dev/null +++ b/json/src/spec/tendermint.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 . + +//! Tendermint params deserialization. + +use uint::Uint; +use hash::Address; + +/// Tendermint params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TendermintParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Valid authorities + pub validators: Vec
, +} + +/// Tendermint engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Tendermint { + /// Ethash params. + pub params: TendermintParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::tendermint::Tendermint; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: Tendermint = serde_json::from_str(s).unwrap(); + } +} From 535c502771437e76db815001c14ea4e266d50d3f Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 15:44:01 +0200 Subject: [PATCH 008/382] delete old test --- ethcore/src/engines/bft.rs | 296 ------------------------------------- json/src/spec/bft.rs | 59 -------- 2 files changed, 355 deletions(-) delete mode 100644 ethcore/src/engines/bft.rs delete mode 100644 json/src/spec/bft.rs diff --git a/ethcore/src/engines/bft.rs b/ethcore/src/engines/bft.rs deleted file mode 100644 index 2131800b7fb..00000000000 --- a/ethcore/src/engines/bft.rs +++ /dev/null @@ -1,296 +0,0 @@ -// 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 account_provider::AccountProvider; -use block::*; -use spec::CommonParams; -use engines::Engine; -use evm::Schedule; -use ethjson; - -/// `BFT` params. -#[derive(Debug)] -pub struct BFTParams { - /// Gas limit divisor. - pub gas_limit_bound_divisor: U256, - /// Block duration. - pub duration_limit: u64, - /// Validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, - /// Precommit step votes. - precommits: RwLock>> -} - -impl From for BFTParams { - fn from(p: ethjson::spec::BFTParams) -> Self { - let val = p.validators.into_iter().map(Into::into).collect::>(); - BFTParams { - gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - duration_limit: p.duration_limit.into(), - validator_n: val.len(), - validators: val, - precommits: RwLock::new(HashMap::new()) - } - } -} - -/// Engine using `BFT` consensus algorithm, suitable for EVM chain. -pub struct BFT { - params: CommonParams, - our_params: BFTParams, - builtins: BTreeMap, -} - -impl BFT { - /// Create a new instance of BFT engine - pub fn new(params: CommonParams, our_params: BFTParams, builtins: BTreeMap) -> Self { - BFT { - params: params, - our_params: our_params, - builtins: builtins, - } - } - - fn add_precommit(&self, bare_hash: &H256, signature: &Signature) { - if let Some(mut precommits) = self.our_params.precommits.write().get_mut(bare_hash) { - precommits.insert(signature.clone()); - } else { - let mut new = HashSet::new(); - new.insert(signature.clone()); - assert!(self.our_params.precommits.write().insert(bare_hash.clone(), new).is_none()); - } - } - - fn check_precommit(&self, bare_hash: &H256, signature: &Signature) -> result::Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); - match self.our_params.validators.contains(&signer) { - false => try!(Err(BlockError::InvalidSeal)), - true => Ok(()), - } - } - - fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } - - fn signatures_seal(&self, signatures: &HashSet) -> Vec { - signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() - } -} - -impl Engine for BFT { - fn name(&self) -> &str { "BFT" } - fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { self.our_params.validator_n } - - 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.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 - 1.into()) - } else { - max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) - } - }; - header.note_dirty(); - } - - /// 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 using all available signatures. - /// - /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - let hash = block.header().bare_hash(); - let guard = self.our_params.precommits.read(); - let signatures = guard.get(&hash).unwrap_or(return None); - let threshold = self.supermajority(); - match (signatures.len(), accounts) { - (v, Some(ap)) if v == threshold-1 => { - // account should be pernamently unlocked, otherwise signing will fail - if let Ok(signature) = ap.sign(*block.header().author(), hash) { - self.add_precommit(&hash, &signature.into()); - Some(self.signatures_seal(signatures)) - } else { - trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); - None - } - }, - (v, _) if v < threshold => None, - _ => Some(block.header().seal.clone()), - } - } - - 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> { - let hash = header.bare_hash(); - let threshold = self.supermajority(); - let guard = self.our_params.precommits.read(); - let mut signatures = guard.get(&hash).unwrap_or(try!(Err(BlockError::InvalidSeal))); - if signatures.len() > threshold { return Ok(()) } - // Count all valid precommits. - for seal_field in header.seal() { - let sig = try!(UntrustedRlp::new(&seal_field).as_val::()); - if !signatures.contains(&sig) || self.check_precommit(&hash, &sig).is_ok() { - trace!(target: "bft", "verify_block_unordered: new validator vote found"); - self.add_precommit(&hash, &sig); - if signatures.len() > threshold { return Ok(()) } - } - } - try!(Err(BlockError::InvalidSeal)) - } - - 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 - } -} - - -#[cfg(test)] -mod tests { - use common::*; - use block::*; - use tests::helpers::*; - use account_provider::AccountProvider; - use spec::Spec; - - /// Create a new test chain spec with `BFT` consensus engine. - fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/test_authority.json")) } - - #[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_schedule() { - let engine = new_test_authority().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 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_generate_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); - - 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()).unwrap(); - let last_hashes = Arc::new(vec![genesis_header.hash()]); - let vm_factory = Default::default(); - let b = OpenBlock::new(engine, &vm_factory, 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()); - } -} diff --git a/json/src/spec/bft.rs b/json/src/spec/bft.rs deleted file mode 100644 index a5a34c55063..00000000000 --- a/json/src/spec/bft.rs +++ /dev/null @@ -1,59 +0,0 @@ -// 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 BFTParams { - /// Gas limit divisor. - #[serde(rename="gasLimitBoundDivisor")] - pub gas_limit_bound_divisor: Uint, - /// Block duration. - #[serde(rename="durationLimit")] - pub duration_limit: Uint, - /// Valid authorities - pub validators: Vec
, -} - -/// Authority engine deserialization. -#[derive(Debug, PartialEq, Deserialize)] -pub struct BFT { - /// Ethash params. - pub params: BFTParams, -} - -#[cfg(test)] -mod tests { - use serde_json; - use spec::bft::BFT; - - #[test] - fn basic_authority_deserialization() { - let s = r#"{ - "params": { - "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", - "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] - } - }"#; - - let _deserialized: BFT = serde_json::from_str(s).unwrap(); - } -} From 207f9d02f25c42b120bad0ea21ad1606a1c3f442 Mon Sep 17 00:00:00 2001 From: arkpar Date: Mon, 15 Aug 2016 14:25:57 +0200 Subject: [PATCH 009/382] Started inf networking --- ethcore/src/client/chain_notify.rs | 4 + ethcore/src/client/client.rs | 4 + ethcore/src/client/test_client.rs | 4 + ethcore/src/client/traits.rs | 3 + sync/src/api.rs | 63 ++++++++-- sync/src/infinity.rs | 190 +++++++++++++++++++++++++++++ sync/src/lib.rs | 1 + 7 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 sync/src/infinity.rs diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 897c8cfacfc..e4638f1524e 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -40,6 +40,10 @@ pub trait ChainNotify : Send + Sync { fn stop(&self) { // does nothing by default } + + /// fires when chain broadcasts a message + fn broadcast(&self, _data: Vec) { + } } impl IpcConfig for ChainNotify { } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index aced57e4c50..bf871e4ad39 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1020,6 +1020,10 @@ impl BlockChainClient for Client { fn pending_transactions(&self) -> Vec { self.miner.pending_transactions() } + + fn queue_infinity_message(&self, _message: Bytes) { + //TODO: handle message here + } } impl MiningBlockChainClient for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 212dead9a9d..8852448ccef 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -554,6 +554,10 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } + fn queue_infinity_message(&self, _packet: Bytes) { + unimplemented!(); + } + fn pending_transactions(&self) -> Vec { self.miner.pending_transactions() } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 271e9578540..da876efa682 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -182,6 +182,9 @@ pub trait BlockChainClient : Sync + Send { /// Queue transactions for importing. fn queue_transactions(&self, transactions: Vec); + /// Queue packet + fn queue_infinity_message(&self, packet: Bytes); + /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/sync/src/api.rs b/sync/src/api.rs index 608d9d521f5..cb1c4722915 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -23,6 +23,7 @@ use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; use chain::{ChainSync, SyncStatus}; +use infinity::{InfinitySync}; use std::net::{SocketAddr, AddrParseError}; use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; @@ -30,6 +31,8 @@ use parking_lot::RwLock; /// Ethereum sync protocol pub const ETH_PROTOCOL: &'static str = "eth"; +/// Infinity protocol +pub const INF_PROTOCOL: &'static str = "inf"; /// Sync configuration #[derive(Debug, Clone)] @@ -65,18 +68,22 @@ pub trait SyncProvider: Send + Sync { pub struct EthSync { /// Network service network: NetworkService, - /// Protocol handler - handler: Arc, + /// Ethereum Protocol handler + eth_handler: Arc, + /// Infinity Protocol handler + inf_handler: Arc, } impl EthSync { /// Creates and register protocol with the network service pub fn new(config: SyncConfig, chain: Arc, network_config: NetworkConfiguration) -> Result, NetworkError> { + let inf_sync = InfinitySync::new(&config, chain.clone()); let chain_sync = ChainSync::new(config, &*chain); let service = try!(NetworkService::new(try!(network_config.into_basic()))); let sync = Arc::new(EthSync{ network: service, - handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain }), + eth_handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain.clone() }), + inf_handler: Arc::new(InfProtocolHandler { sync: RwLock::new(inf_sync), chain: chain }), }); Ok(sync) @@ -88,12 +95,12 @@ impl EthSync { impl SyncProvider for EthSync { /// Get sync status fn status(&self) -> SyncStatus { - self.handler.sync.write().status() + self.eth_handler.sync.write().status() } } struct SyncProtocolHandler { - /// Shared blockchain client. TODO: this should evetually become an IPC endpoint + /// Shared blockchain client. chain: Arc, /// Sync strategy sync: RwLock, @@ -122,6 +129,33 @@ impl NetworkProtocolHandler for SyncProtocolHandler { } } +struct InfProtocolHandler { + /// Shared blockchain client. + chain: Arc, + /// Sync strategy + sync: RwLock, +} + +impl NetworkProtocolHandler for InfProtocolHandler { + fn initialize(&self, _io: &NetworkContext) { + } + + fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + InfinitySync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain), *peer, packet_id, data); + } + + fn connected(&self, io: &NetworkContext, peer: &PeerId) { + self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain), *peer); + } + + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain), *peer); + } + + fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) { + } +} + impl ChainNotify for EthSync { fn new_blocks(&self, imported: Vec, @@ -132,8 +166,8 @@ impl ChainNotify for EthSync { _duration: u64) { self.network.with_context(ETH_PROTOCOL, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); - self.handler.sync.write().chain_new_blocks( + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.eth_handler.sync.write().chain_new_blocks( &mut sync_io, &imported, &invalid, @@ -145,13 +179,22 @@ impl ChainNotify for EthSync { fn start(&self) { self.network.start().unwrap_or_else(|e| warn!("Error starting network: {:?}", e)); - self.network.register_protocol(self.handler.clone(), ETH_PROTOCOL, &[62u8, 63u8]) + self.network.register_protocol(self.eth_handler.clone(), ETH_PROTOCOL, &[62u8, 63u8]) .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); + self.network.register_protocol(self.inf_handler.clone(), INF_PROTOCOL, &[1u8]) + .unwrap_or_else(|e| warn!("Error registering infinity protocol: {:?}", e)); } fn stop(&self) { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } + + fn broadcast(&self, message: Vec) { + self.network.with_context(ETH_PROTOCOL, |context| { + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); + }); + } } impl IpcConfig for ManageNetwork { } @@ -201,8 +244,8 @@ impl ManageNetwork for EthSync { fn stop_network(&self) { self.network.with_context(ETH_PROTOCOL, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); - self.handler.sync.write().abort(&mut sync_io); + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.eth_handler.sync.write().abort(&mut sync_io); }); self.stop(); } diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs new file mode 100644 index 00000000000..23886560e40 --- /dev/null +++ b/sync/src/infinity.rs @@ -0,0 +1,190 @@ +// 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 . + +/// Infinity networking + +use util::*; +use network::*; +use ethcore::client::{BlockChainClient}; +use sync_io::SyncIo; +use super::SyncConfig; + +known_heap_size!(0, PeerInfo); + +type PacketDecodeError = DecoderError; + +const PROTOCOL_VERSION: u8 = 1u8; + +const STATUS_PACKET: u8 = 0x00; +const GENERIC_PACKET: u8 = 0x01; + +/// Syncing status and statistics +#[derive(Clone)] +pub struct NetworkStatus { + pub protocol_version: u8, + /// The underlying p2p network version. + pub network_id: U256, + /// Total number of connected peers + pub num_peers: usize, + /// Total number of active peers + pub num_active_peers: usize, +} + +#[derive(Clone)] +/// Inf peer information +struct PeerInfo { + /// inf protocol version + protocol_version: u32, + /// Peer chain genesis hash + genesis: H256, + /// Peer network id + network_id: U256, +} + +/// Infinity protocol handler. +pub struct InfinitySync { + chain: Arc, + /// All connected peers + peers: HashMap, + /// Network ID + network_id: U256, +} + +impl InfinitySync { + /// Create a new instance of syncing strategy. + pub fn new(config: &SyncConfig, chain: Arc) -> InfinitySync { + let mut sync = InfinitySync { + chain: chain, + peers: HashMap::new(), + network_id: config.network_id, + }; + sync.reset(); + sync + } + + /// @returns Synchonization status + pub fn _status(&self) -> NetworkStatus { + NetworkStatus { + protocol_version: 1, + network_id: self.network_id, + num_peers: self.peers.len(), + num_active_peers: 0, + } + } + + #[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()` + /// Reset sync. Clear all downloaded data but keep the queue + fn reset(&mut self) { + } + + /// Called by peer to report status + fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + let peer = PeerInfo { + protocol_version: try!(r.val_at(0)), + network_id: try!(r.val_at(1)), + genesis: try!(r.val_at(2)), + }; + trace!(target: "inf", "New peer {} (protocol: {}, network: {:?}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.genesis); + if self.peers.contains_key(&peer_id) { + debug!(target: "inf", "Unexpected status packet from {}:{}", peer_id, io.peer_info(peer_id)); + return Ok(()); + } + let chain_info = io.chain().chain_info(); + if peer.genesis != chain_info.genesis_hash { + io.disable_peer(peer_id); + trace!(target: "inf", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, chain_info.genesis_hash, peer.genesis); + return Ok(()); + } + if peer.network_id != self.network_id { + io.disable_peer(peer_id); + trace!(target: "inf", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); + return Ok(()); + } + + self.peers.insert(peer_id.clone(), peer); + Ok(()) + } + + /// Called when a new peer is connected + pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) { + trace!(target: "inf", "== Connected {}: {}", peer, io.peer_info(peer)); + if let Err(e) = self.send_status(io) { + debug!(target:"inf", "Error sending status request: {:?}", e); + io.disable_peer(peer); + } + } + + /// Generic packet sender + fn send_packet(&mut self, sync: &mut SyncIo, peer_id: PeerId, packet_id: PacketId, packet: Bytes) { + if self.peers.contains_key(&peer_id) { + if let Err(e) = sync.send(peer_id, packet_id, packet) { + debug!(target:"inf", "Error sending request: {:?}", e); + sync.disable_peer(peer_id); + } + } + } + + /// Called when peer sends us new transactions + fn on_peer_packet(&mut self, _io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + self.chain.queue_infinity_message(r.as_raw().to_vec()); + Ok(()) + } + + /// Called by peer when it is disconnecting + pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) { + trace!(target: "inf", "== Disconnecting {}: {}", peer, io.peer_info(peer)); + if self.peers.contains_key(&peer) { + debug!(target: "inf", "Disconnected {}", peer); + self.peers.remove(&peer); + } + } + + /// Send Status message + fn send_status(&mut self, io: &mut SyncIo) -> Result<(), NetworkError> { + let mut packet = RlpStream::new_list(5); + let chain = io.chain().chain_info(); + packet.append(&(PROTOCOL_VERSION as u32)); + packet.append(&self.network_id); + packet.append(&chain.total_difficulty); + packet.append(&chain.best_block_hash); + packet.append(&chain.genesis_hash); + io.respond(STATUS_PACKET, packet.out()) + } + + pub fn dispatch_packet(sync: &RwLock, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + match packet_id { + STATUS_PACKET => sync.write().on_peer_status(io, peer, &rlp).unwrap_or_else( + |e| trace!(target: "inf", "Error processing packet: {:?}", e)), + GENERIC_PACKET => sync.write().on_peer_packet(io, peer, &rlp).unwrap_or_else( + |e| warn!(target: "inf", "Error queueing packet: {:?}", e)), + p @ _ => trace!(target: "inf", "Unexpected packet {} from {}", p, peer), + }; + } + + pub fn propagate_packet(&mut self, io: &mut SyncIo, packet: Bytes) { + let lucky_peers: Vec<_> = self.peers.keys().cloned().collect(); + trace!(target: "inf", "Sending packets to {:?}", lucky_peers); + for peer_id in lucky_peers { + self.send_packet(io, peer_id, GENERIC_PACKET, packet.clone()); + } + } +} + +#[cfg(test)] +mod tests { +} + diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 69dd03a2aed..29bd50bb2e9 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -82,6 +82,7 @@ extern crate parking_lot; mod chain; mod blocks; mod sync_io; +mod infinity; #[cfg(test)] mod tests; From 99a143eb37a0989f15a3d509140a18afc918f3c6 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 17:19:23 +0200 Subject: [PATCH 010/382] change broadcast interface, add basic message handling --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/client/client.rs | 12 ++++++++++-- ethcore/src/engines/mod.rs | 4 ++-- ethcore/src/engines/tendermint.rs | 14 ++++++++------ sync/src/api.rs | 2 +- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index e4638f1524e..64f525b9f5d 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -42,7 +42,7 @@ pub trait ChainNotify : Send + Sync { } /// fires when chain broadcasts a message - fn broadcast(&self, _data: Vec) { + fn broadcast(&self, _data: &[u8]) { } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bf871e4ad39..1dcc23d02a1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -25,6 +25,7 @@ use time::precise_time_ns; use util::{journaldb, rlp, Bytes, View, PerfTimer, Itertools, Mutex, RwLock}; use util::journaldb::JournalDB; use util::rlp::{UntrustedRlp}; +use util::ec::recover; use util::{U256, H256, Address, H2048, Uint}; use util::sha3::*; use util::kvdb::*; @@ -1021,8 +1022,15 @@ impl BlockChainClient for Client { self.miner.pending_transactions() } - fn queue_infinity_message(&self, _message: Bytes) { - //TODO: handle message here + fn queue_infinity_message(&self, message: Bytes) { + let full_rlp = UntrustedRlp::new(&message); + let signature = full_rlp.val_at(0).unwrap_or(return); + let message: Vec<_> = full_rlp.val_at(1).unwrap_or(return); + let message_rlp = UntrustedRlp::new(&message); + let pub_key = recover(&signature, &message.sha3()).unwrap_or(return); + if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { + self.notify(|notify| notify.broadcast(new_message.as_raw())); + } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index fc45edbc944..59464a85b3b 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -30,7 +30,7 @@ pub use self::tendermint::Tendermint; pub use self::signed_vote::{SignedVote, VoteError}; pub use self::propose_collect::{ProposeCollect}; -use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; @@ -121,7 +121,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: Bytes) -> Option> { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } // 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. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index cdc0aaf3f77..5726ab2ba16 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -88,6 +88,10 @@ impl Tendermint { let ref p = self.our_params; p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } + + fn propose_message(&self, message: UntrustedRlp) -> Option { + None + } } impl Engine for Tendermint { @@ -140,13 +144,11 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: Bytes) -> Option> { - match message[0] { - 0 => println!("0"), - _ => println!("unknown"), + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { + match message.val_at(0).unwrap_or(return None) { + 0u8 if sender == self.proposer() => self.propose_message(message), + _ => None, } - //let sig: Signature = message.into(); - None } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { diff --git a/sync/src/api.rs b/sync/src/api.rs index cb1c4722915..1f88c34bf03 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -189,7 +189,7 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn broadcast(&self, message: Vec) { + fn broadcast(&self, message: &[u8]) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); From 1cb3c164da6294ce6b7bb32baa6623b81d55b4c8 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 24 Aug 2016 11:58:49 +0200 Subject: [PATCH 011/382] propose step --- ethcore/src/client/client.rs | 11 ++++++----- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint.rs | 29 ++++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1dcc23d02a1..51376fc0ae2 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1024,12 +1024,13 @@ impl BlockChainClient for Client { fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - let signature = full_rlp.val_at(0).unwrap_or(return); - let message: Vec<_> = full_rlp.val_at(1).unwrap_or(return); + let signature = full_rlp.val_at(0).unwrap_or_else(|| return); + let message: Vec<_> = full_rlp.val_at(1).unwrap_or_else(|| return); let message_rlp = UntrustedRlp::new(&message); - let pub_key = recover(&signature, &message.sha3()).unwrap_or(return); - if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { - self.notify(|notify| notify.broadcast(new_message.as_raw())); + let pub_key = recover(&signature, &message.sha3()).unwrap_or_else(|| return); + if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) + { + self.notify(|notify| notify.broadcast(&new_message)); } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 59464a85b3b..ef7590540d4 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -121,7 +121,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } // 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. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 5726ab2ba16..0a674739080 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -38,7 +38,7 @@ pub struct TendermintParams { /// Consensus round. r: u64, /// Consensus step. - s: Step, + s: RwLock, /// Used to swith proposer. proposer_nonce: usize } @@ -61,7 +61,7 @@ impl From for TendermintParams { validators: val, validator_n: val_n, r: 0, - s: Step::Propose, + s: RwLock::new(Step::Propose), proposer_nonce: 0 } } @@ -89,9 +89,27 @@ impl Tendermint { p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } - fn propose_message(&self, message: UntrustedRlp) -> Option { + fn propose_message(&self, message: UntrustedRlp) -> Option { + match *self.our_params.s.try_read().unwrap() { + Step::Propose => (), + _ => return None, + } + let proposal = message.val_at(0).unwrap_or_else(|| return None); + let vote = ProposeCollect::new(proposal, + self.our_params.validators.iter().cloned().collect(), + self.threshold()); + let mut guard = self.our_params.s.try_write().unwrap(); + *guard = Step::Prevote(vote); + Some(message.as_raw().to_vec()) + } + + fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + + fn threshold(&self) -> usize { + self.our_params.validator_n*2/3 + } } impl Engine for Tendermint { @@ -144,9 +162,10 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { - match message.val_at(0).unwrap_or(return None) { + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { + match message.val_at(0).unwrap_or_else(|| return None) { 0u8 if sender == self.proposer() => self.propose_message(message), + 1 => self.prevote_message(sender, message), _ => None, } } From 77f06be7fbc431e7532c73c2029f1fd9cfaffa6b Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 24 Aug 2016 15:55:47 +0200 Subject: [PATCH 012/382] fix error propagation --- ethcore/res/bft.json | 39 ----------------------- ethcore/src/client/client.rs | 16 +++++----- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 2 +- ethcore/src/engines/mod.rs | 19 ++++++++++-- ethcore/src/engines/propose_collect.rs | 4 +-- ethcore/src/engines/signed_vote.rs | 10 ++---- ethcore/src/engines/tendermint.rs | 43 ++++++++++++++++---------- ethcore/src/error.rs | 12 +++---- 9 files changed, 63 insertions(+), 84 deletions(-) delete mode 100644 ethcore/res/bft.json diff --git a/ethcore/res/bft.json b/ethcore/res/bft.json deleted file mode 100644 index 24bd386b275..00000000000 --- a/ethcore/res/bft.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "TestBFT", - "engine": { - "BFT": { - "params": { - "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", - "validators" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] - } - } - }, - "params": { - "accountStartNonce": "0x0100000", - "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/src/client/client.rs b/ethcore/src/client/client.rs index 51376fc0ae2..87367bf9887 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1024,13 +1024,15 @@ impl BlockChainClient for Client { fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - let signature = full_rlp.val_at(0).unwrap_or_else(|| return); - let message: Vec<_> = full_rlp.val_at(1).unwrap_or_else(|| return); - let message_rlp = UntrustedRlp::new(&message); - let pub_key = recover(&signature, &message.sha3()).unwrap_or_else(|| return); - if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) - { - self.notify(|notify| notify.broadcast(&new_message)); + if let Ok(signature) = full_rlp.val_at(0) { + if let Ok(message) = full_rlp.val_at::>(1) { + let message_rlp = UntrustedRlp::new(&message); + if let Ok(pub_key) = recover(&signature, &message.sha3()) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { + self.notify(|notify| notify.broadcast(&new_message)); + } + } + } } } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 8852448ccef..a7d710da3de 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,7 +38,7 @@ use spec::Spec; use block_queue::BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use executive::Executed; -use error::CallError; +use error::{Error, CallError}; use trace::LocalizedTrace; /// Test client. diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index da876efa682..368bae7d666 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -183,7 +183,7 @@ pub trait BlockChainClient : Sync + Send { fn queue_transactions(&self, transactions: Vec); /// Queue packet - fn queue_infinity_message(&self, packet: Bytes); + fn queue_infinity_message(&self, message: Bytes); /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ef7590540d4..07d17399adf 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -27,8 +27,8 @@ pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::tendermint::Tendermint; -pub use self::signed_vote::{SignedVote, VoteError}; -pub use self::propose_collect::{ProposeCollect}; +pub use self::signed_vote::SignedVote; +pub use self::propose_collect::ProposeCollect; use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; use account_provider::AccountProvider; @@ -36,6 +36,19 @@ use block::ExecutedBlock; use spec::CommonParams; use evm::Schedule; +/// Voting errors. +#[derive(Debug)] +pub enum EngineError { + /// Voter is not in the voters set. + UnauthorisedVoter, + /// Message pertaining incorrect consensus step. + WrongStep, + /// Message pertaining unknown consensus step. + UnknownStep, + /// Message was not expected. + UnexpectedMessage +} + /// 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. pub trait Engine : Sync + Send { @@ -121,7 +134,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // 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. diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index f09618c50fa..f94132e7d15 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -18,7 +18,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; -use engines::VoteError; +use super::EngineError; /// Collect votes on a hash. #[derive(Debug)] @@ -59,7 +59,7 @@ impl ProposeCollect { fn can_vote(&self, signature: &Signature) -> Result<(), Error> { let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { - false => try!(Err(VoteError::UnauthorisedVoter)), + false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), } } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index d3381112a98..b7c5082a30e 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -16,6 +16,7 @@ //! Voting on hashes, where each vote has to come from a set of public keys. +use super::EngineError; use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; /// Signed voting on hashes. @@ -33,13 +34,6 @@ pub struct SignedVote { winner: RwLock> } -/// Voting errors. -#[derive(Debug)] -pub enum VoteError { - /// Voter is not in the voters set. - UnauthorisedVoter -} - impl SignedVote { /// Create a new instance of BFT engine pub fn new(voters: HashSet
, threshold: usize) -> Self { @@ -71,7 +65,7 @@ impl SignedVote { fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); match self.voters.contains(&signer) { - false => try!(Err(VoteError::UnauthorisedVoter)), + false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), } } diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 0a674739080..bc865ce2dcf 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -20,7 +20,7 @@ use common::*; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, ProposeCollect}; +use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; @@ -89,22 +89,22 @@ impl Tendermint { p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } - fn propose_message(&self, message: UntrustedRlp) -> Option { + fn propose_message(&self, message: UntrustedRlp) -> Result { match *self.our_params.s.try_read().unwrap() { Step::Propose => (), - _ => return None, + _ => try!(Err(EngineError::WrongStep)), } - let proposal = message.val_at(0).unwrap_or_else(|| return None); + let proposal = try!(message.val_at(0)); let vote = ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()); - let mut guard = self.our_params.s.try_write().unwrap(); + let mut guard = self.our_params.s.write(); *guard = Step::Prevote(vote); - Some(message.as_raw().to_vec()) + Ok(message.as_raw().to_vec()) } - fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Option { - None + fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { + try!(Err(EngineError::WrongStep)) } fn threshold(&self) -> usize { @@ -162,11 +162,11 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { - match message.val_at(0).unwrap_or_else(|| return None) { + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { + match try!(message.val_at(0)) { 0u8 if sender == self.proposer() => self.propose_message(message), 1 => self.prevote_message(sender, message), - _ => None, + _ => try!(Err(EngineError::UnknownStep)), } } @@ -224,18 +224,18 @@ mod tests { use spec::Spec; /// Create a new test chain spec with `Tendermint` consensus engine. - fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/bft.json")) } + fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } #[test] fn has_valid_metadata() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: 0.into(), @@ -251,7 +251,7 @@ mod tests { #[test] fn can_do_seal_verification_fail() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -265,7 +265,7 @@ mod tests { #[test] fn can_do_signature_verification_fail() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&Signature::zero()).to_vec()]); @@ -284,7 +284,7 @@ mod tests { let addr = tap.insert_account("".sha3(), "").unwrap(); tap.unlock_account_permanently(addr, "".into()).unwrap(); - let spec = new_test_authority(); + let spec = new_test_tendermint(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); @@ -298,6 +298,15 @@ mod tests { assert!(b.try_seal(engine, seal).is_ok()); } + #[test] + fn propose_step(){ + let engine = new_test_tendermint().engine; + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("1".sha3(), "1").unwrap(); + println!("{:?}", addr); + false; + } + #[test] fn handle_message() { false; diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index ccc926ce6ff..45ab99bcc86 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -24,7 +24,7 @@ use client::Error as ClientError; use ipc::binary::{BinaryConvertError, BinaryConvertable}; use types::block_import_error::BlockImportError; use snapshot::Error as SnapshotError; -use engines::VoteError; +use engines::EngineError; pub use types::executed::{ExecutionError, CallError}; @@ -240,7 +240,7 @@ pub enum Error { /// Snapshot error. Snapshot(SnapshotError), /// Consensus vote error. - Vote(VoteError), + Engine(EngineError), } impl fmt::Display for Error { @@ -261,7 +261,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Vote(ref err) => + Error::Engine(ref err) => f.write_fmt(format_args!("Bad vote: {:?}", err)), } } @@ -366,10 +366,10 @@ impl From for Error { } } -impl From for Error { - fn from(err: VoteError) -> Error { +impl From for Error { + fn from(err: EngineError) -> Error { match err { - other => Error::Vote(other), + other => Error::Engine(other), } } } From fcae03e55fe0f302b58e949f3839159e6b556f16 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 25 Aug 2016 19:22:10 +0200 Subject: [PATCH 013/382] propose message test --- ethcore/res/tendermint.json | 42 +++++++++++++++++++++++++++++++ ethcore/src/client/client.rs | 1 + ethcore/src/client/test_client.rs | 2 +- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint.rs | 38 ++++++++++++++++++++-------- 5 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 ethcore/res/tendermint.json diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json new file mode 100644 index 00000000000..12676123306 --- /dev/null +++ b/ethcore/res/tendermint.json @@ -0,0 +1,42 @@ +{ + "name": "TestBFT", + "engine": { + "Tendermint": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } + } + }, + "params": { + "accountStartNonce": "0x0100000", + "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/src/client/client.rs b/ethcore/src/client/client.rs index 87367bf9887..721a3154878 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1022,6 +1022,7 @@ impl BlockChainClient for Client { self.miner.pending_transactions() } + // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); if let Ok(signature) = full_rlp.val_at(0) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a7d710da3de..8852448ccef 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,7 +38,7 @@ use spec::Spec; use block_queue::BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use executive::Executed; -use error::{Error, CallError}; +use error::CallError; use trace::LocalizedTrace; /// Test client. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 07d17399adf..d27bd97a1c9 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -134,7 +134,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _sender: Address, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // 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. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index bc865ce2dcf..e5c46c65aaa 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -16,6 +16,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; use account_provider::AccountProvider; use block::*; @@ -40,7 +41,7 @@ pub struct TendermintParams { /// Consensus step. s: RwLock, /// Used to swith proposer. - proposer_nonce: usize + proposer_nonce: AtomicUsize } #[derive(Debug)] @@ -62,7 +63,7 @@ impl From for TendermintParams { validator_n: val_n, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: 0 + proposer_nonce: AtomicUsize::new(0) } } } @@ -86,7 +87,7 @@ impl Tendermint { fn proposer(&self) -> Address { let ref p = self.our_params; - p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() + p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } fn propose_message(&self, message: UntrustedRlp) -> Result { @@ -94,7 +95,8 @@ impl Tendermint { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } - let proposal = try!(message.val_at(0)); + let proposal = try!(message.as_val()); + self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); let vote = ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()); @@ -164,8 +166,8 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { match try!(message.val_at(0)) { - 0u8 if sender == self.proposer() => self.propose_message(message), - 1 => self.prevote_message(sender, message), + 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), + 1 => self.prevote_message(sender, try!(message.at(1))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -222,8 +224,10 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; + use super::Step; /// Create a new test chain spec with `Tendermint` consensus engine. + /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } #[test] @@ -299,12 +303,26 @@ mod tests { } #[test] - fn propose_step(){ + fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("1".sha3(), "1").unwrap(); - println!("{:?}", addr); - false; + let mut s = RlpStream::new_list(2); + let header = Header::default(); + s.append(&0u8).append(&header.bare_hash()); + let drain = s.out(); + let propose_rlp = UntrustedRlp::new(&drain); + + let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); + assert!(engine.handle_message(not_validator_addr, propose_rlp.clone()).is_err()); + + let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); + assert!(engine.handle_message(not_proposer_addr, propose_rlp.clone()).is_err()); + + let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); + assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], + engine.handle_message(proposer_addr, propose_rlp.clone()).unwrap()); + + assert!(engine.handle_message(not_proposer_addr, propose_rlp).is_err()); } #[test] From 2cc2bd6518628ba74ccd3a3673f9d75dab1cad25 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 10:40:00 +0200 Subject: [PATCH 014/382] impl Hash for Signature --- ethkey/src/signature.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index eec0fbf4787..b723ee3b020 100644 --- a/ethkey/src/signature.rs +++ b/ethkey/src/signature.rs @@ -18,6 +18,7 @@ use std::ops::{Deref, DerefMut}; use std::cmp::PartialEq; use std::{mem, fmt}; use std::str::FromStr; +use std::hash::{Hash, Hasher}; use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError}; use secp256k1::key::{SecretKey, PublicKey}; use rustc_serialize::hex::{ToHex, FromHex}; @@ -114,6 +115,12 @@ impl Default for Signature { } } +impl Hash for Signature { + fn hash(&self, state: &mut H) { + H520::from(self.0).hash(state); + } +} + impl From<[u8; 65]> for Signature { fn from(s: [u8; 65]) -> Self { Signature(s) From e7a9bf4df888031293753712ff5df1ca70919697 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 11:27:54 +0200 Subject: [PATCH 015/382] impl Clone for Signature --- ethkey/src/signature.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index b723ee3b020..8733f1245f5 100644 --- a/ethkey/src/signature.rs +++ b/ethkey/src/signature.rs @@ -121,6 +121,12 @@ impl Hash for Signature { } } +impl Clone for Signature { + fn clone(&self) -> Self { + Signature(self.0) + } +} + impl From<[u8; 65]> for Signature { fn from(s: [u8; 65]) -> Self { Signature(s) From a4ba7262ada81de63d2a0cb29bc6ed200c1de758 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 13:16:56 +0200 Subject: [PATCH 016/382] update Signature and ipc usage --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/client/client.rs | 15 +++++++-------- ethcore/src/engines/propose_collect.rs | 13 +++++++------ ethcore/src/engines/signed_vote.rs | 11 ++++++----- ethcore/src/engines/tendermint.rs | 23 ++++++----------------- ethcore/src/error.rs | 7 ++++--- sync/src/api.rs | 2 +- 7 files changed, 32 insertions(+), 41 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 64f525b9f5d..e4638f1524e 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -42,7 +42,7 @@ pub trait ChainNotify : Send + Sync { } /// fires when chain broadcasts a message - fn broadcast(&self, _data: &[u8]) { + fn broadcast(&self, _data: Vec) { } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index c84ebccfc02..991e880a883 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -25,12 +25,12 @@ use time::precise_time_ns; use util::{journaldb, rlp, Bytes, View, PerfTimer, Itertools, Mutex, RwLock}; use util::journaldb::JournalDB; use util::rlp::{UntrustedRlp}; -use util::ec::recover; -use util::{U256, H256, Address, H2048, Uint}; +use util::{U256, H256, H520, Address, H2048, Uint}; use util::sha3::*; use util::kvdb::*; // other +use ethkey::recover; use io::*; use views::{BlockView, HeaderView, BodyView}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult}; @@ -1031,12 +1031,11 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - if let Ok(signature) = full_rlp.val_at(0) { - if let Ok(message) = full_rlp.val_at::>(1) { - let message_rlp = UntrustedRlp::new(&message); - if let Ok(pub_key) = recover(&signature, &message.sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { - self.notify(|notify| notify.broadcast(&new_message)); + if let Ok(signature) = full_rlp.val_at::(0) { + if let Ok(message) = full_rlp.at(1) { + if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message) { + self.notify(|notify| notify.broadcast(new_message.clone())); } } } diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index f94132e7d15..c693c71ec43 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -17,8 +17,9 @@ //! Voting on a hash, where each vote has to come from a set of addresses. use std::sync::atomic::{AtomicBool, Ordering}; -use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use common::{HashSet, RwLock, H256, Address, Error, Hashable}; use super::EngineError; +use ethkey::{recover, Signature}; /// Collect votes on a hash. #[derive(Debug)] @@ -57,7 +58,7 @@ impl ProposeCollect { } fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); + let signer = Address::from(try!(recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), @@ -108,18 +109,18 @@ mod tests { // Unapproved voter. let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(&signature.into())); + assert!(!vote.vote(&signature)); assert!(vote.winner().is_none()); // First good vote. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(&signature.into())); + assert!(vote.vote(&signature)); assert_eq!(vote.winner().unwrap(), bare_hash); // Voting again is ineffective. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(&signature.into())); + assert!(!vote.vote(&signature)); // Second valid vote. let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(&signature.into())); + assert!(vote.vote(&signature)); assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index b7c5082a30e..e3627e986d5 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -17,7 +17,8 @@ //! Voting on hashes, where each vote has to come from a set of public keys. use super::EngineError; -use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use common::{HashSet, HashMap, RwLock, H256, Address, Error, Hashable}; +use ethkey::{Signature, recover}; /// Signed voting on hashes. #[derive(Debug)] @@ -49,11 +50,11 @@ impl SignedVote { } /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { - if !self.can_vote(&bare_hash, signature).is_ok() { return false; } + pub fn vote(&self, bare_hash: H256, signature: Signature) -> bool { + if !self.can_vote(&bare_hash, &signature).is_ok() { return false; } let mut guard = self.votes.try_write().unwrap(); let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); - if !set.insert(signature.clone()) { return false; } + if !set.insert(signature) { return false; } // Set the winner if threshold is reached. if set.len() >= self.threshold { let mut guard = self.winner.try_write().unwrap(); @@ -63,7 +64,7 @@ impl SignedVote { } fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + let signer = Address::from(try!(recover(&signature, bare_hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index e5c46c65aaa..2899c77df97 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -106,7 +106,10 @@ impl Tendermint { } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { - try!(Err(EngineError::WrongStep)) + match *self.our_params.s.try_write().unwrap() { + Step::Prevote(ref mut vote) => try!(Err(EngineError::WrongStep)), + _ => try!(Err(EngineError::WrongStep)), + } } fn threshold(&self) -> usize { @@ -167,7 +170,7 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { match try!(message.val_at(0)) { 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), - 1 => self.prevote_message(sender, try!(message.at(1))), + 1 if self.our_params.validators.contains(&sender) => self.prevote_message(sender, try!(message.at(1))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -225,6 +228,7 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use super::Step; + use ethkey::Signature; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. @@ -267,21 +271,6 @@ mod tests { } } - #[test] - fn can_do_signature_verification_fail() { - let engine = new_test_tendermint().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_generate_seal() { let tap = AccountProvider::transient_provider(); diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 1f854837e23..5d6b56c1662 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -366,9 +366,10 @@ impl From for Error { impl From for Error { fn from(err: EngineError) -> Error { - match err { - other => Error::Engine(other), - } + Error::Engine(err) + } +} + impl From for Error { fn from(err: EthkeyError) -> Error { Error::Ethkey(err) diff --git a/sync/src/api.rs b/sync/src/api.rs index 59d2723eee4..92b276d21b9 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -189,7 +189,7 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn broadcast(&self, message: &[u8]) { + fn broadcast(&self, message: Vec) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); From f60d4645edeb49bc4d0b52411c0fd9307cd29086 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 19:27:02 +0200 Subject: [PATCH 017/382] move vote with addresses, remove recover check --- ethcore/src/engines/propose_collect.rs | 60 ++++++++++---------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index c693c71ec43..ee4aa381036 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -17,9 +17,7 @@ //! Voting on a hash, where each vote has to come from a set of addresses. use std::sync::atomic::{AtomicBool, Ordering}; -use common::{HashSet, RwLock, H256, Address, Error, Hashable}; -use super::EngineError; -use ethkey::{recover, Signature}; +use common::{HashSet, RwLock, H256, Address}; /// Collect votes on a hash. #[derive(Debug)] @@ -31,7 +29,7 @@ pub struct ProposeCollect { /// Threshold vote number for success. pub threshold: usize, /// Votes. - votes: RwLock>, + votes: RwLock>, /// Was enough votes reached. is_won: AtomicBool } @@ -50,35 +48,24 @@ impl ProposeCollect { } /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, signature: &Signature) -> bool { - if self.votes.try_read().unwrap().contains(signature) { return false; } - if !self.can_vote(signature).is_ok() { return false; } - self.votes.try_write().unwrap().insert(signature.clone()); + pub fn vote(&self, voter: Address) -> bool { + if self.votes.try_read().unwrap().contains(&voter) { return false; } + if !self.voters.contains(&voter) { return false; } + self.votes.try_write().unwrap().insert(voter); true } - fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(recover(&signature, &self.hash)).sha3()); - match self.voters.contains(&signer) { - false => try!(Err(EngineError::UnauthorisedVoter)), - true => Ok(()), - } - } - /// Some winner if voting threshold was reached. - pub fn winner(&self) -> Option { + pub fn is_won(&self) -> bool { let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { true => { self.is_won.store(true, Ordering::Relaxed); true }, false => false, }; - match self.is_won.load(Ordering::Relaxed) || threshold_checker() { - true => Some(self.hash), - false => None, - } + self.is_won.load(Ordering::Relaxed) || threshold_checker() } - /// Get signatures backing given hash. - pub fn votes(&self) -> HashSet { + /// Get addresses backing given hash. + pub fn votes(&self) -> HashSet
{ self.votes.try_read().unwrap().clone() } } @@ -103,24 +90,21 @@ mod tests { let header = Header::default(); let bare_hash = header.bare_hash(); - let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); - let vote = ProposeCollect::new(bare_hash, voters.into(), 1); - assert!(vote.winner().is_none()); + let voters: HashSet<_> = vec![addr1.clone(), addr2.clone(), Address::default()].into_iter().map(Into::into).collect(); + let vote = ProposeCollect::new(bare_hash, voters.into(), 2); + assert!(!vote.is_won()); // Unapproved voter. - let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(&signature)); - assert!(vote.winner().is_none()); + assert!(!vote.vote(addr3)); + assert!(!vote.is_won()); // First good vote. - let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(&signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); + assert!(vote.vote(addr1.clone())); + assert!(!vote.is_won()); // Voting again is ineffective. - let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(&signature)); - // Second valid vote. - let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(&signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); + assert!(!vote.vote(addr1)); + assert!(!vote.is_won()); + // Second valid vote thus win. + assert!(vote.vote(addr2)); + assert!(vote.is_won()); } } From a12a764d6c370cecaf7831e68243295ab7c8c127 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 19:27:50 +0200 Subject: [PATCH 018/382] add rounds check, simplify tests --- ethcore/src/engines/mod.rs | 6 +- ethcore/src/engines/signed_vote.rs | 8 +- ethcore/src/engines/tendermint.rs | 116 +++++++++++++++++++++++------ 3 files changed, 101 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index d27bd97a1c9..1db8acb0fc1 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -46,7 +46,11 @@ pub enum EngineError { /// Message pertaining unknown consensus step. UnknownStep, /// Message was not expected. - UnexpectedMessage + UnexpectedMessage, + /// Received a vote for a different proposal. + WrongVote, + /// Received message is from a different consensus round. + WrongRound } /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index e3627e986d5..323972ed420 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -106,18 +106,18 @@ mod tests { // Unapproved voter. let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, &signature.into())); + assert!(!vote.vote(bare_hash, signature)); assert!(vote.winner().is_none()); // First good vote. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, &signature.into())); + assert!(vote.vote(bare_hash, signature)); assert_eq!(vote.winner().unwrap(), bare_hash); // Voting again is ineffective. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, &signature.into())); + assert!(!vote.vote(bare_hash, signature)); // Second valid vote. let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, &signature.into())); + assert!(vote.vote(bare_hash, signature)); assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 2899c77df97..76f9938b281 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -41,7 +41,9 @@ pub struct TendermintParams { /// Consensus step. s: RwLock, /// Used to swith proposer. - proposer_nonce: AtomicUsize + proposer_nonce: AtomicUsize, + /// Seal collection. + seal: Vec } #[derive(Debug)] @@ -63,7 +65,8 @@ impl From for TendermintParams { validator_n: val_n, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0) + proposer_nonce: AtomicUsize::new(0), + seal: Vec::new() } } } @@ -90,24 +93,76 @@ impl Tendermint { p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } + fn is_proposer(&self, address: &Address) -> bool { + self.proposer() == *address + } + + fn is_validator(&self, address: &Address) -> bool { + self.our_params.validators.contains(address) + } + + fn new_vote(&self, proposal: H256) -> ProposeCollect { + ProposeCollect::new(proposal, + self.our_params.validators.iter().cloned().collect(), + self.threshold()) + } + fn propose_message(&self, message: UntrustedRlp) -> Result { + // Check if message is for correct step. match *self.our_params.s.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } let proposal = try!(message.as_val()); self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); - let vote = ProposeCollect::new(proposal, - self.our_params.validators.iter().cloned().collect(), - self.threshold()); let mut guard = self.our_params.s.write(); - *guard = Step::Prevote(vote); + // Proceed to the prevote step. + *guard = Step::Prevote(self.new_vote(proposal)); Ok(message.as_raw().to_vec()) } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { + // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => try!(Err(EngineError::WrongStep)), + Step::Prevote(ref mut vote) => { + // Vote if message is about the right block. + if vote.hash == try!(message.as_val()) { + vote.vote(sender); + // Move to next step is prevote is won. + if vote.is_won() { + let mut guard = self.our_params.s.write(); + *guard = Step::Precommit(self.new_vote(vote.hash)); + Ok(message.as_raw().to_vec()) + } else { + Ok(message.as_raw().to_vec()) + } + } else { + try!(Err(EngineError::WrongVote)) + } + }, + _ => try!(Err(EngineError::WrongStep)), + } + } + + fn precommit_message(&self, sender: Address, message: UntrustedRlp) -> Result { + // Check if message is for correct step. + match *self.our_params.s.try_write().unwrap() { + Step::Prevote(ref mut vote) => { + // Vote and accumulate seal if message is about the right block. + if vote.hash == try!(message.as_val()) { + vote.vote(sender); + // Commit if precommit is won. + if vote.is_won() { + let mut guard = self.our_params.s.write(); + *guard = Step::Commit; + Ok(message.as_raw().to_vec()) + } else { + Ok(message.as_raw().to_vec()) + } + } else { + try!(Err(EngineError::WrongVote)) + } + }, _ => try!(Err(EngineError::WrongStep)), } } @@ -168,9 +223,13 @@ impl Engine for Tendermint { } fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { - match try!(message.val_at(0)) { - 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), - 1 if self.our_params.validators.contains(&sender) => self.prevote_message(sender, try!(message.at(1))), + // Check if correct round. + if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + // Handle according to step. + match try!(message.val_at(1)) { + 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), + 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), + 2 if self.is_validator(&sender) => self.precommit_message(sender, try!(message.at(2))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -219,7 +278,6 @@ impl Engine for Tendermint { } } - #[cfg(test)] mod tests { use common::*; @@ -227,13 +285,22 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use super::Step; - use ethkey::Signature; + use engines::Engine; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + fn propose_default(engine: &Arc, proposer: Address) -> Result { + let mut s = RlpStream::new_list(3); + let header = Header::default(); + s.append(&0u8).append(&0u8).append(&header.bare_hash()); + let drain = s.out(); + let propose_rlp = UntrustedRlp::new(&drain); + + engine.handle_message(proposer, propose_rlp) + } + #[test] fn has_valid_metadata() { let engine = new_test_tendermint().engine; @@ -284,8 +351,7 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(db.as_hashdb_mut()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); - let vm_factory = Default::default(); - let b = OpenBlock::new(engine, &vm_factory, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + 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()); @@ -295,23 +361,25 @@ mod tests { fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let mut s = RlpStream::new_list(2); - let header = Header::default(); - s.append(&0u8).append(&header.bare_hash()); - let drain = s.out(); - let propose_rlp = UntrustedRlp::new(&drain); let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(engine.handle_message(not_validator_addr, propose_rlp.clone()).is_err()); + assert!(propose_default(&engine, not_validator_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(engine.handle_message(not_proposer_addr, propose_rlp.clone()).is_err()); + assert!(propose_default(&engine, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], - engine.handle_message(proposer_addr, propose_rlp.clone()).unwrap()); + propose_default(&engine, proposer_addr).unwrap()); + + assert!(propose_default(&engine, proposer_addr).is_err()); + assert!(propose_default(&engine, not_proposer_addr).is_err()); + } - assert!(engine.handle_message(not_proposer_addr, propose_rlp).is_err()); + #[test] + fn prevote_step() { + let engine = new_test_tendermint().engine; + propose_default(&engine, Address::default()); } #[test] From 402564518895d21bc02dab17708910f9424c2b56 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 29 Aug 2016 12:09:51 +0200 Subject: [PATCH 019/382] accumulate seal in precommit --- ethcore/src/client/client.rs | 3 ++- ethcore/src/engines/mod.rs | 4 ++-- ethcore/src/engines/tendermint.rs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 991e880a883..9fea1513a3d 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1034,7 +1034,8 @@ impl BlockChainClient for Client { if let Ok(signature) = full_rlp.val_at::(0) { if let Ok(message) = full_rlp.at(1) { if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), signature, message) + { self.notify(|notify| notify.broadcast(new_message.clone())); } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 1db8acb0fc1..7dbdaf86b6e 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -30,7 +30,7 @@ pub use self::tendermint::Tendermint; pub use self::signed_vote::SignedVote; pub use self::propose_collect::ProposeCollect; -use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp, H520}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; @@ -138,7 +138,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _sender: Address, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _sender: Address, _signature: H520, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // 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. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 76f9938b281..d1bfe26ced8 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -50,7 +50,7 @@ pub struct TendermintParams { enum Step { Propose, Prevote(ProposeCollect), - Precommit(ProposeCollect), + Precommit(ProposeCollect, Vec), Commit } @@ -131,7 +131,7 @@ impl Tendermint { // Move to next step is prevote is won. if vote.is_won() { let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(vote.hash)); + *guard = Step::Precommit(self.new_vote(vote.hash), Vec::new()); Ok(message.as_raw().to_vec()) } else { Ok(message.as_raw().to_vec()) @@ -144,13 +144,13 @@ impl Tendermint { } } - fn precommit_message(&self, sender: Address, message: UntrustedRlp) -> Result { + fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => { + Step::Precommit(ref mut vote, ref mut seal) => { // Vote and accumulate seal if message is about the right block. if vote.hash == try!(message.as_val()) { - vote.vote(sender); + if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. if vote.is_won() { let mut guard = self.our_params.s.write(); @@ -222,14 +222,14 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { + fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), - 2 if self.is_validator(&sender) => self.precommit_message(sender, try!(message.at(2))), + 2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -298,7 +298,7 @@ mod tests { let drain = s.out(); let propose_rlp = UntrustedRlp::new(&drain); - engine.handle_message(proposer, propose_rlp) + engine.handle_message(proposer, H520::default(), propose_rlp) } #[test] From d7499044e3a3590471720981d4ba606397536d1a Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 29 Aug 2016 14:32:37 +0200 Subject: [PATCH 020/382] move seal into commit --- ethcore/src/engines/tendermint.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index d1bfe26ced8..116a0ce16b2 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,6 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::time::Duration; use common::*; use account_provider::AccountProvider; use block::*; @@ -36,24 +37,36 @@ pub struct TendermintParams { pub validators: Vec
, /// Number of validators. pub validator_n: usize, + /// Timeout durations for different steps. + timeouts: Timeouts, /// Consensus round. r: u64, /// Consensus step. s: RwLock, /// Used to swith proposer. proposer_nonce: AtomicUsize, - /// Seal collection. - seal: Vec } #[derive(Debug)] enum Step { Propose, Prevote(ProposeCollect), - Precommit(ProposeCollect, Vec), - Commit + /// Precommit step storing the precommit vote and accumulating seal. + Precommit(ProposeCollect, Seal), + /// Commit step storing a complete valid seal. + Commit(Seal) } +#[derive(Debug)] +struct Timeouts { + propose: Duration, + prevote: Duration, + precommit: Duration, + commit: Duration +} + +type Seal = Vec; + impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); @@ -63,10 +76,10 @@ impl From for TendermintParams { duration_limit: p.duration_limit.into(), validators: val, validator_n: val_n, + timeouts: Timeouts { propose: Duration::from_secs(3), prevote: Duration::from_secs(3), precommit: Duration::from_secs(3), commit: Duration::from_secs(3) }, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0), - seal: Vec::new() + proposer_nonce: AtomicUsize::new(0) } } } @@ -154,7 +167,7 @@ impl Tendermint { // Commit if precommit is won. if vote.is_won() { let mut guard = self.our_params.s.write(); - *guard = Step::Commit; + *guard = Step::Commit(seal.clone()); Ok(message.as_raw().to_vec()) } else { Ok(message.as_raw().to_vec()) @@ -245,7 +258,7 @@ impl Engine for Tendermint { Ok(()) } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { Ok(()) } From e475d0bf4ca97e72deed10781fb2352b0f10ae18 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 31 Aug 2016 18:18:02 +0200 Subject: [PATCH 021/382] initial timeouts --- ethcore/res/tendermint.json | 1 - ethcore/src/engines/propose_collect.rs | 11 +- ethcore/src/engines/tendermint.rs | 162 ++++++++++++++++++++----- ethcore/src/service.rs | 4 + json/src/spec/tendermint.rs | 4 - 5 files changed, 146 insertions(+), 36 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 12676123306..8aa8f24f77b 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,7 +4,6 @@ "Tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", "validators" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index ee4aa381036..46defd557b1 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -49,10 +49,13 @@ impl ProposeCollect { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, voter: Address) -> bool { - if self.votes.try_read().unwrap().contains(&voter) { return false; } - if !self.voters.contains(&voter) { return false; } - self.votes.try_write().unwrap().insert(voter); - true + match self.votes.try_read().unwrap().contains(&voter) || !self.voters.contains(&voter) { + true => false, + false => { + self.votes.try_write().unwrap().insert(voter); + true + }, + } } /// Some winner if voting threshold was reached. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 116a0ce16b2..8ccbbf95a0a 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,7 +17,6 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -use std::time::Duration; use common::*; use account_provider::AccountProvider; use block::*; @@ -25,28 +24,54 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; +use io::{IoContext, IoHandler, TimerToken}; +use service::{ClientIoMessage, ENGINE_TIMEOUT_TOKEN}; +use time::get_time; /// `Tendermint` params. #[derive(Debug)] pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// Block duration. - pub duration_limit: u64, /// List of validators. pub validators: Vec
, /// Number of validators. pub validator_n: usize, /// Timeout durations for different steps. - timeouts: Timeouts, + timeouts: DefaultTimeouts, /// Consensus round. r: u64, /// Consensus step. s: RwLock, + /// Current step timeout in ms. + timeout: AtomicTimerToken, /// Used to swith proposer. proposer_nonce: AtomicUsize, } +impl Default for TendermintParams { + fn default() -> Self { + let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = validators.len(); + let propose_timeout = 3000; + TendermintParams { + gas_limit_bound_divisor: 0x0400.into(), + validators: validators, + validator_n: val_n, + timeouts: DefaultTimeouts { + propose: propose_timeout, + prevote: 3000, + precommit: 3000, + commit: 3000 + }, + r: 0, + s: RwLock::new(Step::Propose), + timeout: AtomicUsize::new(propose_timeout), + proposer_nonce: AtomicUsize::new(0) + } + } +} + #[derive(Debug)] enum Step { Propose, @@ -58,27 +83,62 @@ enum Step { } #[derive(Debug)] -struct Timeouts { - propose: Duration, - prevote: Duration, - precommit: Duration, - commit: Duration +struct DefaultTimeouts { + propose: TimerToken, + prevote: TimerToken, + precommit: TimerToken, + commit: TimerToken } type Seal = Vec; +type AtomicTimerToken = AtomicUsize; + +impl IoHandler for Tendermint { + fn initialize(&self, io: &IoContext) { + io.register_timer(ENGINE_TIMEOUT_TOKEN, self.our_params.timeout.load(AtomicOrdering::Relaxed) as u64).expect("Error registering engine timeout"); + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + println!("Timeout: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); + match *self.our_params.s.try_read().unwrap() { + Step::Propose => self.to_propose(), + Step::Prevote(ref proposal) => self.to_precommit(proposal.hash.clone()), + Step::Precommit(_, _) => self.to_propose(), + Step::Commit(_) => self.to_propose(), + }; + io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") + } + } + + fn message(&self, io: &IoContext, net_message: &ClientIoMessage) { + if let &ClientIoMessage::ConsensusStep(next_timeout) = net_message { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); + io.register_timer(ENGINE_TIMEOUT_TOKEN, next_timeout).expect("Failed to start new consensus timer.") + } + } +} impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); + let propose_timeout = 3; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - duration_limit: p.duration_limit.into(), validators: val, validator_n: val_n, - timeouts: Timeouts { propose: Duration::from_secs(3), prevote: Duration::from_secs(3), precommit: Duration::from_secs(3), commit: Duration::from_secs(3) }, + timeouts: DefaultTimeouts { + propose: propose_timeout, + prevote: 3, + precommit: 3, + commit: 3 + }, r: 0, s: RwLock::new(Step::Propose), + timeout: AtomicUsize::new(propose_timeout), proposer_nonce: AtomicUsize::new(0) } } @@ -120,6 +180,12 @@ impl Tendermint { self.threshold()) } + fn to_propose(&self) { + self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + let mut guard = self.our_params.s.write(); + *guard = Step::Propose; + } + fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_read().unwrap() { @@ -127,11 +193,14 @@ impl Tendermint { _ => try!(Err(EngineError::WrongStep)), } let proposal = try!(message.as_val()); - self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + self.to_prevote(proposal); + Ok(message.as_raw().to_vec()) + } + + fn to_prevote(&self, proposal: H256) { let mut guard = self.our_params.s.write(); // Proceed to the prevote step. *guard = Step::Prevote(self.new_vote(proposal)); - Ok(message.as_raw().to_vec()) } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { @@ -142,13 +211,8 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. - if vote.is_won() { - let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(vote.hash), Vec::new()); - Ok(message.as_raw().to_vec()) - } else { - Ok(message.as_raw().to_vec()) - } + if vote.is_won() { self.to_precommit(vote.hash); } + Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } @@ -157,6 +221,11 @@ impl Tendermint { } } + fn to_precommit(&self, proposal: H256) { + let mut guard = self.our_params.s.write(); + *guard = Step::Precommit(self.new_vote(proposal), Vec::new()); + } + fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { @@ -165,13 +234,8 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. - if vote.is_won() { - let mut guard = self.our_params.s.write(); - *guard = Step::Commit(seal.clone()); - Ok(message.as_raw().to_vec()) - } else { - Ok(message.as_raw().to_vec()) - } + if vote.is_won() { self.to_commit(seal.clone()); } + Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } @@ -180,6 +244,11 @@ impl Tendermint { } } + fn to_commit(&self, seal: Seal) { + let mut guard = self.our_params.s.write(); + *guard = Step::Commit(seal); + } + fn threshold(&self) -> usize { self.our_params.validator_n*2/3 } @@ -294,16 +363,40 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use common::*; + use std::thread::sleep; + use std::time::{Duration, Instant}; use block::*; use tests::helpers::*; + use service::{ClientService, ClientIoMessage}; + use devtools::RandomTempPath; + use client::ClientConfig; + use miner::Miner; use account_provider::AccountProvider; use spec::Spec; use engines::Engine; + use super::{Tendermint, TendermintParams}; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + fn new_test_client_service() -> ClientService { + let temp_path = RandomTempPath::new(); + let mut path = temp_path.as_path().to_owned(); + path.push("pruning"); + path.push("db"); + + let spec = get_test_spec(); + let service = ClientService::start( + ClientConfig::default(), + &spec, + &path, + &path, + Arc::new(Miner::with_spec(&spec)), + ); + service.unwrap() + } + fn propose_default(engine: &Arc, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); @@ -399,4 +492,19 @@ mod tests { fn handle_message() { false; } + + #[test] + fn timeout_switching() { + let service = new_test_client_service(); + let engine = new_test_tendermint().engine; + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + service.register_io_handler(Arc::new(tender)); + + println!("Waiting for timeout"); + sleep(Duration::from_secs(10)); + + let message_channel = service.io().channel(); + message_channel.send(ClientIoMessage::ConsensusStep(1000)); + sleep(Duration::from_secs(5)); + } } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 355c7d58047..7a3da2691c1 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -43,6 +43,8 @@ pub enum ClientIoMessage { FeedStateChunk(H256, Bytes), /// Feed a block chunk to the snapshot service FeedBlockChunk(H256, Bytes), + /// Signal consensus step timeout. + ConsensusStep(u64), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -143,6 +145,8 @@ struct ClientIoHandler { const CLIENT_TICK_TIMER: TimerToken = 0; const CLIENT_TICK_MS: u64 = 5000; +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 1; impl IoHandler for ClientIoHandler { fn initialize(&self, io: &IoContext) { diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index c3294810c6b..97c30fbb21d 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -25,9 +25,6 @@ pub struct TendermintParams { /// Gas limit divisor. #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, - /// Block duration. - #[serde(rename="durationLimit")] - pub duration_limit: Uint, /// Valid authorities pub validators: Vec
, } @@ -49,7 +46,6 @@ mod tests { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] } }"#; From 0fcbf8d99fd2b3b84dd71a7d47ff4cd4974b5d58 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 31 Aug 2016 18:43:24 +0200 Subject: [PATCH 022/382] fix after merge --- ethcore/src/engines/tendermint.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 8ccbbf95a0a..7333cf70dea 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -108,7 +108,7 @@ impl IoHandler for Tendermint { Step::Precommit(_, _) => self.to_propose(), Step::Commit(_) => self.to_propose(), }; - io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") + //io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") } } @@ -271,17 +271,16 @@ impl Engine for Tendermint { } fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { - header.difficulty = parent.difficulty; - header.gas_limit = { - let gas_limit = parent.gas_limit; + 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()) } - }; - header.note_dirty(); + }); } /// Apply the block reward on finalisation of the block. @@ -319,9 +318,9 @@ impl Engine for Tendermint { 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() { + if header.seal().len() != self.seal_fields() { return Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.seal_fields(), found: header.seal.len() } + Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))); } Ok(()) @@ -342,10 +341,10 @@ impl Engine for Tendermint { 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 }))); + 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(()) } From 83c371e6d4c04245b009b454a0975adb7148722e Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Sep 2016 14:12:26 +0200 Subject: [PATCH 023/382] add non renewing timer --- util/io/src/service.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/util/io/src/service.rs b/util/io/src/service.rs index a47e84e568b..c75efbdb6a3 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -53,6 +53,7 @@ pub enum IoMessage where Message: Send + Clone + Sized { handler_id: HandlerId, token: TimerToken, delay: u64, + once: bool, }, RemoveTimer { handler_id: HandlerId, @@ -89,12 +90,24 @@ impl IoContext where Message: Send + Clone + '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(()) } @@ -160,6 +173,7 @@ impl IoContext where Message: Send + Clone + 'static { struct UserTimer { delay: u64, timeout: Timeout, + once: bool, } /// Root IO handler. Manages user handlers, messages and IO timers. @@ -228,8 +242,14 @@ impl Handler for IoManager where Message: Send + Clone + Sync let handler_index = token.as_usize() / TOKENS_PER_HANDLER; let token_id = token.as_usize() % TOKENS_PER_HANDLER; if let Some(handler) = self.handlers.get(handler_index) { - if let Some(timer) = self.timers.read().get(&token.as_usize()) { - event_loop.timeout_ms(token, timer.delay).expect("Error re-registering user timer"); + let option = self.timers.read().get(&token.as_usize()).cloned(); + if let Some(timer) = option { + if timer.once { + self.timers.write().remove(&token_id); + event_loop.clear_timeout(timer.timeout); + } else { + event_loop.timeout_ms(token, 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(); } @@ -257,10 +277,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_ms(Token(timer_id), 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; From 8851acec7c76914b07969037fb568afff4a6f0e9 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Sep 2016 17:06:43 +0200 Subject: [PATCH 024/382] fix propose collect locking --- ethcore/src/engines/propose_collect.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index 46defd557b1..ad245e2cd02 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -49,12 +49,12 @@ impl ProposeCollect { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, voter: Address) -> bool { - match self.votes.try_read().unwrap().contains(&voter) || !self.voters.contains(&voter) { - true => false, - false => { - self.votes.try_write().unwrap().insert(voter); - true - }, + let is_known = self.votes.try_read().unwrap().contains(&voter); + if !is_known && self.voters.contains(&voter) { + self.votes.try_write().unwrap().insert(voter); + true + } else { + false } } @@ -66,11 +66,6 @@ impl ProposeCollect { }; self.is_won.load(Ordering::Relaxed) || threshold_checker() } - - /// Get addresses backing given hash. - pub fn votes(&self) -> HashSet
{ - self.votes.try_read().unwrap().clone() - } } #[cfg(test)] From 0af4bf23a982ced4b97d39aa5e2684d4738b53ba Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Sep 2016 17:51:29 +0200 Subject: [PATCH 025/382] add internal timeout service, test proposer switching --- ethcore/src/engines/tendermint.rs | 245 ++++++++++++++++-------------- ethcore/src/service.rs | 4 - ethcore/src/spec/spec.rs | 2 +- 3 files changed, 130 insertions(+), 121 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 7333cf70dea..66b46992383 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,6 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::sync::Weak; use common::*; use account_provider::AccountProvider; use block::*; @@ -24,8 +25,7 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken}; -use service::{ClientIoMessage, ENGINE_TIMEOUT_TOKEN}; +use io::{IoContext, IoHandler, TimerToken, IoService}; use time::get_time; /// `Tendermint` params. @@ -39,14 +39,6 @@ pub struct TendermintParams { pub validator_n: usize, /// Timeout durations for different steps. timeouts: DefaultTimeouts, - /// Consensus round. - r: u64, - /// Consensus step. - s: RwLock, - /// Current step timeout in ms. - timeout: AtomicTimerToken, - /// Used to swith proposer. - proposer_nonce: AtomicUsize, } impl Default for TendermintParams { @@ -64,10 +56,6 @@ impl Default for TendermintParams { precommit: 3000, commit: 3000 }, - r: 0, - s: RwLock::new(Step::Propose), - timeout: AtomicUsize::new(propose_timeout), - proposer_nonce: AtomicUsize::new(0) } } } @@ -82,88 +70,70 @@ enum Step { Commit(Seal) } -#[derive(Debug)] -struct DefaultTimeouts { - propose: TimerToken, - prevote: TimerToken, - precommit: TimerToken, - commit: TimerToken -} - -type Seal = Vec; -type AtomicTimerToken = AtomicUsize; - -impl IoHandler for Tendermint { - fn initialize(&self, io: &IoContext) { - io.register_timer(ENGINE_TIMEOUT_TOKEN, self.our_params.timeout.load(AtomicOrdering::Relaxed) as u64).expect("Error registering engine timeout"); - } - - fn timeout(&self, io: &IoContext, timer: TimerToken) { - if timer == ENGINE_TIMEOUT_TOKEN { - println!("Timeout: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); - match *self.our_params.s.try_read().unwrap() { - Step::Propose => self.to_propose(), - Step::Prevote(ref proposal) => self.to_precommit(proposal.hash.clone()), - Step::Precommit(_, _) => self.to_propose(), - Step::Commit(_) => self.to_propose(), - }; - //io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") - } - } - - fn message(&self, io: &IoContext, net_message: &ClientIoMessage) { - if let &ClientIoMessage::ConsensusStep(next_timeout) = net_message { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); - io.register_timer(ENGINE_TIMEOUT_TOKEN, next_timeout).expect("Failed to start new consensus timer.") - } - } -} - impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); - let propose_timeout = 3; + let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), validators: val, validator_n: val_n, timeouts: DefaultTimeouts { propose: propose_timeout, - prevote: 3, - precommit: 3, - commit: 3 + prevote: 3000, + precommit: 3000, + commit: 3000 }, - r: 0, - s: RwLock::new(Step::Propose), - timeout: AtomicUsize::new(propose_timeout), - proposer_nonce: AtomicUsize::new(0) } } } +#[derive(Clone)] +struct StepMessage; + /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, + timeout_service: IoService, + /// Consensus round. + r: u64, + /// Consensus step. + s: RwLock, + /// Current step timeout in ms. + timeout: AtomicMs, + /// Used to swith proposer. + proposer_nonce: AtomicUsize, +} + +struct TimerHandler { + engine: Weak, } impl Tendermint { /// Create a new instance of Tendermint engine - pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Self { - Tendermint { - params: params, - our_params: our_params, - builtins: builtins, - } + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { + let engine = Arc::new( + Tendermint { + params: params, + timeout: AtomicUsize::new(our_params.timeouts.propose), + our_params: our_params, + builtins: builtins, + timeout_service: IoService::::start().expect("Error creating engine timeout service"), + r: 0, + s: RwLock::new(Step::Propose), + proposer_nonce: AtomicUsize::new(0) + }); + let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); + engine } fn proposer(&self) -> Address { let ref p = self.our_params; - p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() + p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } fn is_proposer(&self, address: &Address) -> bool { @@ -180,15 +150,21 @@ impl Tendermint { self.threshold()) } + fn to_step(&self, step: Step) { + let mut guard = self.s.try_write().unwrap(); + *guard = step; + } + fn to_propose(&self) { - self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); - let mut guard = self.our_params.s.write(); - *guard = Step::Propose; + trace!(target: "tendermint", "step: entering propose"); + println!("step: entering propose"); + self.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + self.to_step(Step::Propose); } fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_read().unwrap() { + match *self.s.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } @@ -198,20 +174,24 @@ impl Tendermint { } fn to_prevote(&self, proposal: H256) { - let mut guard = self.our_params.s.write(); + trace!(target: "tendermint", "step: entering prevote"); + println!("step: entering prevote"); // Proceed to the prevote step. - *guard = Step::Prevote(self.new_vote(proposal)); + self.to_step(Step::Prevote(self.new_vote(proposal))); } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_write().unwrap() { + match *self.s.try_write().unwrap() { Step::Prevote(ref mut vote) => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. - if vote.is_won() { self.to_precommit(vote.hash); } + if vote.is_won() { + //self.our_params.timeouts.precommit + self.to_precommit(vote.hash); + } Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) @@ -222,13 +202,14 @@ impl Tendermint { } fn to_precommit(&self, proposal: H256) { - let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(proposal), Vec::new()); + trace!(target: "tendermint", "step: entering precommit"); + println!("step: entering precommit"); + self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); } fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_write().unwrap() { + match *self.s.try_write().unwrap() { Step::Precommit(ref mut vote, ref mut seal) => { // Vote and accumulate seal if message is about the right block. if vote.hash == try!(message.as_val()) { @@ -245,13 +226,18 @@ impl Tendermint { } fn to_commit(&self, seal: Seal) { - let mut guard = self.our_params.s.write(); - *guard = Step::Commit(seal); + trace!(target: "tendermint", "step: entering commit"); + println!("step: entering commit"); + self.to_step(Step::Commit(seal)); } fn threshold(&self) -> usize { self.our_params.validator_n*2/3 } + + fn next_timeout(&self) -> u64 { + self.timeout.load(AtomicOrdering::Relaxed) as u64 + } } impl Engine for Tendermint { @@ -305,7 +291,7 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. - if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + if self.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), @@ -359,17 +345,55 @@ impl Engine for Tendermint { } } +/// Base timeout of each step in ms. +#[derive(Debug)] +struct DefaultTimeouts { + propose: Ms, + prevote: Ms, + precommit: Ms, + commit: Ms +} + +type Ms = usize; +type Seal = Vec; +type AtomicMs = AtomicUsize; + +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; + +impl IoHandler for TimerHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + println!("Timeout: {:?}", get_time()); + engine.to_propose(); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } + } + + fn message(&self, io: &IoContext, _net_message: &StepMessage) { + if let Some(engine) = self.engine.upgrade() { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } +} + #[cfg(test)] mod tests { use common::*; use std::thread::sleep; - use std::time::{Duration, Instant}; + use std::time::{Duration}; use block::*; use tests::helpers::*; - use service::{ClientService, ClientIoMessage}; - use devtools::RandomTempPath; - use client::ClientConfig; - use miner::Miner; use account_provider::AccountProvider; use spec::Spec; use engines::Engine; @@ -379,23 +403,6 @@ mod tests { /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } - fn new_test_client_service() -> ClientService { - let temp_path = RandomTempPath::new(); - let mut path = temp_path.as_path().to_owned(); - path.push("pruning"); - path.push("db"); - - let spec = get_test_spec(); - let service = ClientService::start( - ClientConfig::default(), - &spec, - &path, - &path, - Arc::new(Miner::with_spec(&spec)), - ); - service.unwrap() - } - fn propose_default(engine: &Arc, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); @@ -406,6 +413,10 @@ mod tests { engine.handle_message(proposer, H520::default(), propose_rlp) } + fn default_block() -> Vec { + vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] + } + #[test] fn has_valid_metadata() { let engine = new_test_tendermint().engine; @@ -474,36 +485,38 @@ mod tests { assert!(propose_default(&engine, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], - propose_default(&engine, proposer_addr).unwrap()); + assert_eq!(default_block(), propose_default(&engine, proposer_addr).unwrap()); assert!(propose_default(&engine, proposer_addr).is_err()); assert!(propose_default(&engine, not_proposer_addr).is_err()); } #[test] - fn prevote_step() { + fn proposer_switching() { let engine = new_test_tendermint().engine; - propose_default(&engine, Address::default()); + let tap = AccountProvider::transient_provider(); + + let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); + assert!(propose_default(&engine, not_proposer_addr).is_err()); + + sleep(Duration::from_secs(3)); + + assert_eq!(default_block(), propose_default(&engine, not_proposer_addr).unwrap()); } #[test] - fn handle_message() { - false; + fn prevote_step() { + let engine = new_test_tendermint().engine; + propose_default(&engine, Address::default()); } #[test] fn timeout_switching() { - let service = new_test_client_service(); let engine = new_test_tendermint().engine; let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); - service.register_io_handler(Arc::new(tender)); println!("Waiting for timeout"); - sleep(Duration::from_secs(10)); + sleep(Duration::from_secs(60)); - let message_channel = service.io().channel(); - message_channel.send(ClientIoMessage::ConsensusStep(1000)); - sleep(Duration::from_secs(5)); } } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index e378e63de13..e2e4772a4fa 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -46,8 +46,6 @@ pub enum ClientIoMessage { FeedStateChunk(H256, Bytes), /// Feed a block chunk to the snapshot service FeedBlockChunk(H256, Bytes), - /// Signal consensus step timeout. - ConsensusStep(u64), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -148,8 +146,6 @@ struct ClientIoHandler { const CLIENT_TICK_TIMER: TimerToken = 0; const CLIENT_TICK_MS: u64 = 5000; -/// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 1; impl IoHandler for ClientIoHandler { fn initialize(&self, io: &IoContext) { diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index d5f3570978f..96bb5354be2 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -137,7 +137,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::Tendermint(tendermint) => Arc::new(Tendermint::new(params, From::from(tendermint.params), builtins)), + ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins), } } From 45e6b4ac9dcda51322ebd3e9c14c623231719f60 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 6 Sep 2016 12:26:06 +0200 Subject: [PATCH 026/382] seal generation and verificatio --- ethcore/src/engines/tendermint.rs | 50 ++++++++++++++++++------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 1c9ffad4126..303b94275e7 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::Weak; use common::*; use rlp::{UntrustedRlp, View, encode}; +use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; @@ -278,15 +279,9 @@ impl Engine for Tendermint { /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - accounts.and_then(|ap| { - let header = block.header(); - if header.author() == &self.proposer() { - ap.sign(*header.author(), header.bare_hash()) - .ok() - .and_then(|signature| Some(vec![encode(&(&*signature as &[u8])).to_vec()])) - } else { - None - } + self.s.try_read().and_then(|s| match *s { + Step::Commit(ref seal) => Some(seal.clone()), + _ => None, }) } @@ -302,22 +297,35 @@ impl Engine for Tendermint { } } - 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() } - ))); + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + if header.seal().len() < self.threshold() { + Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.threshold(), found: header.seal().len() } + ))) + } else { + Ok(()) } - Ok(()) } - fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - Ok(()) + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let to_address = |b: &Vec| { + let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); + Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) + }; + let validator_set = self.our_params.validators.iter().cloned().collect(); + let seal_set = try!(header + .seal() + .iter() + .map(to_address) + .collect::, Error>>()); + if self.threshold() < seal_set.intersection(&validator_set).count() { + Ok(()) + } else { + try!(Err(BlockError::InvalidSeal)) + } } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> 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() }))); @@ -336,7 +344,7 @@ impl Engine for Tendermint { Ok(()) } - fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> { + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { try!(t.check_low_s()); Ok(()) } From ba21bafd7b0ad5484ef080ff9c5b0c1aaec99a0d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Sep 2016 16:25:42 +0200 Subject: [PATCH 027/382] tests and fixes --- ethcore/src/engines/tendermint.rs | 268 +++++++++++++++++++++++------- 1 file changed, 209 insertions(+), 59 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 303b94275e7..df8368052a8 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -31,7 +31,7 @@ use io::{IoContext, IoHandler, TimerToken, IoService}; use time::get_time; /// `Tendermint` params. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, @@ -47,17 +47,11 @@ impl Default for TendermintParams { fn default() -> Self { let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; let val_n = validators.len(); - let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: 0x0400.into(), validators: validators, validator_n: val_n, - timeouts: DefaultTimeouts { - propose: propose_timeout, - prevote: 3000, - precommit: 3000, - commit: 3000 - }, + timeouts: DefaultTimeouts::default() } } } @@ -69,24 +63,18 @@ enum Step { /// Precommit step storing the precommit vote and accumulating seal. Precommit(ProposeCollect, Seal), /// Commit step storing a complete valid seal. - Commit(Seal) + Commit(H256, Seal) } impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); - let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), validators: val, validator_n: val_n, - timeouts: DefaultTimeouts { - propose: propose_timeout, - prevote: 3000, - precommit: 3000, - commit: 3000 - }, + timeouts: DefaultTimeouts::default() } } } @@ -101,7 +89,7 @@ pub struct Tendermint { builtins: BTreeMap, timeout_service: IoService, /// Consensus round. - r: u64, + r: AtomicUsize, /// Consensus step. s: RwLock, /// Current step timeout in ms. @@ -124,7 +112,7 @@ impl Tendermint { our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), - r: 0, + r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) }); @@ -184,23 +172,27 @@ impl Tendermint { fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.s.try_write().unwrap() { + let hash = match *self.s.try_write().unwrap() { Step::Prevote(ref mut vote) => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. if vote.is_won() { - //self.our_params.timeouts.precommit - self.to_precommit(vote.hash); + // If won assign a hash used for precommit. + vote.hash.clone() + } else { + // Just propoagate the message if not won yet. + return Ok(message.as_raw().to_vec()); } - Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } }, _ => try!(Err(EngineError::WrongStep)), - } + }; + self.to_precommit(hash); + Ok(message.as_raw().to_vec()) } fn to_precommit(&self, proposal: H256) { @@ -217,7 +209,7 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. - if vote.is_won() { self.to_commit(seal.clone()); } + if vote.is_won() { self.to_commit(vote.hash.clone(), seal.clone()); } Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) @@ -227,10 +219,11 @@ impl Tendermint { } } - fn to_commit(&self, seal: Seal) { + /// Move to commit step, when valid block is known and being distributed. + pub fn to_commit(&self, block_hash: H256, seal: Vec) { trace!(target: "tendermint", "step: entering commit"); println!("step: entering commit"); - self.to_step(Step::Commit(seal)); + self.to_step(Step::Commit(block_hash, seal)); } fn threshold(&self) -> usize { @@ -278,16 +271,18 @@ impl Engine for Tendermint { /// Attempt to seal the block internally using all available signatures. /// /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { self.s.try_read().and_then(|s| match *s { - Step::Commit(ref seal) => Some(seal.clone()), + Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), _ => None, }) } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. - if self.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { + try!(Err(EngineError::WrongRound)) + } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), @@ -298,7 +293,7 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - if header.seal().len() < self.threshold() { + if header.seal().len() <= self.threshold() { Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.threshold(), found: header.seal().len() } ))) @@ -318,10 +313,10 @@ impl Engine for Tendermint { .iter() .map(to_address) .collect::, Error>>()); - if self.threshold() < seal_set.intersection(&validator_set).count() { - Ok(()) - } else { + if seal_set.intersection(&validator_set).count() <= self.threshold() { try!(Err(BlockError::InvalidSeal)) + } else { + Ok(()) } } @@ -355,7 +350,7 @@ impl Engine for Tendermint { } /// Base timeout of each step in ms. -#[derive(Debug)] +#[derive(Debug, Clone)] struct DefaultTimeouts { propose: Ms, prevote: Ms, @@ -363,6 +358,17 @@ struct DefaultTimeouts { commit: Ms } +impl Default for DefaultTimeouts { + fn default() -> Self { + DefaultTimeouts { + propose: 3000, + prevote: 3000, + precommit: 3000, + commit: 3000 + } + } +} + type Ms = usize; type Seal = Vec; type AtomicMs = AtomicUsize; @@ -381,7 +387,20 @@ impl IoHandler for TimerHandler { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { println!("Timeout: {:?}", get_time()); - engine.to_propose(); + // Can you release entering a clause? + let next_step = match *engine.s.try_read().unwrap() { + Step::Propose => Step::Propose, + Step::Prevote(_) => Step::Propose, + Step::Precommit(_, _) => Step::Propose, + Step::Commit(_, _) => { + engine.r.fetch_add(1, AtomicOrdering::Relaxed); + Step::Propose + }, + }; + match next_step { + Step::Propose => engine.to_propose(), + _ => (), + } io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") } } @@ -401,28 +420,53 @@ mod tests { use common::*; use std::thread::sleep; use std::time::{Duration}; - use rlp::{UntrustedRlp, RlpStream, Stream, View}; + use rlp::{UntrustedRlp, RlpStream, Stream, View, encode}; use block::*; use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use engines::Engine; + use engines::{Engine, EngineError}; use super::{Tendermint, TendermintParams}; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } - fn propose_default(engine: &Arc, proposer: Address) -> Result { + fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); - s.append(&0u8).append(&0u8).append(&header.bare_hash()); + s.append(&round).append(&0u8).append(&header.bare_hash()); let drain = s.out(); let propose_rlp = UntrustedRlp::new(&drain); engine.handle_message(proposer, H520::default(), propose_rlp) } + fn vote_default(engine: &Arc, round: u8, voter: Address) -> Result { + let mut s = RlpStream::new_list(3); + let header = Header::default(); + s.append(&round).append(&1u8).append(&header.bare_hash()); + let drain = s.out(); + let vote_rlp = UntrustedRlp::new(&drain); + + engine.handle_message(voter, H520::default(), vote_rlp) + } + + fn good_seal(header: &Header) -> Vec { + let tap = AccountProvider::transient_provider(); + + let mut seal = Vec::new(); + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let sig0 = tap.sign_with_password(v0, "0".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig0 as &[u8])).to_vec()); + + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + let sig1 = tap.sign_with_password(v1, "1".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal + } + fn default_block() -> Vec { vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] } @@ -451,7 +495,7 @@ mod tests { } #[test] - fn can_do_seal_verification_fail() { + fn verification_fails_on_short_seal() { let engine = new_test_tendermint().engine; let header: Header = Header::default(); @@ -465,21 +509,73 @@ mod tests { } #[test] - fn can_generate_seal() { + fn verification_fails_on_wrong_signatures() { + let engine = new_test_tendermint().engine; + let mut header = Header::default(); let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); + let mut seal = Vec::new(); + + let v1 = tap.insert_account("0".sha3(), "0").unwrap(); + let sig1 = tap.sign_with_password(v1, "0".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + + header.set_seal(seal.clone()); + + // Not enough signatures. + assert!(engine.verify_block_basic(&header, None).is_err()); + + let v2 = tap.insert_account("101".sha3(), "101").unwrap(); + let sig2 = tap.sign_with_password(v2, "101".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig2 as &[u8])).to_vec()); + + header.set_seal(seal); + + // Enough signatures. + assert!(engine.verify_block_basic(&header, None).is_ok()); + + let verify_result = engine.verify_block_unordered(&header, None); + + // But wrong signatures. + match verify_result { + Err(Error::Block(BlockError::InvalidSeal)) => (), + Err(_) => panic!("should be block seal-arity mismatch error (got {:?})", verify_result), + _ => panic!("Should be error, got Ok"), + } + } + + #[test] + fn seal_with_enough_signatures_is_ok() { + let engine = new_test_tendermint().engine; + let mut header = Header::default(); + + let seal = good_seal(&header); + header.set_seal(seal); + + // Enough signatures. + assert!(engine.verify_block_basic(&header, None).is_ok()); + + // And they are ok. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + } + + #[test] + fn can_generate_seal() { let spec = new_test_tendermint(); - let engine = &*spec.engine; + let ref engine = *spec.engine; + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + 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()).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 = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + + tender.to_commit(b.hash(), good_seal(&b.header())); + + let seal = tender.generate_seal(b.block(), None).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } @@ -487,18 +583,19 @@ mod tests { fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); + let r = 0; let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, not_validator_addr).is_err()); + assert!(propose_default(&engine, r, not_validator_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, r, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_block(), propose_default(&engine, proposer_addr).unwrap()); + assert_eq!(default_block(), propose_default(&engine, r, proposer_addr).unwrap()); - assert!(propose_default(&engine, proposer_addr).is_err()); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, r, proposer_addr).is_err()); + assert!(propose_default(&engine, r, not_proposer_addr).is_err()); } #[test] @@ -506,27 +603,80 @@ mod tests { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); + // Currently not a proposer. let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, 0, not_proposer_addr).is_err()); - sleep(Duration::from_secs(3)); + sleep(Duration::from_millis(TendermintParams::default().timeouts.propose as u64)); - assert_eq!(default_block(), propose_default(&engine, not_proposer_addr).unwrap()); + // Becomes proposer after timeout. + assert_eq!(default_block(), propose_default(&engine, 0, not_proposer_addr).unwrap()); } #[test] fn prevote_step() { let engine = new_test_tendermint().engine; - propose_default(&engine, Address::default()); + let tap = AccountProvider::transient_provider(); + let r = 0; + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + + // Propose. + assert!(propose_default(&engine, r, v1.clone()).is_ok()); + + // Prevote. + assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + + assert!(vote_default(&engine, r, v0).is_err()); + assert!(vote_default(&engine, r, v1).is_err()); + } + + #[test] + fn precommit_step() { + let engine = new_test_tendermint().engine; + let tap = AccountProvider::transient_provider(); + let r = 0; + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + + // Propose. + assert!(propose_default(&engine, r, v1.clone()).is_ok()); + + // Prevote. + assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + + assert!(vote_default(&engine, r, v0).is_err()); + assert!(vote_default(&engine, r, v1).is_err()); } #[test] fn timeout_switching() { - let engine = new_test_tendermint().engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); - + let tender = { + let engine = new_test_tendermint().engine; + Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) + }; + println!("Waiting for timeout"); - sleep(Duration::from_secs(60)); + sleep(Duration::from_secs(10)); + } + + #[test] + fn increments_round() { + let spec = new_test_tendermint(); + let ref engine = *spec.engine; + let def_params = TendermintParams::default(); + let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); + let header = Header::default(); + + tender.to_commit(header.bare_hash(), good_seal(&header)); + sleep(Duration::from_millis(def_params.timeouts.commit as u64)); + + match propose_default(&(tender as Arc), 0, Address::default()) { + Err(Error::Engine(EngineError::WrongRound)) => {}, + _ => panic!("Should be EngineError::WrongRound"), + } } } From 9fe62d975036b8eb660ee533f25c1d2f0aa84e4f Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Sep 2016 10:31:46 +0200 Subject: [PATCH 028/382] adjust default timeouts --- ethcore/src/engines/tendermint.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index df8368052a8..b88874b8458 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -361,10 +361,10 @@ struct DefaultTimeouts { impl Default for DefaultTimeouts { fn default() -> Self { DefaultTimeouts { - propose: 3000, - prevote: 3000, - precommit: 3000, - commit: 3000 + propose: 1000, + prevote: 1000, + precommit: 1000, + commit: 1000 } } } From 6cbb859bd2fb5dd4cb15efbb2036c5c1dce8e388 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 14:44:42 +0100 Subject: [PATCH 029/382] add tendermint message types and deserialization --- ethcore/src/engines/tendermint/message.rs | 81 ++++++++++++++ .../{tendermint.rs => tendermint/mod.rs} | 105 ++++-------------- ethcore/src/engines/tendermint/timeout.rs | 104 +++++++++++++++++ 3 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 ethcore/src/engines/tendermint/message.rs rename ethcore/src/engines/{tendermint.rs => tendermint/mod.rs} (88%) create mode 100644 ethcore/src/engines/tendermint/timeout.rs diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs new file mode 100644 index 00000000000..86691b47635 --- /dev/null +++ b/ethcore/src/engines/tendermint/message.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 . + +//! Tendermint message handling. + +use super::{Height, Round, BlockHash}; +use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; + +pub enum ConsensusMessage { + Prevote(Height, Round, BlockHash), + Precommit(Height, Round, BlockHash), + Commit(Height, BlockHash), +} + +/// (height, step, ...) +impl Decodable for ConsensusMessage { + fn decode(decoder: &D) -> Result where D: Decoder { + // Handle according to step. + let rlp = decoder.as_rlp(); + if decoder.as_raw().len() != try!(rlp.payload_info()).total() { + return Err(DecoderError::RlpIsTooBig); + } + let height = try!(rlp.val_at(0)); + Ok(match try!(rlp.val_at(1)) { + 0u8 => ConsensusMessage::Prevote( + height, + try!(rlp.val_at(2)), + try!(rlp.val_at(3)) + ), + 1 => ConsensusMessage::Precommit( + height, + try!(rlp.val_at(2)), + try!(rlp.val_at(3)) + ), + 2 => ConsensusMessage::Commit( + height, + try!(rlp.val_at(2))), + _ => return Err(DecoderError::Custom("Unknown step.")), + }) + } +} + +impl Encodable for ConsensusMessage { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + ConsensusMessage::Prevote(h, r, hash) => { + s.begin_list(4); + s.append(&h); + s.append(&0u8); + s.append(&r); + s.append(&hash); + }, + ConsensusMessage::Precommit(h, r, hash) => { + s.begin_list(4); + s.append(&h); + s.append(&1u8); + s.append(&r); + s.append(&hash); + }, + ConsensusMessage::Commit(h, hash) => { + s.begin_list(3); + s.append(&h); + s.append(&2u8); + s.append(&hash); + }, + } + } +} diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint/mod.rs similarity index 88% rename from ethcore/src/engines/tendermint.rs rename to ethcore/src/engines/tendermint/mod.rs index b88874b8458..4ebaca347c5 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -16,8 +16,10 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. +mod message; +mod timeout; + use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -use std::sync::Weak; use common::*; use rlp::{UntrustedRlp, View, encode}; use ethkey::{recover, public_to_address}; @@ -27,8 +29,9 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken, IoService}; -use time::get_time; +use io::IoService; +use self::message::ConsensusMessage; +use self::timeout::{TimerHandler, NextStep, DefaultTimeouts}; /// `Tendermint` params. #[derive(Debug, Clone)] @@ -63,9 +66,16 @@ enum Step { /// Precommit step storing the precommit vote and accumulating seal. Precommit(ProposeCollect, Seal), /// Commit step storing a complete valid seal. - Commit(H256, Seal) + Commit(BlockHash, Seal) } +pub type Height = usize; +pub type Round = usize; +pub type BlockHash = H256; + +pub type AtomicMs = AtomicUsize; +type Seal = Vec; + impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); @@ -79,15 +89,12 @@ impl From for TendermintParams { } } -#[derive(Clone)] -struct StepMessage; - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - timeout_service: IoService, + timeout_service: IoService, /// Consensus round. r: AtomicUsize, /// Consensus step. @@ -98,25 +105,21 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, } -struct TimerHandler { - engine: Weak, -} - impl Tendermint { /// Create a new instance of Tendermint engine pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { let engine = Arc::new( Tendermint { params: params, - timeout: AtomicUsize::new(our_params.timeouts.propose), + timeout: AtomicUsize::new(our_params.timeouts.propose()), our_params: our_params, builtins: builtins, - timeout_service: IoService::::start().expect("Error creating engine timeout service"), + timeout_service: IoService::::start().expect("Error creating engine timeout service"), r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) }); - let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + let handler = TimerHandler::new(Arc::downgrade(&engine)); engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); engine } @@ -134,7 +137,7 @@ impl Tendermint { self.our_params.validators.contains(address) } - fn new_vote(&self, proposal: H256) -> ProposeCollect { + fn new_vote(&self, proposal: BlockHash) -> ProposeCollect { ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()) @@ -163,7 +166,7 @@ impl Tendermint { Ok(message.as_raw().to_vec()) } - fn to_prevote(&self, proposal: H256) { + fn to_prevote(&self, proposal: BlockHash) { trace!(target: "tendermint", "step: entering prevote"); println!("step: entering prevote"); // Proceed to the prevote step. @@ -195,7 +198,7 @@ impl Tendermint { Ok(message.as_raw().to_vec()) } - fn to_precommit(&self, proposal: H256) { + fn to_precommit(&self, proposal: BlockHash) { trace!(target: "tendermint", "step: entering precommit"); println!("step: entering precommit"); self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); @@ -349,72 +352,6 @@ impl Engine for Tendermint { } } -/// Base timeout of each step in ms. -#[derive(Debug, Clone)] -struct DefaultTimeouts { - propose: Ms, - prevote: Ms, - precommit: Ms, - commit: Ms -} - -impl Default for DefaultTimeouts { - fn default() -> Self { - DefaultTimeouts { - propose: 1000, - prevote: 1000, - precommit: 1000, - commit: 1000 - } - } -} - -type Ms = usize; -type Seal = Vec; -type AtomicMs = AtomicUsize; - -/// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; - -impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { - if let Some(engine) = self.engine.upgrade() { - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); - } - } - - fn timeout(&self, io: &IoContext, timer: TimerToken) { - if timer == ENGINE_TIMEOUT_TOKEN { - if let Some(engine) = self.engine.upgrade() { - println!("Timeout: {:?}", get_time()); - // Can you release entering a clause? - let next_step = match *engine.s.try_read().unwrap() { - Step::Propose => Step::Propose, - Step::Prevote(_) => Step::Propose, - Step::Precommit(_, _) => Step::Propose, - Step::Commit(_, _) => { - engine.r.fetch_add(1, AtomicOrdering::Relaxed); - Step::Propose - }, - }; - match next_step { - Step::Propose => engine.to_propose(), - _ => (), - } - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") - } - } - } - - fn message(&self, io: &IoContext, _net_message: &StepMessage) { - if let Some(engine) = self.engine.upgrade() { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") - } - } -} - #[cfg(test)] mod tests { use common::*; diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs new file mode 100644 index 00000000000..979c08a39a9 --- /dev/null +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -0,0 +1,104 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use std::sync::atomic::{Ordering as AtomicOrdering}; +use std::sync::Weak; +use io::{IoContext, IoHandler, TimerToken}; +use super::{Tendermint, Step}; +use time::get_time; + +pub struct TimerHandler { + engine: Weak, +} + +impl TimerHandler { + pub fn new(engine: Weak) -> Self { + TimerHandler { engine: engine } + } +} + +/// Base timeout of each step in ms. +#[derive(Debug, Clone)] +pub struct DefaultTimeouts { + propose: Ms, + prevote: Ms, + precommit: Ms, + commit: Ms +} + +impl DefaultTimeouts { + pub fn propose(&self) -> usize { self.propose } +} + +impl Default for DefaultTimeouts { + fn default() -> Self { + DefaultTimeouts { + propose: 1000, + prevote: 1000, + precommit: 1000, + commit: 1000 + } + } +} + +type Ms = usize; + +#[derive(Clone)] +pub struct NextStep; + +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; + +impl IoHandler for TimerHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + println!("Timeout: {:?}", get_time()); + // Can you release entering a clause? + let next_step = match *engine.s.try_read().unwrap() { + Step::Propose => Step::Propose, + Step::Prevote(_) => Step::Propose, + Step::Precommit(_, _) => Step::Propose, + Step::Commit(_, _) => { + engine.r.fetch_add(1, AtomicOrdering::Relaxed); + Step::Propose + }, + }; + match next_step { + Step::Propose => engine.to_propose(), + _ => (), + } + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } + } + + fn message(&self, io: &IoContext, _net_message: &NextStep) { + if let Some(engine) = self.engine.upgrade() { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } +} From d0851462543b94c5b9cee5e61abd4302810fc8d4 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 15:32:49 +0100 Subject: [PATCH 030/382] separate params out --- ethcore/src/engines/tendermint/mod.rs | 44 ++--------------- ethcore/src/engines/tendermint/params.rs | 60 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 ethcore/src/engines/tendermint/params.rs diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4ebaca347c5..e6ee0f387e3 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -18,6 +18,7 @@ mod message; mod timeout; +mod params; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -28,36 +29,10 @@ use block::*; use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; -use ethjson; use io::IoService; use self::message::ConsensusMessage; -use self::timeout::{TimerHandler, NextStep, DefaultTimeouts}; - -/// `Tendermint` params. -#[derive(Debug, Clone)] -pub struct TendermintParams { - /// Gas limit divisor. - pub gas_limit_bound_divisor: U256, - /// List of validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, - /// Timeout durations for different steps. - timeouts: DefaultTimeouts, -} - -impl Default for TendermintParams { - fn default() -> Self { - let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; - let val_n = validators.len(); - TendermintParams { - gas_limit_bound_divisor: 0x0400.into(), - validators: validators, - validator_n: val_n, - timeouts: DefaultTimeouts::default() - } - } -} +use self::timeout::{TimerHandler, NextStep}; +use self::params::TendermintParams; #[derive(Debug)] enum Step { @@ -76,19 +51,6 @@ pub type BlockHash = H256; pub type AtomicMs = AtomicUsize; type Seal = Vec; -impl From for TendermintParams { - fn from(p: ethjson::spec::TendermintParams) -> Self { - let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); - let val_n = val.len(); - TendermintParams { - gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - validators: val, - validator_n: val_n, - timeouts: DefaultTimeouts::default() - } - } -} - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs new file mode 100644 index 00000000000..2a23cbb279d --- /dev/null +++ b/ethcore/src/engines/tendermint/params.rs @@ -0,0 +1,60 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use common::{Address, U256}; +use ethjson; +use super::timeout::DefaultTimeouts; + +/// `Tendermint` params. +#[derive(Debug, Clone)] +pub struct TendermintParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// List of validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Timeout durations for different steps. + pub timeouts: DefaultTimeouts, +} + +impl Default for TendermintParams { + fn default() -> Self { + let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = validators.len(); + TendermintParams { + gas_limit_bound_divisor: 0x0400.into(), + validators: validators, + validator_n: val_n, + timeouts: DefaultTimeouts::default() + } + } +} + +impl From for TendermintParams { + fn from(p: ethjson::spec::TendermintParams) -> Self { + let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val_n = val.len(); + TendermintParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + validators: val, + validator_n: val_n, + timeouts: DefaultTimeouts::default() + } + } +} From d59e9e816e84821057bb45bb671b5340f737446a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 16:57:52 +0100 Subject: [PATCH 031/382] fix tests compilation --- ethcore/src/engines/tendermint/message.rs | 1 + ethcore/src/engines/tendermint/mod.rs | 35 +++++++++++------------ ethcore/src/engines/tendermint/params.rs | 2 +- ethcore/src/engines/tendermint/timeout.rs | 16 ++++------- ethcore/src/spec/spec.rs | 6 ++++ 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 86691b47635..b14a07c1e21 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -19,6 +19,7 @@ use super::{Height, Round, BlockHash}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +#[derive(Debug)] pub enum ConsensusMessage { Prevote(Height, Round, BlockHash), Precommit(Height, Round, BlockHash), diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e6ee0f387e3..4a9f76ac756 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -73,7 +73,7 @@ impl Tendermint { let engine = Arc::new( Tendermint { params: params, - timeout: AtomicUsize::new(our_params.timeouts.propose()), + timeout: AtomicUsize::new(our_params.timeouts.propose), our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), @@ -244,6 +244,8 @@ impl Engine for Tendermint { } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { + let c: ConsensusMessage = try!(message.as_val()); + println!("{:?}", c); // Check if correct round. if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) @@ -325,11 +327,8 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError}; - use super::{Tendermint, TendermintParams}; - - /// Create a new test chain spec with `Tendermint` consensus engine. - /// Account "0".sha3() and "1".sha3() are a validators. - fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + use super::Tendermint; + use super::params::TendermintParams; fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); @@ -372,14 +371,14 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: 0.into(), @@ -395,7 +394,7 @@ mod tests { #[test] fn verification_fails_on_short_seal() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -409,7 +408,7 @@ mod tests { #[test] fn verification_fails_on_wrong_signatures() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); let tap = AccountProvider::transient_provider(); @@ -445,7 +444,7 @@ mod tests { #[test] fn seal_with_enough_signatures_is_ok() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); let seal = good_seal(&header); @@ -460,7 +459,7 @@ mod tests { #[test] fn can_generate_seal() { - let spec = new_test_tendermint(); + let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); @@ -480,7 +479,7 @@ mod tests { #[test] fn propose_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -499,7 +498,7 @@ mod tests { #[test] fn proposer_switching() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); // Currently not a proposer. @@ -514,7 +513,7 @@ mod tests { #[test] fn prevote_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -533,7 +532,7 @@ mod tests { #[test] fn precommit_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -553,7 +552,7 @@ mod tests { #[test] fn timeout_switching() { let tender = { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) }; @@ -563,7 +562,7 @@ mod tests { #[test] fn increments_round() { - let spec = new_test_tendermint(); + let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; let def_params = TendermintParams::default(); let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 2a23cbb279d..95c6be85d09 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +//! Tendermint specific parameters. use common::{Address, U256}; use ethjson; diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 979c08a39a9..47840d8b7ed 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +//! Tendermint timeout handling. use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; @@ -35,14 +35,10 @@ impl TimerHandler { /// Base timeout of each step in ms. #[derive(Debug, Clone)] pub struct DefaultTimeouts { - propose: Ms, - prevote: Ms, - precommit: Ms, - commit: Ms -} - -impl DefaultTimeouts { - pub fn propose(&self) -> usize { self.propose } + pub propose: Ms, + pub prevote: Ms, + pub precommit: Ms, + pub commit: Ms } impl Default for DefaultTimeouts { @@ -56,7 +52,7 @@ impl Default for DefaultTimeouts { } } -type Ms = usize; +pub type Ms = usize; #[derive(Clone)] pub struct NextStep; diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f02136e0c2d..c24849cba44 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -266,6 +266,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 Tendermint consensus which does internal sealing (not requiring work). + /// Account "0".sha3() and "1".sha3() are a validators. + pub fn new_test_tendermint() -> Self { + Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") + } } #[cfg(test)] From 8a51ae02aab37d2a85bc47595e36f973904c657a Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 30 Sep 2016 12:22:46 +0100 Subject: [PATCH 032/382] simplify seal --- ethcore/src/engines/tendermint/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4a9f76ac756..8d92d1828b0 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -204,7 +204,7 @@ impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { self.our_params.validator_n } + fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -260,12 +260,13 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - if header.seal().len() <= self.threshold() { + let seal_length = header.seal().len(); + if seal_length == self.seal_fields() { + Ok(()) + } else { Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.threshold(), found: header.seal().len() } + Mismatch { expected: self.seal_fields(), found: seal_length } ))) - } else { - Ok(()) } } From 76d7ec84bb7b6748a08dfe936130a82e5ad98850 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 30 Sep 2016 14:43:52 +0100 Subject: [PATCH 033/382] new block ordering engine method --- ethcore/src/engines/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ff8f13ebda5..c29a094d242 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -156,5 +156,8 @@ pub trait Engine : Sync + Send { /// Panics if `is_builtin(a)` is not true. fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); } + /// Check if new block should be chosen as the one in chain. + fn is_new_best_block(&self, parent_details: BlockDetails, best_header: HeaderView, new_header: HeaderView) -> bool; + // TODO: sealing stuff - though might want to leave this for later. } From 67c24dcb95cda6339666608f7f9949e80329784d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:29:35 +0100 Subject: [PATCH 034/382] use Engine to order blockchain --- ethcore/src/blockchain/blockchain.rs | 48 ++++++++++++++++------------ ethcore/src/client/client.rs | 6 ++-- ethcore/src/snapshot/service.rs | 5 +-- ethcore/src/snapshot/tests/blocks.rs | 16 ++++++---- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 8daf672b917..d599f957f0e 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -33,6 +33,7 @@ use blockchain::update::ExtrasUpdate; use blockchain::{CacheSize, ImportRoute, Config}; use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; +use engines::Engine; const LOG_BLOOMS_LEVELS: usize = 3; const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; @@ -182,6 +183,8 @@ pub struct BlockChain { pending_best_block: RwLock>, pending_block_hashes: RwLock>, pending_transaction_addresses: RwLock>>, + + engine: Arc, } impl BlockProvider for BlockChain { @@ -387,8 +390,8 @@ impl<'a> Iterator for AncestryIter<'a> { } impl BlockChain { - /// Create new instance of blockchain from given Genesis - pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { + /// Create new instance of blockchain from given Genesis and block picking rules of Engine. + pub fn new(config: Config, genesis: &[u8], db: Arc, engine: Arc) -> BlockChain { // 400 is the avarage size of the key let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400); @@ -411,6 +414,7 @@ impl BlockChain { pending_best_block: RwLock::new(None), pending_block_hashes: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), + engine: engine, }; // load best block @@ -799,13 +803,12 @@ impl BlockChain { let number = header.number(); let parent_hash = header.parent_hash(); let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash)); - let total_difficulty = parent_details.total_difficulty + header.difficulty(); - let is_new_best = total_difficulty > self.best_block_total_difficulty(); + let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), HeaderView::new(&self.best_block_header()), &parent_details, header); BlockInfo { hash: hash, number: number, - total_difficulty: total_difficulty, + total_difficulty: parent_details.total_difficulty + header.difficulty(), location: if is_new_best { // on new best block we need to make sure that all ancestors // are moved to "canon chain" @@ -1226,11 +1229,16 @@ mod tests { use views::BlockView; use transaction::{Transaction, Action}; use log_entry::{LogEntry, LocalizedLogEntry}; + use spec::Spec; fn new_db(path: &str) -> Arc { Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap()) } + fn new_chain(genesis: &[u8], db: Arc) -> BlockChain { + BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) + } + #[test] fn should_cache_best_block() { // given @@ -1241,7 +1249,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 0); // when @@ -1267,7 +1275,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.genesis_hash(), genesis_hash.clone()); assert_eq!(bc.best_block_hash(), genesis_hash.clone()); @@ -1298,7 +1306,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut block_hashes = vec![genesis_hash.clone()]; let mut batch = db.transaction(); @@ -1334,7 +1342,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); for b in &[&b1a, &b1b, &b2a, &b2b, &b3a, &b3b, &b4a, &b4b, &b5a, &b5b] { @@ -1396,7 +1404,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1484,7 +1492,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1546,7 +1554,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let ir1 = bc.insert_block(&mut batch, &b1, vec![]); @@ -1662,7 +1670,7 @@ mod tests { let temp = RandomTempPath::new(); { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), genesis_hash); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); @@ -1673,7 +1681,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), first_hash); } @@ -1728,7 +1736,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &b1, vec![]); db.write(batch).unwrap(); @@ -1788,7 +1796,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); insert_block(&db, &bc, &b1, vec![Receipt { state_root: H256::default(), gas_used: 10_000.into(), @@ -1892,7 +1900,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5); let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 5); @@ -1949,7 +1957,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap(); let mut batch =db.transaction(); @@ -1968,7 +1976,7 @@ mod tests { // re-loading the blockchain should load the correct best block. let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 5); } @@ -1985,7 +1993,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 541bd7872e1..8fc13cfb78a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -23,7 +23,7 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock}; -use util::journaldb::{self, JournalDB}; +use util::journaldb; use util::{U256, H256, H520, Address, H2048, Uint}; use util::sha3::*; use util::TrieFactory; @@ -170,7 +170,7 @@ impl Client { let gb = spec.genesis_block(); let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); - let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); + let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); @@ -689,7 +689,7 @@ impl snapshot::DatabaseRestore for Client { try!(db.restore(new_db)); *state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE)); - *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); + *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone(), self.engine.clone())); *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); Ok(()) } diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 5243a47929f..30361227305 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -32,6 +32,7 @@ use engines::Engine; use error::Error; use ids::BlockID; use service::ClientIoMessage; +use spec::Spec; use io::IoChannel; @@ -97,7 +98,7 @@ impl Restoration { let raw_db = Arc::new(try!(Database::open(params.db_config, &*params.db_path.to_string_lossy()) .map_err(UtilError::SimpleString))); - let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); + let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone(), Spec::new_null().engine); let blocks = try!(BlockRebuilder::new(chain, manifest.block_number)); let root = manifest.state_root.clone(); @@ -629,4 +630,4 @@ mod tests { service.restore_state_chunk(Default::default(), vec![]); service.restore_block_chunk(Default::default(), vec![]); } -} \ No newline at end of file +} diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 6c4344b6e2b..3c8744bcce4 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -28,6 +28,8 @@ use util::kvdb::{Database, DatabaseConfig}; use std::sync::Arc; +use spec::Spec; + fn chunk_and_restore(amount: u64) { let mut canon_chain = ChainGenerator::default(); let mut finalizer = BlockFinalizer::default(); @@ -39,8 +41,10 @@ fn chunk_and_restore(amount: u64) { let mut snapshot_path = new_path.as_path().to_owned(); snapshot_path.push("SNAP"); + let new_chain = |db| BlockChain::new(Default::default(), &genesis, db, Spec::new_null().engine); + let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); - let bc = BlockChain::new(Default::default(), &genesis, old_db.clone()); + let bc = new_chain(old_db.clone()); // build the blockchain. let mut batch = old_db.transaction(); @@ -67,9 +71,9 @@ fn chunk_and_restore(amount: u64) { }).unwrap(); // restore it. - let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); - let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); - let mut rebuilder = BlockRebuilder::new(new_chain, amount).unwrap(); + let restored_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); + let restored_chain = new_chain(restored_db.clone()); + let mut rebuilder = BlockRebuilder::new(restored_chain, amount).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let engine = ::engines::NullEngine::new(Default::default(), Default::default()); for chunk_hash in &reader.manifest().block_hashes { @@ -81,8 +85,8 @@ fn chunk_and_restore(amount: u64) { rebuilder.glue_chunks(); // and test it. - let new_chain = BlockChain::new(Default::default(), &genesis, new_db); - assert_eq!(new_chain.best_block_hash(), best_hash); + let restored_chain = new_chain(restored_db); + assert_eq!(restored_chain.best_block_hash(), best_hash); } #[test] From a03db2ff29c7e36d4a60e6dec5be5296b59eda7f Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:30:44 +0100 Subject: [PATCH 035/382] add is_new_best method to engines --- ethcore/src/engines/mod.rs | 6 +++++- ethcore/src/ethereum/ethash.rs | 12 ++++++++++++ ethcore/src/tests/helpers.rs | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c29a094d242..0be50738747 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -36,6 +36,8 @@ use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; use evm::Schedule; +use ethereum::ethash; +use blockchain::extras::BlockDetails; /// Voting errors. #[derive(Debug)] @@ -157,7 +159,9 @@ pub trait Engine : Sync + Send { fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); } /// Check if new block should be chosen as the one in chain. - fn is_new_best_block(&self, parent_details: BlockDetails, best_header: HeaderView, new_header: HeaderView) -> bool; + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) + } // TODO: sealing stuff - though might want to leave this for later. } diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 982698a5069..a1dfd0a4034 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -22,6 +22,7 @@ use engines::Engine; use evm::Schedule; use ethjson; use rlp::{self, UntrustedRlp, View}; +use blockchain::extras::BlockDetails; /// Ethash params. #[derive(Debug, PartialEq)] @@ -273,6 +274,11 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + /// Check if new block should be chosen as the one in chain. + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + is_new_best_block(best_total_difficulty, parent_details, new_header) + } } #[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self @@ -347,6 +353,12 @@ impl Ethash { } } +/// Check if a new block should replace the best blockchain block. +pub fn is_new_best_block(best_total_difficulty: U256, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + parent_details.total_difficulty + new_header.difficulty() > best_total_difficulty +} + + impl Header { /// Get the none field of the header. pub fn nonce(&self) -> H64 { diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 6504ef8a9e3..11cd152dc0b 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -277,7 +277,7 @@ fn new_db(path: &str) -> Arc { pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); for block_order in 1..block_number { @@ -295,7 +295,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); @@ -314,7 +314,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); GuardedTempResult:: { _temp: temp, From 64d7bcbd0c325ad8502162f98a54ceee68de70bb Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:31:31 +0100 Subject: [PATCH 036/382] validators -> authorities --- ethcore/res/tendermint.json | 2 +- ethcore/src/spec/spec.rs | 2 +- json/src/spec/tendermint.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 8aa8f24f77b..2f40d707b27 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,7 +4,7 @@ "Tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "validators" : [ + "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" ] diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index d86f7ec46d4..5b861c25ee0 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -279,7 +279,7 @@ impl Spec { } /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). - /// Account "0".sha3() and "1".sha3() are a validators. + /// Account "0".sha3() and "1".sha3() are a authorities. pub fn new_test_tendermint() -> Self { Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") } diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 97c30fbb21d..3d1a5a06d7b 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -26,7 +26,7 @@ pub struct TendermintParams { #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, /// Valid authorities - pub validators: Vec
, + pub authorities: Vec
, } /// Tendermint engine deserialization. @@ -46,7 +46,7 @@ mod tests { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", - "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] } }"#; From cb2c9938a1467ee3b60efac4f47f75275f7aceca Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:32:15 +0100 Subject: [PATCH 037/382] keep author as validator --- ethcore/src/engines/tendermint/params.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 95c6be85d09..cf7a90931f1 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -25,22 +25,22 @@ use super::timeout::DefaultTimeouts; pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// List of validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, + /// List of authorities. + pub authorities: Vec
, + /// Number of authorities. + pub authority_n: usize, /// Timeout durations for different steps. pub timeouts: DefaultTimeouts, } impl Default for TendermintParams { fn default() -> Self { - let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; - let val_n = validators.len(); + let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = authorities.len(); TendermintParams { gas_limit_bound_divisor: 0x0400.into(), - validators: validators, - validator_n: val_n, + authorities: authorities, + authority_n: val_n, timeouts: DefaultTimeouts::default() } } @@ -48,12 +48,12 @@ impl Default for TendermintParams { impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { - let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect(); let val_n = val.len(); TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - validators: val, - validator_n: val_n, + authorities: val, + authority_n: val_n, timeouts: DefaultTimeouts::default() } } From 096b71feb20cd2731a59cc5193143ebb71ce057e Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:33:07 +0100 Subject: [PATCH 038/382] add Vote generation --- ethcore/src/basic_types.rs | 4 +- ethcore/src/engines/tendermint/mod.rs | 115 +++++++++++++++++-------- ethcore/src/engines/tendermint/vote.rs | 63 ++++++++++++++ ethcore/src/header.rs | 9 +- 4 files changed, 149 insertions(+), 42 deletions(-) create mode 100644 ethcore/src/engines/tendermint/vote.rs diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5f6515c0da4..e2a705dd7a9 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -25,10 +25,12 @@ pub type LogBloom = H2048; pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); #[cfg_attr(feature="dev", allow(enum_variant_names))] -/// Semantic boolean for when a seal/signature is included. +/// Enum for when a seal/signature is included. pub enum Seal { /// The seal/signature is included. With, /// The seal/signature is not included. Without, + /// First N fields of seal are included. + WithSome(usize), } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8d92d1828b0..2af0ab78186 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -19,6 +19,7 @@ mod message; mod timeout; mod params; +mod vote; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -28,20 +29,22 @@ use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; +use blockchain::extras::BlockDetails; use evm::Schedule; use io::IoService; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; +use self::vote::Vote; #[derive(Debug)] enum Step { Propose, Prevote(ProposeCollect), /// Precommit step storing the precommit vote and accumulating seal. - Precommit(ProposeCollect, Seal), + Precommit(ProposeCollect, Signatures), /// Commit step storing a complete valid seal. - Commit(BlockHash, Seal) + Commit(BlockHash, Signatures) } pub type Height = usize; @@ -49,7 +52,7 @@ pub type Round = usize; pub type BlockHash = H256; pub type AtomicMs = AtomicUsize; -type Seal = Vec; +type Signatures = Vec; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { @@ -57,6 +60,8 @@ pub struct Tendermint { our_params: TendermintParams, builtins: BTreeMap, timeout_service: IoService, + /// Address to be used as authority. + authority: RwLock
, /// Consensus round. r: AtomicUsize, /// Consensus step. @@ -77,6 +82,7 @@ impl Tendermint { our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), + authority: RwLock::new(Address::default()), r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) @@ -86,22 +92,26 @@ impl Tendermint { engine } - fn proposer(&self) -> Address { + fn is_proposer(&self, address: &Address) -> bool { + self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + } + + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; - p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() + p.authorities.get(proposer_nonce%p.authority_n).unwrap() } - fn is_proposer(&self, address: &Address) -> bool { - self.proposer() == *address + fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { + self.nonce_proposer(proposer_nonce) == address } - fn is_validator(&self, address: &Address) -> bool { - self.our_params.validators.contains(address) + fn is_authority(&self, address: &Address) -> bool { + self.our_params.authorities.contains(address) } fn new_vote(&self, proposal: BlockHash) -> ProposeCollect { ProposeCollect::new(proposal, - self.our_params.validators.iter().cloned().collect(), + self.our_params.authorities.iter().cloned().collect(), self.threshold()) } @@ -192,7 +202,7 @@ impl Tendermint { } fn threshold(&self) -> usize { - self.our_params.validator_n*2/3 + self.our_params.authority_n*2/3 } fn next_timeout(&self) -> u64 { @@ -203,8 +213,8 @@ impl Tendermint { impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { 2 } + /// (consensus round, proposal signature, authority signatures) + fn seal_fields(&self) -> usize { 3 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -229,34 +239,62 @@ impl Engine for Tendermint { }); } - /// Apply the block reward on finalisation of the block. + /// Get the address to be used as authority. + fn on_new_block(&self, block: &mut ExecutedBlock) { + if let Some(mut authority) = self.authority.try_write() { + *authority = *block.header().author() + } + } + + /// Set author to proposer. /// 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 using all available signatures. /// /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { - self.s.try_read().and_then(|s| match *s { - Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), - _ => None, - }) + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) { + let header = block.header(); + let author = header.author(); + match *step { + Step::Commit(hash, ref seal) if hash == header.bare_hash() => + // Commit the block using a complete signature set. + return Some(seal.clone()), + Step::Propose if self.is_proposer(header.author()) => + // Seal block with propose signature. + if let Some(proposal) = Vote::propose(header, &ap) { + return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()]) + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + }, + _ => {}, + } + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + } + None } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - let c: ConsensusMessage = try!(message.as_val()); - println!("{:?}", c); + let message: ConsensusMessage = try!(message.as_val()); + try!(Err(EngineError::UnknownStep)) + //match message { + // ConsensusMessage::Prevote + //} + + // Check if correct round. - if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { - try!(Err(EngineError::WrongRound)) - } + //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { + // try!(Err(EngineError::WrongRound)) + //} // Handle according to step. - match try!(message.val_at(1)) { - 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), - 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), - 2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), - _ => try!(Err(EngineError::UnknownStep)), - } +// match try!(message.val_at(1)) { +// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), +// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))), +// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), +// _ => try!(Err(EngineError::UnknownStep)), +// } } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -270,18 +308,19 @@ impl Engine for Tendermint { } } + /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let to_address = |b: &Vec| { let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) }; - let validator_set = self.our_params.validators.iter().cloned().collect(); + let authority_set = self.our_params.authorities.iter().cloned().collect(); let seal_set = try!(header .seal() .iter() .map(to_address) .collect::, Error>>()); - if seal_set.intersection(&validator_set).count() <= self.threshold() { + if seal_set.intersection(&authority_set).count() <= self.threshold() { try!(Err(BlockError::InvalidSeal)) } else { Ok(()) @@ -315,6 +354,10 @@ impl Engine for Tendermint { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len() + } } #[cfg(test)] @@ -465,9 +508,9 @@ mod tests { let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); let genesis_header = spec.genesis_header(); - let mut db_result = get_temp_journal_db(); + let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(db.as_hashdb_mut()).unwrap(); + 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, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); @@ -484,8 +527,8 @@ mod tests { let tap = AccountProvider::transient_provider(); let r = 0; - let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, r, not_validator_addr).is_err()); + let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); + assert!(propose_default(&engine, r, not_authority_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); assert!(propose_default(&engine, r, not_proposer_addr).is_err()); diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs new file mode 100644 index 00000000000..a11583201a8 --- /dev/null +++ b/ethcore/src/engines/tendermint/vote.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 . + +//! Tendermint block seal. + +use common::{H256, Address, H520, Header}; +use util::Hashable; +use account_provider::AccountProvider; +use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +use basic_types::Seal; + +#[derive(Debug)] +pub struct Vote { + signature: H520 +} + +fn message(header: &Header) -> H256 { + header.rlp(Seal::WithSome(1)).sha3() +} + +impl Vote { + fn new(signature: H520) -> Vote { Vote { signature: signature }} + + /// Try to use the author address to create a vote. + pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { + accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new) + } + + /// Use any unlocked validator account to create a vote. + pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { + accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new) + } +} + +impl Decodable for Vote { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + if decoder.as_raw().len() != try!(rlp.payload_info()).total() { + return Err(DecoderError::RlpIsTooBig); + } + rlp.as_val().map(Self::new) + } +} + +impl Encodable for Vote { + fn rlp_append(&self, s: &mut RlpStream) { + let Vote { ref signature } = *self; + s.append(signature); + } +} diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 7d86cfd6109..794b9230b4d 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -226,7 +226,8 @@ impl Header { // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 }); + let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 }; + s.begin_list(13 + seal_n); s.append(&self.parent_hash); s.append(&self.uncles_hash); s.append(&self.author); @@ -240,10 +241,8 @@ impl Header { s.append(&self.gas_used); s.append(&self.timestamp); s.append(&self.extra_data); - if let Seal::With = with_seal { - for b in &self.seal { - s.append_raw(b, 1); - } + for b in self.seal.iter().take(seal_n) { + s.append_raw(b, 1); } } From e343153f06b12ddf626f9ff3a592168817705211 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 11 Oct 2016 18:37:31 +0100 Subject: [PATCH 039/382] mixed merge and changes... --- .gitlab-ci.yml | 23 +- .travis.yml | 1 + Cargo.lock | 34 +- appveyor.yml | 2 +- dapps/src/api/api.rs | 5 +- dapps/src/apps/fetcher.rs | 3 +- ethcore/build.rs | 6 +- ethcore/src/client/client.rs | 125 ++++--- ethcore/src/client/mod.rs | 11 +- ethcore/src/client/test_client.rs | 13 +- ethcore/src/engines/tendermint/mod.rs | 18 +- ethcore/src/engines/tendermint/vote.rs | 23 +- ethcore/src/evm/interpreter/mod.rs | 8 +- ethcore/src/evm/interpreter/shared_cache.rs | 2 +- ethcore/src/executive.rs | 28 +- ethcore/src/miner/miner.rs | 203 ++++++++--- ethcore/src/miner/mod.rs | 13 +- ethcore/src/miner/transaction_queue.rs | 132 ++++++- ethcore/src/snapshot/mod.rs | 8 +- ethcore/src/state/account.rs | 63 +--- ethcore/src/state/mod.rs | 290 +++++++++------ ethcore/src/state_db.rs | 372 ++++++++++++++++---- ethcore/src/tests/client.rs | 6 +- ethcore/src/tests/mod.rs | 1 + ethcore/src/tests/rpc.rs | 3 +- ethcore/src/trace/db.rs | 27 +- ethcore/src/types/filter.rs | 2 +- ethstore/src/dir/disk.rs | 17 +- ipc/codegen/src/lib.rs | 43 ++- ipc/hypervisor/Cargo.toml | 1 + ipc/hypervisor/src/lib.rs | 28 +- ipc/hypervisor/src/service.rs.in | 5 +- parity/cli/config.full.toml | 3 +- parity/cli/config.toml | 1 + parity/cli/mod.rs | 9 +- parity/cli/usage.txt | 7 +- parity/configuration.rs | 5 +- parity/helpers.rs | 30 +- parity/main.rs | 3 + parity/modules.rs | 5 +- parity/params.rs | 2 +- parity/sync.rs | 5 +- rpc/src/v1/helpers/errors.rs | 23 +- rpc/src/v1/impls/eth.rs | 20 +- rpc/src/v1/impls/eth_filter.rs | 12 +- rpc/src/v1/tests/eth.rs | 3 +- rpc/src/v1/tests/helpers/miner_service.rs | 13 +- rpc/src/v1/types/filter.rs | 41 ++- sync/build.rs | 2 +- sync/src/blocks.rs | 6 +- sync/src/chain.rs | 217 ++++++------ sync/src/lib.rs | 6 + sync/src/tests/chain.rs | 24 +- sync/src/tests/helpers.rs | 39 +- util/bigint/Cargo.toml | 2 +- util/io/src/lib.rs | 2 + util/io/src/worker.rs | 13 +- util/network/src/host.rs | 3 +- 58 files changed, 1394 insertions(+), 618 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 32e8d953f10..36bb0650a36 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,8 +3,9 @@ stages: - test variables: GIT_DEPTH: "3" - SIMPLECOV: "true" + SIMPLECOV: "true" RUST_BACKTRACE: "1" + RUSTFLAGS: "-D warnings" cache: key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" untracked: true @@ -20,7 +21,7 @@ linux-stable: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/checksum --body checksum @@ -43,7 +44,7 @@ linux-stable-14.04: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/checksum --body checksum @@ -106,7 +107,7 @@ linux-centos: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/checksum --body checksum @@ -134,7 +135,7 @@ linux-armv7: - cargo build --target armv7-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity - md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/checksum --body checksum @@ -163,7 +164,7 @@ linux-arm: - cargo build --target arm-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity - md5sum target/arm-unknown-linux-gnueabihf/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/checksum --body checksum @@ -191,8 +192,8 @@ linux-armv6: - cat .cargo/config - cargo build --target arm-unknown-linux-gnueabi --release --verbose - arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity - - md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/checksum --body checksum @@ -221,7 +222,7 @@ linux-aarch64: - cargo build --target aarch64-unknown-linux-gnu --release --verbose - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity - md5sum target/aarch64-unknown-linux-gnu/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/checksum --body checksum @@ -243,7 +244,7 @@ darwin: script: - cargo build --release --verbose - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/checksum --body checksum @@ -264,7 +265,7 @@ windows: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - - set RUSTFLAGS=-Zorbit=off + - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off - rustup default stable-x86_64-pc-windows-msvc - cargo build --release --verbose - cmd md5sum target\release\parity >> checksum diff --git a/.travis.yml b/.travis.yml index 6428ccecf93..d9cda5715b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ env: - RUN_COVERAGE="false" - RUN_DOCS="false" - TEST_OPTIONS="" + - RUSTFLAGS="-D warnings" # GH_TOKEN for documentation - secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw= - KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov" diff --git a/Cargo.lock b/Cargo.lock index bb91080c237..a092325ead1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,7 +240,7 @@ version = "0.5.4" source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c" dependencies = [ "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "ethcore-bigint" -version = "0.1.0" +version = "0.1.1" dependencies = [ "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -399,6 +399,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -538,7 +539,7 @@ dependencies = [ "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -567,7 +568,7 @@ name = "ethcrypto" version = "0.1.0" dependencies = [ "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "ethkey 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -590,7 +591,7 @@ version = "0.2.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -674,8 +675,11 @@ dependencies = [ [[package]] name = "gcc" -version = "0.3.28" +version = "0.3.35" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "glob" @@ -936,7 +940,7 @@ name = "miniz-sys" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1022,7 +1026,7 @@ name = "nanomsg-sys" version = "0.5.0" source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1394,7 +1398,7 @@ name = "rlp" version = "0.1.0" dependencies = [ "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1402,7 +1406,7 @@ dependencies = [ [[package]] name = "rocksdb" version = "0.4.5" -source = "git+https://github.com/ethcore/rust-rocksdb#485dd747a2c9a9f910fc8ac696fc9edf5fa22aa3" +source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" dependencies = [ "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)", @@ -1411,9 +1415,9 @@ dependencies = [ [[package]] name = "rocksdb-sys" version = "0.3.0" -source = "git+https://github.com/ethcore/rust-rocksdb#485dd747a2c9a9f910fc8ac696fc9edf5fa22aa3" +source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1444,7 +1448,7 @@ name = "rust-crypto" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1537,7 +1541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "sha3" version = "0.1.0" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1926,7 +1930,7 @@ dependencies = [ "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" -"checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" +"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" diff --git a/appveyor.yml b/appveyor.yml index 3f6bb85ef17..75a2da7cb53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ environment: certpass: secure: 0BgXJqxq9Ei34/hZ7121FQ== keyfile: C:\users\appveyor\Certificates.p12 - RUSTFLAGS: -Zorbit=off + RUSTFLAGS: -Zorbit=off -D warnings branches: only: diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 9a8dfef959d..80c5e09dee2 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -92,11 +92,14 @@ impl server::Handler for RestApiRouter { } let url = url.expect("Check for None early-exists above; qed"); - let path = self.path.take().expect("on_request called only once, and path is always defined in new; qed"); + let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed"); let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed"); let endpoint = url.path.get(1).map(|v| v.as_str()); let hash = url.path.get(2).map(|v| v.as_str()); + // at this point path.app_id contains 'api', adjust it to the hash properly, otherwise + // we will try and retrieve 'api' as the hash when doing the /api/content route + if let Some(hash) = hash.clone() { path.app_id = hash.to_owned() } let handler = endpoint.and_then(|v| match v { "apps" => Some(as_json(&self.api.list_apps())), diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 8702e4706b0..2e132885830 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -122,7 +122,7 @@ impl ContentFetcher { }, // We need to start fetching app None => { - trace!(target: "dapps", "Content unavailable. Fetching..."); + trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id); let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true."); let content = self.resolver.resolve(content_hex); @@ -415,4 +415,3 @@ mod tests { assert_eq!(fetcher.contains("test3"), false); } } - diff --git a/ethcore/build.rs b/ethcore/build.rs index b839557084e..5a3a3f0ba2b 100644 --- a/ethcore/build.rs +++ b/ethcore/build.rs @@ -18,7 +18,7 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/snapshot/snapshot_service_trait.rs").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/client/traits.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/snapshot/snapshot_service_trait.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/client/chain_notify.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ece20dd77e1..ba00284f192 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -149,13 +149,6 @@ pub struct Client { /// assume finality of a given candidate. pub const HISTORY: u64 = 1200; -/// Append a path element to the given path and return the string. -pub fn append_path

(path: P, item: &str) -> String where P: AsRef { - let mut p = path.as_ref().to_path_buf(); - p.push(item); - p.to_str().unwrap().to_owned() -} - impl Client { /// Create a new client with given spec and DB path and custom verifier. pub fn new( @@ -169,7 +162,7 @@ impl Client { let path = path.to_path_buf(); let gb = spec.genesis_block(); - let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); + let db = Arc::new(try!(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database))); let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); @@ -298,31 +291,27 @@ impl Client { // Check if Parent is in chain let chain_has_parent = chain.block_header(header.parent_hash()); - if let None = chain_has_parent { - warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); - return Err(()); - }; - - // Enact Verified Block - let parent = chain_has_parent.unwrap(); - let last_hashes = self.build_last_hashes(header.parent_hash().clone()); - let is_canon = header.parent_hash() == &chain.best_block_hash(); - let db = if is_canon { self.state_db.lock().boxed_clone_canon() } else { self.state_db.lock().boxed_clone() }; - - let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); - if let Err(e) = enact_result { - warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); - }; + if let Some(parent) = chain_has_parent { + // Enact Verified Block + let last_hashes = self.build_last_hashes(header.parent_hash().clone()); + let db = self.state_db.lock().boxed_clone_canon(&header.parent_hash()); + + let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); + let locked_block = try!(enact_result.map_err(|e| { + warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + })); + + // Final Verification + if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { + warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + return Err(()); + } - // Final Verification - let locked_block = enact_result.unwrap(); - if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { - warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); + Ok(locked_block) + } else { + warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); + Err(()) } - - Ok(locked_block) } fn calculate_enacted_retracted(&self, import_results: &[ImportRoute]) -> (Vec, Vec) { @@ -366,23 +355,21 @@ impl Client { for block in blocks { let header = &block.header; - if invalid_blocks.contains(header.parent_hash()) { - invalid_blocks.insert(header.hash()); - continue; - } - let closed_block = self.check_and_close_block(&block); - if let Err(_) = closed_block { + let is_invalid = invalid_blocks.contains(header.parent_hash()); + if is_invalid { invalid_blocks.insert(header.hash()); continue; } + if let Ok(closed_block) = self.check_and_close_block(&block) { + imported_blocks.push(header.hash()); - let closed_block = closed_block.unwrap(); - imported_blocks.push(header.hash()); + let route = self.commit_block(closed_block, &header.hash(), &block.bytes); + import_results.push(route); - let route = self.commit_block(closed_block, &header.hash(), &block.bytes); - import_results.push(route); - - self.report.write().accrue_block(&block); + self.report.write().accrue_block(&block); + } else { + invalid_blocks.insert(header.hash()); + } } let imported = imported_blocks.len(); @@ -432,7 +419,7 @@ impl Client { // Are we committing an era? let ancient = if number >= HISTORY { let n = number - HISTORY; - Some((n, chain.block_hash(n).unwrap())) + Some((n, chain.block_hash(n).expect("only verified blocks can be commited; verified block has hash; qed"))) } else { None }; @@ -461,6 +448,8 @@ impl Client { enacted: route.enacted.clone(), retracted: route.retracted.len() }); + let is_canon = route.enacted.last().map_or(false, |h| h == hash); + state.sync_cache(&route.enacted, &route.retracted, is_canon); // Final commit to the DB self.db.read().write_buffered(batch); chain.commit(); @@ -535,9 +524,11 @@ impl Client { /// Get a copy of the best block's state. pub fn state(&self) -> State { + let header = self.best_block_header(); + let header = HeaderView::new(&header); State::from_existing( - self.state_db.lock().boxed_clone(), - HeaderView::new(&self.best_block_header()).state_root(), + self.state_db.lock().boxed_clone_canon(&header.hash()), + header.state_root(), self.engine.account_start_nonce(), self.factories.clone()) .expect("State root of best block header always valid.") @@ -899,8 +890,10 @@ impl BlockChainClient for Client { BodyView::new(&block).localized_transaction_at(&address.block_hash, block_number, address.index) }); - match (t, chain.transaction_receipt(&address)) { - (Some(tx), Some(receipt)) => { + let tx_and_sender = t.and_then(|tx| tx.sender().ok().map(|sender| (tx, sender))); + + match (tx_and_sender, chain.transaction_receipt(&address)) { + (Some((tx, sender)), Some(receipt)) => { let block_hash = tx.block_hash.clone(); let block_number = tx.block_number.clone(); let transaction_hash = tx.hash(); @@ -922,7 +915,7 @@ impl BlockChainClient for Client { gas_used: receipt.gas_used - prior_gas_used, contract_address: match tx.action { Action::Call(_) => None, - Action::Create => Some(contract_address(&tx.sender().unwrap(), &tx.nonce)) + Action::Create => Some(contract_address(&sender, &tx.nonce)) }, logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry { entry: log, @@ -1023,17 +1016,18 @@ impl BlockChainClient for Client { let start = self.block_number(filter.range.start); let end = self.block_number(filter.range.end); - if start.is_some() && end.is_some() { - let filter = trace::Filter { - range: start.unwrap() as usize..end.unwrap() as usize, - from_address: From::from(filter.from_address), - to_address: From::from(filter.to_address), - }; + match (start, end) { + (Some(s), Some(e)) => { + let filter = trace::Filter { + range: s as usize..e as usize, + from_address: From::from(filter.from_address), + to_address: From::from(filter.to_address), + }; - let traces = self.tracedb.read().filter(&filter); - Some(traces) - } else { - None + let traces = self.tracedb.read().filter(&filter); + Some(traces) + }, + _ => None, } } @@ -1080,7 +1074,7 @@ impl BlockChainClient for Client { } fn pending_transactions(&self) -> Vec { - self.miner.pending_transactions() + self.miner.pending_transactions(self.chain.read().best_block_number()) } // TODO: Make it an actual queue, return errors. @@ -1109,7 +1103,7 @@ impl MiningBlockChainClient for Client { engine, self.factories.clone(), false, // TODO: this will need to be parameterised once we want to do immediate mining insertion. - self.state_db.lock().boxed_clone(), + self.state_db.lock().boxed_clone_canon(&h), &chain.block_header(&h).expect("h is best block hash: so its header must exist: qed"), self.build_last_hashes(h.clone()), author, @@ -1120,11 +1114,15 @@ impl MiningBlockChainClient for Client { // Add uncles chain .find_uncle_headers(&h, engine.maximum_uncle_age()) - .unwrap() + .unwrap_or_else(Vec::new) .into_iter() .take(engine.maximum_uncle_count()) .foreach(|h| { - open_block.push_uncle(h).unwrap(); + open_block.push_uncle(h).expect("pushing maximum_uncle_count; + open_block was just created; + push_uncle is not ok only if more than maximum_uncle_count is pushed; + so all push_uncle are Ok; + qed"); }); open_block @@ -1145,6 +1143,7 @@ impl MiningBlockChainClient for Client { let block_data = block.rlp_bytes(); let route = self.commit_block(block, &h, &block_data); trace!(target: "client", "Imported sealed block #{} ({})", number, h); + self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false); let (enacted, retracted) = self.calculate_enacted_retracted(&[route]); self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3bbf9011b2d..1e8aa9d72e9 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -30,13 +30,20 @@ pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use types::trace_filter::Filter as TraceFilter; pub use executive::{Executed, Executive, TransactOptions}; pub use env_info::{LastHashes, EnvInfo}; -pub use self::chain_notify::{ChainNotify, ChainNotifyClient}; +pub use self::chain_notify::ChainNotify; pub use types::call_analytics::CallAnalytics; pub use block_import_error::BlockImportError; pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportError; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, RemoteClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient}; + +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use super::traits::RemoteClient; + pub use super::chain_notify::ChainNotifyClient; +} mod traits { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index c640e76dbfd..6a53582d3f5 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -55,6 +55,8 @@ pub struct TestBlockChainClient { pub genesis_hash: H256, /// Last block hash. pub last_hash: RwLock, + /// Extra data do set for each block + pub extra_data: Bytes, /// Difficulty. pub difficulty: RwLock, /// Balances. @@ -105,11 +107,17 @@ impl Default for TestBlockChainClient { impl TestBlockChainClient { /// Creates new test client. pub fn new() -> Self { + Self::new_with_extra_data(Bytes::new()) + } + + /// 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(); let mut client = TestBlockChainClient { blocks: RwLock::new(HashMap::new()), numbers: RwLock::new(HashMap::new()), genesis_hash: H256::new(), + extra_data: extra_data, last_hash: RwLock::new(H256::new()), difficulty: RwLock::new(From::from(0)), balances: RwLock::new(HashMap::new()), @@ -129,7 +137,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -184,6 +192,7 @@ impl TestBlockChainClient { header.set_parent_hash(self.last_hash.read().clone()); header.set_number(n as BlockNumber); header.set_gas_limit(U256::from(1_000_000)); + header.set_extra_data(self.extra_data.clone()); let uncles = match with { EachBlockWith::Uncle | EachBlockWith::UncleAndTransaction => { let mut uncles = RlpStream::new_list(1); @@ -606,6 +615,6 @@ impl BlockChainClient for TestBlockChainClient { } fn pending_transactions(&self) -> Vec { - self.miner.pending_transactions() + self.miner.pending_transactions(self.chain_info().best_block_number) } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2af0ab78186..7826f991ef8 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -20,6 +20,7 @@ mod message; mod timeout; mod params; mod vote; +mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -246,9 +247,11 @@ impl Engine for Tendermint { } } - /// Set author to proposer. + /// Set author to proposer and set the correct round in the seal. /// 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 on_close_block(&self, _block: &mut ExecutedBlock) { + + } /// Attempt to seal the block internally using all available signatures. /// @@ -278,11 +281,14 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { let message: ConsensusMessage = try!(message.as_val()); - try!(Err(EngineError::UnknownStep)) - //match message { - // ConsensusMessage::Prevote - //} + if self.is_authority(&sender) { + //match message { + // ConsensusMessage::Prevote + //} + } + + try!(Err(EngineError::UnknownStep)) // Check if correct round. //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs index a11583201a8..a877d324ecd 100644 --- a/ethcore/src/engines/tendermint/vote.rs +++ b/ethcore/src/engines/tendermint/vote.rs @@ -21,27 +21,35 @@ use util::Hashable; use account_provider::AccountProvider; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; use basic_types::Seal; +use super::BlockHash; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct Vote { + block_hash: BlockHash, signature: H520 } -fn message(header: &Header) -> H256 { +fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } impl Vote { - fn new(signature: H520) -> Vote { Vote { signature: signature }} + fn new(block_hash: BlockHash, signature: H520) -> Vote { + Vote { block_hash: block_hash, signature: signature } + } /// Try to use the author address to create a vote. pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { - accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new) + Self::validate(header, accounts, *header.author()) } /// Use any unlocked validator account to create a vote. pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { - accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new) + let message = block_hash(&header); + accounts.sign(validator, message) + .ok() + .map(Into::into) + .map(|sig| Self::new(message, sig)) } } @@ -51,13 +59,14 @@ impl Decodable for Vote { if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - rlp.as_val().map(Self::new) + Ok(Self::new(try!(rlp.val_at(0)), try!(rlp.val_at(1)))) } } impl Encodable for Vote { fn rlp_append(&self, s: &mut RlpStream) { - let Vote { ref signature } = *self; + let Vote { ref block_hash, ref signature } = *self; + s.append(block_hash); s.append(signature); } } diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index fdf99876add..887f37cefd6 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -116,11 +116,11 @@ impl evm::Evm for Interpreter { let instruction = code[reader.position]; reader.position += 1; - let info = infos[instruction as usize]; - try!(self.verify_instruction(ext, instruction, &info, &stack)); + let info = &infos[instruction as usize]; + try!(self.verify_instruction(ext, instruction, info, &stack)); // Calculate gas cost - let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, &info, &stack, self.mem.size())); + let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size())); // TODO: make compile-time removable if too much of a performance hit. let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256()); @@ -129,7 +129,7 @@ impl evm::Evm for Interpreter { gasometer.current_mem_gas = mem_gas; gasometer.current_gas = gasometer.current_gas - gas_cost; - evm_debug!({ informant.before_instruction(reader.position, instruction, &info, &gasometer.current_gas, &stack) }); + evm_debug!({ informant.before_instruction(reader.position, instruction, info, &gasometer.current_gas, &stack) }); let (mem_written, store_written) = match trace_executed { true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)), diff --git a/ethcore/src/evm/interpreter/shared_cache.rs b/ethcore/src/evm/interpreter/shared_cache.rs index 76360138b25..ce383bae810 100644 --- a/ethcore/src/evm/interpreter/shared_cache.rs +++ b/ethcore/src/evm/interpreter/shared_cache.rs @@ -21,7 +21,7 @@ use util::sha3::*; use bit_set::BitSet; use super::super::instructions; -const CACHE_CODE_ITEMS: usize = 4096; +const CACHE_CODE_ITEMS: usize = 65536; /// GLobal cache for EVM interpreter pub struct SharedCache { diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index b0b0b58c856..f3186d6dd99 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -25,10 +25,10 @@ use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, E use crossbeam; pub use types::executed::{Executed, ExecutionResult}; -/// Max depth to avoid stack overflow (when it's reached we start a new thread with VM) +/// Roughly estimate what stack size each level of evm depth will use /// TODO [todr] We probably need some more sophisticated calculations here (limit on my machine 132) /// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp` -const MAX_VM_DEPTH_FOR_THREAD: usize = 64; +const STACK_SIZE_PER_DEPTH: usize = 24*1024; /// Returns new address created from address and given nonce. pub fn contract_address(address: &Address, nonce: &U256) -> Address { @@ -149,12 +149,13 @@ impl<'a> Executive<'a> { // TODO: we might need bigints here, or at least check overflows. let balance = self.state.balance(&sender); - let gas_cost = U512::from(t.gas) * U512::from(t.gas_price); + let gas_cost = t.gas.full_mul(t.gas_price); let total_cost = U512::from(t.value) + gas_cost; // avoid unaffordable transactions - if U512::from(balance) < total_cost { - return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: U512::from(balance) })); + let balance512 = U512::from(balance); + if balance512 < total_cost { + return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: balance512 })); } // NOTE: there can be no invalid transactions from this point. @@ -212,8 +213,11 @@ impl<'a> Executive<'a> { tracer: &mut T, vm_tracer: &mut V ) -> evm::Result where T: Tracer, V: VMTracer { + + let depth_threshold = ::io::LOCAL_STACK_SIZE.with(|sz| sz.get() / STACK_SIZE_PER_DEPTH); + // Ordinary execution - keep VM in same thread - if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 { + if (self.depth + 1) % depth_threshold != 0 { let vm_factory = self.vm_factory; let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); @@ -265,7 +269,7 @@ impl<'a> Executive<'a> { let cost = self.engine.cost_of_builtin(¶ms.code_address, data); if cost <= params.gas { self.engine.execute_builtin(¶ms.code_address, data, &mut output); - self.state.clear_snapshot(); + self.state.discard_snapshot(); // trace only top level calls to builtins to avoid DDoS attacks if self.depth == 0 { @@ -285,7 +289,7 @@ impl<'a> Executive<'a> { Ok(params.gas - cost) } else { // just drain the whole gas - self.state.revert_snapshot(); + self.state.revert_to_snapshot(); tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into()); @@ -331,7 +335,7 @@ impl<'a> Executive<'a> { res } else { // otherwise it's just a basic transaction, only do tracing, if necessary. - self.state.clear_snapshot(); + self.state.discard_snapshot(); tracer.trace_call(trace_info, U256::zero(), trace_output, vec![]); Ok(params.gas) @@ -413,7 +417,7 @@ impl<'a> Executive<'a> { // real ammount to refund let gas_left_prerefund = match result { Ok(x) => x, _ => 0.into() }; - let refunded = cmp::min(refunds_bound, (t.gas - gas_left_prerefund) / U256::from(2)); + let refunded = cmp::min(refunds_bound, (t.gas - gas_left_prerefund) >> 1); let gas_left = gas_left_prerefund + refunded; let gas_used = t.gas - gas_left; @@ -473,10 +477,10 @@ impl<'a> Executive<'a> { | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::OutOfStack {..}) => { - self.state.revert_snapshot(); + self.state.revert_to_snapshot(); }, Ok(_) | Err(evm::Error::Internal) => { - self.state.clear_snapshot(); + self.state.discard_snapshot(); substate.accrue(un_substate); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 77183d45217..a2dab447518 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -48,6 +48,17 @@ pub enum PendingSet { SealingOrElseQueue, } +/// Type of the gas limit to apply to the transaction queue. +#[derive(Debug, PartialEq)] +pub enum GasLimit { + /// Depends on the block gas limit and is updated with every block. + Auto, + /// No limit. + None, + /// Set to a fixed gas value. + Fixed(U256), +} + /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { @@ -71,6 +82,8 @@ pub struct MinerOptions { pub work_queue_size: usize, /// Can we submit two different solutions for the same block and expect both to result in an import? pub enable_resubmission: bool, + /// Global gas limit for all transaction in the queue except for local and retracted. + pub tx_queue_gas_limit: GasLimit, } impl Default for MinerOptions { @@ -81,11 +94,12 @@ impl Default for MinerOptions { reseal_on_external_tx: false, reseal_on_own_tx: true, tx_gas_limit: !U256::zero(), - tx_queue_size: 1024, + tx_queue_size: 2048, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), work_queue_size: 20, enable_resubmission: true, + tx_queue_gas_limit: GasLimit::Auto, } } } @@ -194,7 +208,11 @@ impl Miner { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) }; - let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit))); + let gas_limit = match options.tx_queue_gas_limit { + GasLimit::Fixed(ref limit) => *limit, + _ => !U256::zero(), + }; + let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, gas_limit, options.tx_gas_limit))); Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), @@ -443,6 +461,10 @@ impl Miner { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock(); queue.set_gas_limit(gas_limit); + if let GasLimit::Auto = self.options.tx_queue_gas_limit { + // Set total tx queue gas limit to be 2x the block gas limit. + queue.set_total_gas_limit(gas_limit << 1); + } } /// Returns true if we had to prepare new pending block. @@ -493,6 +515,21 @@ impl Miner { /// Are we allowed to do a non-mandatory reseal? fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } + + fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H + where F: Fn() -> H, G: Fn(&ClosedBlock) -> H { + let sealing_work = self.sealing_work.lock(); + sealing_work.queue.peek_last_ref().map_or_else( + || from_chain(), + |b| { + if b.block().header().number() > latest_block_number { + map_block(b) + } else { + from_chain() + } + } + ) + } } const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; @@ -565,29 +602,35 @@ impl MinerService for Miner { } fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else( + self.from_pending_block( + chain.chain_info().best_block_number, || chain.latest_balance(address), |b| b.block().fields().state.balance(address) ) } fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else( + self.from_pending_block( + chain.chain_info().best_block_number, || chain.latest_storage_at(address, position), |b| b.block().fields().state.storage_at(address, position) ) } fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else(|| chain.latest_nonce(address), |b| b.block().fields().state.nonce(address)) + self.from_pending_block( + chain.chain_info().best_block_number, + || chain.latest_nonce(address), + |b| b.block().fields().state.nonce(address) + ) } fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else(|| chain.latest_code(address), |b| b.block().fields().state.code(address).map(|c| (*c).clone())) + self.from_pending_block( + chain.chain_info().best_block_number, + || chain.latest_code(address), + |b| b.block().fields().state.code(address).map(|c| (*c).clone()) + ) } fn set_author(&self, author: Address) { @@ -737,50 +780,74 @@ impl MinerService for Miner { queue.top_transactions() } - fn pending_transactions(&self) -> Vec { + fn pending_transactions(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - // TODO: should only use the sealing_work when it's current (it could be an old block) - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.top_transactions(), - (_, sealing) => sealing.map_or_else(Vec::new, |s| s.transactions().to_owned()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.top_transactions(), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.top_transactions(), + |sealing| sealing.transactions().to_owned() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || vec![], + |sealing| sealing.transactions().to_owned() + ) + }, } } - fn pending_transactions_hashes(&self) -> Vec { + fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.pending_hashes(), - (_, sealing) => sealing.map_or_else(Vec::new, |s| s.transactions().iter().map(|t| t.hash()).collect()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.pending_hashes(), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.pending_hashes(), + |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || vec![], + |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + ) + }, } } - fn transaction(&self, hash: &H256) -> Option { + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.find(hash), - (_, sealing) => sealing.and_then(|s| s.transactions().iter().find(|t| &t.hash() == hash).cloned()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.find(hash), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.find(hash), + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || None, + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + ) + }, } } - fn pending_receipt(&self, hash: &H256) -> Option { - let sealing_work = self.sealing_work.lock(); - match (sealing_work.enabled, sealing_work.queue.peek_last_ref()) { - (true, Some(pending)) => { + fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { + self.from_pending_block( + best_block, + || None, + |pending| { let txs = pending.transactions(); txs.iter() .map(|t| t.hash()) @@ -801,15 +868,15 @@ impl MinerService for Miner { logs: receipt.logs.clone(), } }) - }, - _ => None - } + } + ) } - fn pending_receipts(&self) -> BTreeMap { - let sealing_work = self.sealing_work.lock(); - match (sealing_work.enabled, sealing_work.queue.peek_last_ref()) { - (true, Some(pending)) => { + fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap { + self.from_pending_block( + best_block, + || BTreeMap::new(), + |pending| { let hashes = pending.transactions() .iter() .map(|t| t.hash()); @@ -817,9 +884,8 @@ impl MinerService for Miner { let receipts = pending.receipts().iter().cloned(); hashes.zip(receipts).collect() - }, - _ => BTreeMap::new() - } + } + ) } fn last_nonce(&self, address: &Address) -> Option { @@ -1016,6 +1082,7 @@ mod tests { reseal_min_period: Duration::from_secs(5), tx_gas_limit: !U256::zero(), tx_queue_size: 1024, + tx_queue_gas_limit: GasLimit::None, pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, @@ -1044,34 +1111,54 @@ mod tests { let client = TestBlockChainClient::default(); let miner = miner(); let transaction = transaction(); + let best_block = 0; // when let res = miner.import_own_transaction(&client, transaction); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.pending_transactions_hashes().len(), 1); - assert_eq!(miner.pending_receipts().len(), 1); + assert_eq!(miner.pending_transactions(best_block).len(), 1); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); + assert_eq!(miner.pending_receipts(best_block).len(), 1); // This method will let us know if pending block was created (before calling that method) assert!(!miner.prepare_work_sealing(&client)); } + #[test] + fn should_not_use_pending_block_if_best_block_is_higher() { + // given + let client = TestBlockChainClient::default(); + let miner = miner(); + let transaction = transaction(); + let best_block = 10; + // when + let res = miner.import_own_transaction(&client, transaction); + + // then + assert_eq!(res.unwrap(), TransactionImportResult::Current); + assert_eq!(miner.all_transactions().len(), 1); + assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); + assert_eq!(miner.pending_receipts(best_block).len(), 0); + } + #[test] fn should_import_external_transaction() { // given let client = TestBlockChainClient::default(); let miner = miner(); let transaction = transaction(); + let best_block = 0; // when let res = miner.import_external_transactions(&client, vec![transaction]).pop().unwrap(); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions_hashes().len(), 0); - assert_eq!(miner.pending_transactions().len(), 0); - assert_eq!(miner.pending_receipts().len(), 0); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); + assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.pending_receipts(best_block).len(), 0); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index e95ce758ac2..8dfddf483a3 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -48,7 +48,7 @@ mod work_notify; mod price_info; pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions}; +pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use client::TransactionImportResult; @@ -56,6 +56,7 @@ use std::collections::BTreeMap; use util::{H256, U256, Address, Bytes}; use client::{MiningBlockChainClient, Executed, CallAnalytics}; use block::ClosedBlock; +use header::BlockNumber; use receipt::{RichReceipt, Receipt}; use error::{Error, CallError}; use transaction::SignedTransaction; @@ -115,7 +116,7 @@ pub trait MinerService : Send + Sync { Result; /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self) -> Vec; + fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec; /// Removes all transactions from the queue and restart mining operation. fn clear_and_reset(&self, chain: &MiningBlockChainClient); @@ -135,19 +136,19 @@ pub trait MinerService : Send + Sync { where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. - fn transaction(&self, hash: &H256) -> Option; + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Get a list of all transactions. fn all_transactions(&self) -> Vec; /// Get a list of all pending transactions. - fn pending_transactions(&self) -> Vec; + fn pending_transactions(&self, best_block: BlockNumber) -> Vec; /// Get a list of all pending receipts. - fn pending_receipts(&self) -> BTreeMap; + fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; /// Get a particular reciept. - fn pending_receipt(&self, hash: &H256) -> Option; + fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Returns highest transaction nonce for given address. fn last_nonce(&self, address: &Address) -> Option; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 75a66358ad9..fdb652780a6 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -130,6 +130,8 @@ struct TransactionOrder { /// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5) /// High nonce_height = Low priority (processed later) nonce_height: U256, + /// Gas specified in the transaction. + gas: U256, /// Gas Price of the transaction. /// Low gas price = Low priority (processed later) gas_price: U256, @@ -146,6 +148,7 @@ impl TransactionOrder { fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self { TransactionOrder { nonce_height: tx.nonce() - base_nonce, + gas: tx.transaction.gas.clone(), gas_price: tx.transaction.gas_price, hash: tx.hash(), origin: tx.origin, @@ -287,6 +290,7 @@ struct TransactionSet { by_address: Table, by_gas_price: GasPriceQueue, limit: usize, + gas_limit: U256, } impl TransactionSet { @@ -317,15 +321,20 @@ impl TransactionSet { /// It drops transactions from this set but also removes associated `VerifiedTransaction`. /// Returns addresses and lowest nonces of transactions removed because of limit. fn enforce_limit(&mut self, by_hash: &mut HashMap) -> Option> { - let len = self.by_priority.len(); - if len <= self.limit { - return None; - } - + let mut count = 0; + let mut gas: U256 = 0.into(); let to_drop : Vec<(Address, U256)> = { self.by_priority .iter() - .skip(self.limit) + .skip_while(|order| { + count = count + 1; + let r = gas.overflowing_add(order.gas); + if r.1 { return false } + gas = r.0; + // Own and retracted transactions are allowed to go above the gas limit, bot not above the count limit. + (gas <= self.gas_limit || order.origin == TransactionOrigin::Local || order.origin == TransactionOrigin::RetractedBlock) && + count <= self.limit + }) .map(|order| by_hash.get(&order.hash) .expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`.")) .map(|tx| (tx.sender(), tx.nonce())) @@ -432,16 +441,17 @@ impl Default for TransactionQueue { impl TransactionQueue { /// Creates new instance of this Queue pub fn new() -> Self { - Self::with_limits(1024, !U256::zero()) + Self::with_limits(1024, !U256::zero(), !U256::zero()) } /// Create new instance of this Queue with specified limits - pub fn with_limits(limit: usize, tx_gas_limit: U256) -> Self { + pub fn with_limits(limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self { let current = TransactionSet { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), limit: limit, + gas_limit: gas_limit, }; let future = TransactionSet { @@ -449,6 +459,7 @@ impl TransactionQueue { by_address: Table::new(), by_gas_price: Default::default(), limit: limit, + gas_limit: gas_limit, }; TransactionQueue { @@ -504,6 +515,13 @@ impl TransactionQueue { }; } + /// Sets new total gas limit. + pub fn set_total_gas_limit(&mut self, gas_limit: U256) { + self.future.gas_limit = gas_limit; + self.current.gas_limit = gas_limit; + self.future.enforce_limit(&mut self.by_hash); + } + /// Set the new limit for the amount of gas any individual transaction may have. /// Any transaction already imported to the queue is not affected. pub fn set_tx_gas_limit(&mut self, limit: U256) { @@ -636,7 +654,7 @@ impl TransactionQueue { }; for k in nonces_from_sender { let order = self.future.drop(&sender, &k).unwrap(); - self.current.insert(sender, k, order.penalize()); + self.future.insert(sender, k, order.penalize()); } } @@ -735,6 +753,15 @@ impl TransactionQueue { .collect() } + #[cfg(test)] + fn future_transactions(&self) -> Vec { + self.future.by_priority + .iter() + .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) + .map(|t| t.transaction.clone()) + .collect() + } + /// Returns hashes of all transactions from current, ordered by priority. pub fn pending_hashes(&self) -> Vec { self.current.by_priority @@ -818,6 +845,16 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); + { + // Rough size sanity check + let gas = &tx.transaction.gas; + if U256::from(tx.transaction.data.len()) > *gas { + // Droping transaction + trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); + return Err(TransactionError::LimitReached); + } + } + // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. @@ -970,6 +1007,7 @@ mod test { } fn default_nonce() -> U256 { 123.into() } + fn default_gas_val() -> U256 { 100_000.into() } fn default_gas_price() -> U256 { 1.into() } fn new_unsigned_tx(nonce: U256, gas_price: U256) -> Transaction { @@ -977,7 +1015,7 @@ mod test { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), + gas: default_gas_val(), gas_price: gas_price, nonce: nonce } @@ -1042,7 +1080,7 @@ mod test { #[test] fn should_return_correct_nonces_when_dropped_because_of_limit() { // given - let mut txq = TransactionQueue::with_limits(2, !U256::zero()); + let mut txq = TransactionQueue::with_limits(2, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let sender = tx1.sender().unwrap(); let nonce = tx1.nonce; @@ -1080,7 +1118,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External).unwrap(); @@ -1120,7 +1159,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; // Create two transactions with same nonce // (same hash) @@ -1168,7 +1208,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 2 + limit: 2, + gas_limit: !U256::zero(), }; let tx = new_tx_default(); let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); @@ -1185,7 +1226,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; assert_eq!(set.gas_price_entry_limit(), 0.into()); @@ -1463,6 +1505,36 @@ mod test { assert_eq!(top.len(), 2); } + #[test] + fn should_penalize_transactions_from_sender_in_future() { + // given + let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() }; + let mut txq = TransactionQueue::new(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(txb.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + + assert_eq!(txq.status().future, 4); + + // when + txq.penalize(&tx1.hash()); + + // then + let top = txq.future_transactions(); + assert_eq!(top[0], txa); + assert_eq!(top[1], txb); + assert_eq!(top[2], tx1); + assert_eq!(top[3], tx2); + assert_eq!(top.len(), 4); + } + + #[test] fn should_penalize_transactions_from_sender() { // given @@ -1651,7 +1723,7 @@ mod test { #[test] fn should_drop_old_transactions_when_hitting_the_limit() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; @@ -1672,7 +1744,7 @@ mod test { #[test] fn should_limit_future_transactions() { - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); txq.current.set_limit(10); let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); @@ -1689,6 +1761,30 @@ mod test { assert_eq!(txq.status().future, 1); } + #[test] + fn should_limit_by_gas() { + let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).ok(); + assert_eq!(txq.status().pending, 2); + } + + #[test] + fn should_keep_own_transactions_above_gas_limit() { + let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + assert_eq!(txq.status().pending, 4); + } + #[test] fn should_drop_transactions_with_old_nonces() { let mut txq = TransactionQueue::new(); @@ -1932,7 +2028,7 @@ mod test { #[test] fn should_keep_right_order_in_future() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance: default_account_details(a).balance }; diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 2150ee226f8..a5e6b58bdfe 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -51,7 +51,7 @@ use rand::{Rng, OsRng}; pub use self::error::Error; pub use self::service::{Service, DatabaseRestore}; -pub use self::traits::{SnapshotService, RemoteSnapshotService}; +pub use self::traits::SnapshotService; pub use self::watcher::Watcher; pub use types::snapshot_manifest::ManifestData; pub use types::restoration_status::RestorationStatus; @@ -67,6 +67,12 @@ mod watcher; #[cfg(test)] mod tests; +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use super::traits::RemoteSnapshotService; +} + mod traits { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues include!(concat!(env!("OUT_DIR"), "/snapshot_service_trait.rs")); diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index bd7ed810b5f..79a9e8ef112 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -16,7 +16,6 @@ //! Single account in the system. -use std::collections::hash_map::Entry; use util::*; use pod_account::*; use rlp::*; @@ -24,9 +23,11 @@ use lru_cache::LruCache; use std::cell::{RefCell, Cell}; -const STORAGE_CACHE_ITEMS: usize = 4096; +const STORAGE_CACHE_ITEMS: usize = 8192; /// Single account in the system. +/// Keeps track of changes to the code and storage. +/// The changes are applied in `commit_storage` and `commit_code` pub struct Account { // Balance of the account. balance: U256, @@ -46,8 +47,6 @@ pub struct Account { code_size: Option, // Code cache of the account. code_cache: Arc, - // Account is new or has been modified. - filth: Filth, // Account code new or has been modified. code_filth: Filth, // Cached address hash. @@ -67,7 +66,6 @@ impl Account { code_hash: code.sha3(), code_size: Some(code.len()), code_cache: Arc::new(code), - filth: Filth::Dirty, code_filth: Filth::Dirty, address_hash: Cell::new(None), } @@ -89,7 +87,6 @@ impl Account { code_filth: Filth::Dirty, code_size: Some(pod.code.as_ref().map_or(0, |c| c.len())), code_cache: Arc::new(pod.code.map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c)), - filth: Filth::Dirty, address_hash: Cell::new(None), } } @@ -105,7 +102,6 @@ impl Account { code_hash: SHA3_EMPTY, code_cache: Arc::new(vec![]), code_size: Some(0), - filth: Filth::Dirty, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -123,7 +119,6 @@ impl Account { code_hash: r.val_at(3), code_cache: Arc::new(vec![]), code_size: None, - filth: Filth::Clean, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -141,7 +136,6 @@ impl Account { code_hash: SHA3_EMPTY, code_cache: Arc::new(vec![]), code_size: None, - filth: Filth::Dirty, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -153,7 +147,6 @@ impl Account { self.code_hash = code.sha3(); self.code_cache = Arc::new(code); self.code_size = Some(self.code_cache.len()); - self.filth = Filth::Dirty; self.code_filth = Filth::Dirty; } @@ -164,17 +157,7 @@ impl Account { /// Set (and cache) the contents of the trie's storage at `key` to `value`. pub fn set_storage(&mut self, key: H256, value: H256) { - match self.storage_changes.entry(key) { - Entry::Occupied(ref mut entry) if entry.get() != &value => { - entry.insert(value); - self.filth = Filth::Dirty; - }, - Entry::Vacant(entry) => { - entry.insert(value); - self.filth = Filth::Dirty; - }, - _ => {}, - } + self.storage_changes.insert(key, value); } /// Get (and cache) the contents of the trie's storage at `key`. @@ -263,17 +246,6 @@ impl Account { !self.code_cache.is_empty() || (self.code_cache.is_empty() && self.code_hash == SHA3_EMPTY) } - /// Is this a new or modified account? - pub fn is_dirty(&self) -> bool { - self.filth == Filth::Dirty || self.code_filth == Filth::Dirty || !self.storage_is_clean() - } - - /// Mark account as clean. - pub fn set_clean(&mut self) { - assert!(self.storage_is_clean()); - self.filth = Filth::Clean - } - /// Provide a database to get `code_hash`. Should not be called if it is a contract without code. pub fn cache_code(&mut self, db: &HashDB) -> bool { // TODO: fill out self.code_cache; @@ -326,25 +298,18 @@ impl Account { /// Increment the nonce of the account by one. pub fn inc_nonce(&mut self) { self.nonce = self.nonce + U256::from(1u8); - self.filth = Filth::Dirty; } - /// Increment the nonce of the account by one. + /// Increase account balance. pub fn add_balance(&mut self, x: &U256) { - if !x.is_zero() { - self.balance = self.balance + *x; - self.filth = Filth::Dirty; - } + self.balance = self.balance + *x; } - /// Increment the nonce of the account by one. + /// Decrease account balance. /// Panics if balance is less than `x` pub fn sub_balance(&mut self, x: &U256) { - if !x.is_zero() { - assert!(self.balance >= *x); - self.balance = self.balance - *x; - self.filth = Filth::Dirty; - } + assert!(self.balance >= *x); + self.balance = self.balance - *x; } /// Commit the `storage_changes` to the backing DB and update `storage_root`. @@ -406,7 +371,6 @@ impl Account { code_hash: self.code_hash.clone(), code_size: self.code_size.clone(), code_cache: self.code_cache.clone(), - filth: self.filth, code_filth: self.code_filth, address_hash: self.address_hash.clone(), } @@ -427,10 +391,10 @@ impl Account { account } - /// Replace self with the data from other account merging storage cache - pub fn merge_with(&mut self, other: Account) { - assert!(self.storage_is_clean()); - assert!(other.storage_is_clean()); + /// Replace self with the data from other account merging storage cache. + /// Basic account data and all modifications are overwritten + /// with new values. + pub fn overwrite_with(&mut self, other: Account) { self.balance = other.balance; self.nonce = other.nonce; self.storage_root = other.storage_root; @@ -443,6 +407,7 @@ impl Account { for (k, v) in other.storage_cache.into_inner().into_iter() { cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here } + self.storage_changes = other.storage_changes; } } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index a2fe25b9144..39c8bbc1114 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::cell::{RefCell, RefMut}; +use std::collections::hash_map::Entry; use common::*; use engines::Engine; use executive::{Executive, TransactOptions}; @@ -42,42 +43,93 @@ pub struct ApplyOutcome { /// Result type for the execution ("application") of a transaction. pub type ApplyResult = Result; +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +/// Account modification state. Used to check if the account was +/// Modified in between commits and overall. +enum AccountState { + /// Account was loaded from disk and never modified in this state object. + CleanFresh, + /// Account was loaded from the global cache and never modified. + CleanCached, + /// Account has been modified and is not committed to the trie yet. + /// This is set if any of the account data is changed, including + /// storage and code. + Dirty, + /// Account was modified and committed to the trie. + Committed, +} + #[derive(Debug)] -enum AccountEntry { - /// Contains account data. - Cached(Account), - /// Account has been deleted. - Killed, - /// Account does not exist. - Missing, +/// In-memory copy of the account data. Holds the optional account +/// and the modification status. +/// Account entry can contain existing (`Some`) or non-existing +/// account (`None`) +struct AccountEntry { + account: Option, + state: AccountState, } +// Account cache item. Contains account data and +// modification state impl AccountEntry { fn is_dirty(&self) -> bool { - match *self { - AccountEntry::Cached(ref a) => a.is_dirty(), - AccountEntry::Killed => true, - AccountEntry::Missing => false, - } + self.state == AccountState::Dirty } - /// Clone dirty data into new `AccountEntry`. + /// Clone dirty data into new `AccountEntry`. This includes + /// basic account data and modified storage keys. /// Returns None if clean. - fn clone_dirty(&self) -> Option { - match *self { - AccountEntry::Cached(ref acc) if acc.is_dirty() => Some(AccountEntry::Cached(acc.clone_dirty())), - AccountEntry::Killed => Some(AccountEntry::Killed), - _ => None, + fn clone_if_dirty(&self) -> Option { + match self.is_dirty() { + true => Some(self.clone_dirty()), + false => None, + } + } + + /// Clone dirty data into new `AccountEntry`. This includes + /// basic account data and modified storage keys. + fn clone_dirty(&self) -> AccountEntry { + AccountEntry { + account: self.account.as_ref().map(Account::clone_dirty), + state: self.state, + } + } + + // Create a new account entry and mark it as dirty. + fn new_dirty(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::Dirty, + } + } + + // Create a new account entry and mark it as clean. + fn new_clean(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::CleanFresh, + } + } + + // Create a new account entry and mark it as clean and cached. + fn new_clean_cached(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::CleanCached, } } - /// Clone account entry data that needs to be saved in the snapshot. - /// This includes basic account information and all locally cached storage keys - fn clone_for_snapshot(&self) -> AccountEntry { - match *self { - AccountEntry::Cached(ref acc) => AccountEntry::Cached(acc.clone_all()), - AccountEntry::Killed => AccountEntry::Killed, - AccountEntry::Missing => AccountEntry::Missing, + // Replace data with another entry but preserve storage cache. + fn overwrite_with(&mut self, other: AccountEntry) { + self.state = other.state; + match other.account { + Some(acc) => match self.account { + Some(ref mut ours) => { + ours.overwrite_with(acc); + }, + None => {}, + }, + None => self.account = None, } } } @@ -90,6 +142,9 @@ impl AccountEntry { /// locally from previous commits. Global cache reflects the database /// state and never contains any changes. /// +/// Cache items contains account data, or the flag that account does not exist +/// and modification state (see `AccountState`) +/// /// Account data can be in the following cache states: /// * In global but not local - something that was queried from the database, /// but never modified @@ -103,12 +158,32 @@ impl AccountEntry { /// then global state cache. If data is not found in any of the caches /// it is loaded from the DB to the local cache. /// -/// Upon destruction all the local cache data merged into the global cache. -/// The merge might be rejected if current state is non-canonical. +/// **** IMPORTANT ************************************************************* +/// All the modifications to the account data must set the `Dirty` state in the +/// `AccountEntry`. This is done in `require` and `require_or_from`. So just +/// use that. +/// **************************************************************************** +/// +/// Upon destruction all the local cache data propagated into the global cache. +/// Propagated items might be rejected if current state is non-canonical. +/// +/// State snapshotting. +/// +/// A new snapshot can be created with `snapshot()`. Snapshots can be +/// created in a hierarchy. +/// When a snapshot is active all changes are applied directly into +/// `cache` and the original value is copied into an active snapshot. +/// Reverting a snapshot with `revert_to_snapshot` involves copying +/// original values from the latest snapshot back into `cache`. The code +/// takes care not to overwrite cached storage while doing that. +/// Snapshot can be discateded with `discard_snapshot`. All of the orignal +/// backed-up values are moved into a parent snapshot (if any). +/// pub struct State { db: StateDB, root: H256, cache: RefCell>, + // The original account is preserved in snapshots: RefCell>>>, account_start_nonce: U256, factories: Factories, @@ -162,35 +237,48 @@ impl State { Ok(state) } - /// Create a recoverable snaphot of this state + /// Create a recoverable snaphot of this state. pub fn snapshot(&mut self) { self.snapshots.borrow_mut().push(HashMap::new()); } - /// Merge last snapshot with previous - pub fn clear_snapshot(&mut self) { + /// Merge last snapshot with previous. + pub fn discard_snapshot(&mut self) { // merge with previous snapshot let last = self.snapshots.borrow_mut().pop(); if let Some(mut snapshot) = last { if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() { - for (k, v) in snapshot.drain() { - prev.entry(k).or_insert(v); + if prev.is_empty() { + **prev = snapshot; + } else { + for (k, v) in snapshot.drain() { + prev.entry(k).or_insert(v); + } } } } } - /// Revert to snapshot - pub fn revert_snapshot(&mut self) { + /// Revert to the last snapshot and discard it. + pub fn revert_to_snapshot(&mut self) { if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() { for (k, v) in snapshot.drain() { match v { Some(v) => { - self.cache.borrow_mut().insert(k, v); + match self.cache.borrow_mut().entry(k) { + Entry::Occupied(mut e) => { + // Merge snapshotted changes back into the main account + // storage preserving the cache. + e.get_mut().overwrite_with(v); + }, + Entry::Vacant(e) => { + e.insert(v); + } + } }, None => { match self.cache.borrow_mut().entry(k) { - ::std::collections::hash_map::Entry::Occupied(e) => { + Entry::Occupied(e) => { if e.get().is_dirty() { e.remove(); } @@ -204,10 +292,17 @@ impl State { } fn insert_cache(&self, address: &Address, account: AccountEntry) { - if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { - if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); - return; + // Dirty account which is not in the cache means this is a new account. + // It goes directly into the snapshot as there's nothing to rever to. + // + // In all other cases account is read as clean first, and after that made + // dirty in and added to the snapshot with `note_cache`. + if account.is_dirty() { + if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { + if !snapshot.contains_key(address) { + snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); + return; + } } } self.cache.borrow_mut().insert(address.clone(), account); @@ -216,14 +311,14 @@ impl State { fn note_cache(&self, address: &Address) { if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_for_snapshot)); + snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_dirty)); } } } /// Destroy the current object and return root and database. pub fn drop(mut self) -> (H256, StateDB) { - self.commit_cache(); + self.propagate_to_global_cache(); (self.root, self.db) } @@ -235,12 +330,12 @@ impl State { /// Create a new contract at address `contract`. If there is already an account at the address /// it will have its code reset, ready for `init_code()`. pub fn new_contract(&mut self, contract: &Address, balance: U256) { - self.insert_cache(contract, AccountEntry::Cached(Account::new_contract(balance, self.account_start_nonce))); + self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce)))); } /// Remove an existing account. pub fn kill_account(&mut self, account: &Address) { - self.insert_cache(account, AccountEntry::Killed); + self.insert_cache(account, AccountEntry::new_dirty(None)); } /// Determine whether an account exists. @@ -272,8 +367,8 @@ impl State { let local_cache = self.cache.borrow_mut(); let mut local_account = None; if let Some(maybe_acc) = local_cache.get(address) { - match *maybe_acc { - AccountEntry::Cached(ref account) => { + match maybe_acc.account { + Some(ref account) => { if let Some(value) = account.cached_storage_at(key) { return value; } else { @@ -292,7 +387,7 @@ impl State { return result; } if let Some(ref mut acc) = local_account { - if let AccountEntry::Cached(ref account) = **acc { + if let Some(ref account) = acc.account { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(address)); return account.storage_at(account_db.as_hashdb(), key) } else { @@ -314,10 +409,7 @@ impl State { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address)); a.storage_at(account_db.as_hashdb(), key) }); - match maybe_acc { - Some(account) => self.insert_cache(address, AccountEntry::Cached(account)), - None => self.insert_cache(address, AccountEntry::Missing), - } + self.insert_cache(address, AccountEntry::new_clean(maybe_acc)); r } @@ -341,13 +433,17 @@ impl State { /// Add `incr` to the balance of account `a`. pub fn add_balance(&mut self, a: &Address, incr: &U256) { trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)); - self.require(a, false).add_balance(incr); + if !incr.is_zero() || !self.exists(a) { + self.require(a, false).add_balance(incr); + } } /// Subtract `decr` from the balance of account `a`. pub fn sub_balance(&mut self, a: &Address, decr: &U256) { trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)); - self.require(a, false).sub_balance(decr); + if !decr.is_zero() || !self.exists(a) { + self.require(a, false).sub_balance(decr); + } } /// Subtracts `by` from the balance of `from` and adds it to that of `to`. @@ -363,7 +459,9 @@ impl State { /// Mutate storage of account `a` so that it is `value` for `key`. pub fn set_storage(&mut self, a: &Address, key: H256, value: H256) { - self.require(a, false).set_storage(key, value) + if self.storage_at(a, &key) != value { + self.require(a, false).set_storage(key, value) + } } /// Initialise the code of account `a` so that it is `code`. @@ -404,10 +502,9 @@ impl State { accounts: &mut HashMap ) -> Result<(), Error> { // first, commit the sub trees. - // TODO: is this necessary or can we dispense with the `ref mut a` for just `a`? - for (address, ref mut a) in accounts.iter_mut() { - match a { - &mut&mut AccountEntry::Cached(ref mut account) if account.is_dirty() => { + for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { + match a.account { + Some(ref mut account) => { db.note_account_bloom(&address); let addr_hash = account.address_hash(address); let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); @@ -420,17 +517,15 @@ impl State { { let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root).unwrap(); - for (address, ref mut a) in accounts.iter_mut() { - match **a { - AccountEntry::Cached(ref mut account) if account.is_dirty() => { - account.set_clean(); + for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { + a.state = AccountState::Committed; + match a.account { + Some(ref mut account) => { try!(trie.insert(address, &account.rlp())); }, - AccountEntry::Killed => { + None => { try!(trie.remove(address)); - **a = AccountEntry::Missing; }, - _ => {}, } } } @@ -438,20 +533,12 @@ impl State { Ok(()) } - fn commit_cache(&mut self) { + /// Propagate local cache into shared canonical state cache. + fn propagate_to_global_cache(&mut self) { let mut addresses = self.cache.borrow_mut(); - for (address, a) in addresses.drain() { - match a { - AccountEntry::Cached(account) => { - if !account.is_dirty() { - self.db.cache_account(address, Some(account)); - } - }, - AccountEntry::Missing => { - self.db.cache_account(address, None); - }, - _ => {}, - } + trace!("Committing cache {:?} entries", addresses.len()); + for (address, a) in addresses.drain().filter(|&(_, ref a)| a.state == AccountState::Committed || a.state == AccountState::CleanFresh) { + self.db.add_to_account_cache(address, a.account, a.state == AccountState::Committed); } } @@ -473,7 +560,7 @@ impl State { assert!(self.snapshots.borrow().is_empty()); for (add, acc) in accounts.drain().into_iter() { self.db.note_account_bloom(&add); - self.cache.borrow_mut().insert(add, AccountEntry::Cached(Account::from_pod(acc))); + self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc)))); } } @@ -483,7 +570,7 @@ impl State { // TODO: handle database rather than just the cache. // will need fat db. PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| { - if let AccountEntry::Cached(ref acc) = *opt { + if let Some(ref acc) = opt.account { m.insert(add.clone(), PodAccount::from_account(acc)); } m @@ -530,7 +617,7 @@ impl State { where F: Fn(Option<&Account>) -> U { // check local cache first if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { - if let AccountEntry::Cached(ref mut account) = **maybe_acc { + if let Some(ref mut account) = maybe_acc.account { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); Self::update_account_cache(require, account, accountdb.as_hashdb()); return f(Some(account)); @@ -562,10 +649,7 @@ impl State { Self::update_account_cache(require, account, accountdb.as_hashdb()); } let r = f(maybe_acc.as_ref()); - match maybe_acc { - Some(account) => self.insert_cache(a, AccountEntry::Cached(account)), - None => self.insert_cache(a, AccountEntry::Missing), - } + self.insert_cache(a, AccountEntry::new_clean(maybe_acc)); r } } @@ -584,36 +668,38 @@ impl State { let contains_key = self.cache.borrow().contains_key(a); if !contains_key { match self.db.get_cached_account(a) { - Some(Some(acc)) => self.insert_cache(a, AccountEntry::Cached(acc)), - Some(None) => self.insert_cache(a, AccountEntry::Missing), + Some(acc) => self.insert_cache(a, AccountEntry::new_clean_cached(acc)), None => { let maybe_acc = if self.db.check_account_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let maybe_acc = match db.get(a) { - Ok(Some(acc)) => AccountEntry::Cached(Account::from_rlp(acc)), - Ok(None) => AccountEntry::Missing, + Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(acc))), + Ok(None) => AccountEntry::new_clean(None), Err(e) => panic!("Potential DB corruption encountered: {}", e), }; maybe_acc } else { - AccountEntry::Missing + AccountEntry::new_clean(None) }; self.insert_cache(a, maybe_acc); } } - } else { - self.note_cache(a); } + self.note_cache(a); - match self.cache.borrow_mut().get_mut(a).unwrap() { - &mut AccountEntry::Cached(ref mut acc) => not_default(acc), - slot => *slot = AccountEntry::Cached(default()), + match &mut self.cache.borrow_mut().get_mut(a).unwrap().account { + &mut Some(ref mut acc) => not_default(acc), + slot => *slot = Some(default()), } + // at this point the account is guaranteed to be in the cache. RefMut::map(self.cache.borrow_mut(), |c| { - match c.get_mut(a).unwrap() { - &mut AccountEntry::Cached(ref mut account) => { + let mut entry = c.get_mut(a).unwrap(); + // set the dirty flag after changing account data. + entry.state = AccountState::Dirty; + match entry.account { + Some(ref mut account) => { if require_code { let addr_hash = account.address_hash(a); let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash); @@ -638,7 +724,7 @@ impl Clone for State { let cache = { let mut cache: HashMap = HashMap::new(); for (key, val) in self.cache.borrow().iter() { - if let Some(entry) = val.clone_dirty() { + if let Some(entry) = val.clone_if_dirty() { cache.insert(key.clone(), entry); } } @@ -1679,12 +1765,12 @@ fn snapshot_basic() { state.snapshot(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.clear_snapshot(); + state.discard_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); state.snapshot(); state.add_balance(&a, &U256::from(1u64)); assert_eq!(state.balance(&a), U256::from(70u64)); - state.revert_snapshot(); + state.revert_to_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); } @@ -1697,9 +1783,9 @@ fn snapshot_nested() { state.snapshot(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.clear_snapshot(); + state.discard_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); - state.revert_snapshot(); + state.revert_to_snapshot(); assert_eq!(state.balance(&a), U256::from(0)); } diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 7a120680173..04db274c424 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -14,56 +14,94 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::{VecDeque, HashSet}; use lru_cache::LruCache; use util::journaldb::JournalDB; use util::hash::{H256}; use util::hashdb::HashDB; use state::Account; +use header::BlockNumber; use util::{Arc, Address, Database, DBTransaction, UtilError, Mutex, Hashable}; use bloom_journal::{Bloom, BloomJournal}; use db::COL_ACCOUNT_BLOOM; use byteorder::{LittleEndian, ByteOrder}; -const STATE_CACHE_ITEMS: usize = 65536; +const STATE_CACHE_ITEMS: usize = 256000; +const STATE_CACHE_BLOCKS: usize = 8; pub const ACCOUNT_BLOOM_SPACE: usize = 1048576; pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000; pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; +/// Shared canonical state cache. struct AccountCache { /// DB Account cache. `None` indicates that account is known to be missing. accounts: LruCache>, + /// Information on the modifications in recently committed blocks; specifically which addresses + /// changed in which block. Ordered by block number. + modifications: VecDeque, +} + +/// Buffered account cache item. +struct CacheQueueItem { + /// Account address. + address: Address, + /// Acccount data or `None` if account does not exist. + account: Option, + /// Indicates that the account was modified before being + /// added to the cache. + modified: bool, +} + +#[derive(Debug)] +/// Accumulates a list of accounts changed in a block. +struct BlockChanges { + /// Block number. + number: BlockNumber, + /// Block hash. + hash: H256, + /// Parent block hash. + parent: H256, + /// A set of modified account addresses. + accounts: HashSet

, + /// Block is part of the canonical chain. + is_canon: bool, } /// State database abstraction. -/// Manages shared global state cache. +/// Manages shared global state cache which reflects the canonical +/// state as it is on the disk. All the entries in the cache are clean. /// A clone of `StateDB` may be created as canonical or not. -/// For canonical clones cache changes are accumulated and applied -/// on commit. -/// For non-canonical clones cache is cleared on commit. +/// For canonical clones local cache is accumulated and applied +/// in `sync_cache` +/// For non-canonical clones local cache is dropped. +/// +/// Global cache propagation. +/// After a `State` object has been committed to the trie it +/// propagates its local cache into the `StateDB` local cache +/// using `add_to_account_cache` function. +/// Then, after the block has been added to the chain the local cache in the +/// `StateDB` is propagated into the global cache. pub struct StateDB { + /// Backing database. db: Box, + /// Shared canonical state cache. account_cache: Arc>, - cache_overlay: Vec<(Address, Option)>, - is_canon: bool, + /// Local dirty cache. + local_cache: Vec, + /// Shared account bloom. Does not handle chain reorganizations. account_bloom: Arc>, + /// Hash of the block on top of which this instance was created or + /// `None` if cache is disabled + parent_hash: Option, + /// Hash of the committing block or `None` if not committed yet. + commit_hash: Option, + /// Number of the committing block or `None` if not committed yet. + commit_number: Option, } impl StateDB { - - /// Create a new instance wrapping `JournalDB` - pub fn new(db: Box) -> StateDB { - let bloom = Self::load_bloom(db.backing()); - StateDB { - db: db, - account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(STATE_CACHE_ITEMS) })), - cache_overlay: Vec::new(), - is_canon: false, - account_bloom: Arc::new(Mutex::new(bloom)), - } - } - /// Loads accounts bloom from the database /// This bloom is used to handle request for the non-existant account fast pub fn load_bloom(db: &Database) -> Bloom { @@ -91,6 +129,23 @@ impl StateDB { bloom } + /// Create a new instance wrapping `JournalDB` + pub fn new(db: Box) -> StateDB { + let bloom = Self::load_bloom(db.backing()); + StateDB { + db: db, + account_cache: Arc::new(Mutex::new(AccountCache { + accounts: LruCache::new(STATE_CACHE_ITEMS), + modifications: VecDeque::new(), + })), + local_cache: Vec::new(), + account_bloom: Arc::new(Mutex::new(bloom)), + parent_hash: None, + commit_hash: None, + commit_number: None, + } + } + pub fn check_account_bloom(&self, address: &Address) -> bool { trace!(target: "account_bloom", "Check account bloom: {:?}", address); let bloom = self.account_bloom.lock(); @@ -125,14 +180,107 @@ impl StateDB { try!(Self::commit_bloom(batch, bloom_lock.drain_journal())); } let records = try!(self.db.commit(batch, now, id, end)); - if self.is_canon { - self.commit_cache(); - } else { - self.clear_cache(); - } + self.commit_hash = Some(id.clone()); + self.commit_number = Some(now); Ok(records) } + /// Propagate local cache into the global cache and synchonize + /// the global cache with the best block state. + /// This function updates the global cache by removing entries + /// that are invalidated by chain reorganization. `sync_cache` + /// should be called after the block has been committed and the + /// blockchain route has ben calculated. + pub fn sync_cache(&mut self, enacted: &[H256], retracted: &[H256], is_best: bool) { + trace!("sync_cache id = (#{:?}, {:?}), parent={:?}, best={}", self.commit_number, self.commit_hash, self.parent_hash, is_best); + let mut cache = self.account_cache.lock(); + let mut cache = &mut *cache; + + // Purge changes from re-enacted and retracted blocks. + // Filter out commiting block if any. + let mut clear = false; + for block in enacted.iter().filter(|h| self.commit_hash.as_ref().map_or(true, |p| *h != p)) { + clear = clear || { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + trace!("Reverting enacted block {:?}", block); + m.is_canon = true; + for a in &m.accounts { + trace!("Reverting enacted address {:?}", a); + cache.accounts.remove(a); + } + false + } else { + true + } + }; + } + + for block in retracted { + clear = clear || { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + trace!("Retracting block {:?}", block); + m.is_canon = false; + for a in &m.accounts { + trace!("Retracted address {:?}", a); + cache.accounts.remove(a); + } + false + } else { + true + } + }; + } + if clear { + // We don't know anything about the block; clear everything + trace!("Wiping cache"); + cache.accounts.clear(); + cache.modifications.clear(); + } + + // Propagate cache only if committing on top of the latest canonical state + // blocks are ordered by number and only one block with a given number is marked as canonical + // (contributed to canonical state cache) + if let (Some(ref number), Some(ref hash), Some(ref parent)) = (self.commit_number, self.commit_hash, self.parent_hash) { + if cache.modifications.len() == STATE_CACHE_BLOCKS { + cache.modifications.pop_back(); + } + let mut modifications = HashSet::new(); + trace!("committing {} cache entries", self.local_cache.len()); + for account in self.local_cache.drain(..) { + if account.modified { + modifications.insert(account.address.clone()); + } + if is_best { + if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&account.address) { + if let Some(new) = account.account { + if account.modified { + existing.overwrite_with(new); + } + continue; + } + } + cache.accounts.insert(account.address, account.account); + } + } + + // Save modified accounts. These are ordered by the block number. + let block_changes = BlockChanges { + accounts: modifications, + number: *number, + hash: hash.clone(), + is_canon: is_best, + parent: parent.clone(), + }; + let insert_at = cache.modifications.iter().enumerate().find(|&(_, ref m)| m.number < *number).map(|(i, _)| i); + trace!("inserting modifications at {:?}", insert_at); + if let Some(insert_at) = insert_at { + cache.modifications.insert(insert_at, block_changes); + } else { + cache.modifications.push_back(block_changes); + } + } + } + /// Returns an interface to HashDB. pub fn as_hashdb(&self) -> &HashDB { self.db.as_hashdb() @@ -148,20 +296,24 @@ impl StateDB { StateDB { db: self.db.boxed_clone(), account_cache: self.account_cache.clone(), - cache_overlay: Vec::new(), - is_canon: false, + local_cache: Vec::new(), account_bloom: self.account_bloom.clone(), + parent_hash: None, + commit_hash: None, + commit_number: None, } } /// Clone the database for a canonical state. - pub fn boxed_clone_canon(&self) -> StateDB { + pub fn boxed_clone_canon(&self, parent: &H256) -> StateDB { StateDB { db: self.db.boxed_clone(), account_cache: self.account_cache.clone(), - cache_overlay: Vec::new(), - is_canon: true, + local_cache: Vec::new(), account_bloom: self.account_bloom.clone(), + parent_hash: Some(parent.clone()), + commit_hash: None, + commit_number: None, } } @@ -180,53 +332,149 @@ impl StateDB { &*self.db } - /// Enqueue cache change. - pub fn cache_account(&mut self, addr: Address, data: Option) { - self.cache_overlay.push((addr, data)); - } - - /// Apply pending cache changes. - fn commit_cache(&mut self) { - let mut cache = self.account_cache.lock(); - for (address, account) in self.cache_overlay.drain(..) { - if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) { - if let Some(new) = account { - existing.merge_with(new); - continue; - } - } - cache.accounts.insert(address, account); - } - } - - /// Clear the cache. - pub fn clear_cache(&mut self) { - self.cache_overlay.clear(); - let mut cache = self.account_cache.lock(); - cache.accounts.clear(); + /// Add a local cache entry. + /// The entry will be propagated to the global cache in `sync_cache`. + /// `modified` indicates that the entry was changed since being read from disk or global cache. + /// `data` can be set to an existing (`Some`), or non-existing account (`None`). + pub fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool) { + self.local_cache.push(CacheQueueItem { + address: addr, + account: data, + modified: modified, + }) } /// Get basic copy of the cached account. Does not include storage. - /// Returns 'None' if the state is non-canonical and cache is disabled - /// or if the account is not cached. + /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached_account(&self, addr: &Address) -> Option> { - if !self.is_canon { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { return None; } - let mut cache = self.account_cache.lock(); cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic())) } /// Get value from a cached account. - /// Returns 'None' if the state is non-canonical and cache is disabled - /// or if the account is not cached. + /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached(&self, a: &Address, f: F) -> Option where F: FnOnce(Option<&mut Account>) -> U { - if !self.is_canon { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(a, &self.parent_hash, &cache.modifications) { return None; } - let mut cache = self.account_cache.lock(); cache.accounts.get_mut(a).map(|c| f(c.as_mut())) } + + /// Check if the account can be returned from cache by matching current block parent hash against canonical + /// state and filtering out account modified in later blocks. + fn is_allowed(addr: &Address, parent_hash: &Option, modifications: &VecDeque) -> bool { + let mut parent = match *parent_hash { + None => { + trace!("Cache lookup skipped for {:?}: no parent hash", addr); + return false; + } + Some(ref parent) => parent, + }; + if modifications.is_empty() { + return true; + } + // Ignore all accounts modified in later blocks + // Modifications contains block ordered by the number + // We search for our parent in that list first and then for + // all its parent until we hit the canonical block, + // checking against all the intermediate modifications. + let mut iter = modifications.iter(); + while let Some(ref m) = iter.next() { + if &m.hash == parent { + if m.is_canon { + return true; + } + parent = &m.parent; + } + if m.accounts.contains(addr) { + trace!("Cache lookup skipped for {:?}: modified in a later block", addr); + return false; + } + } + trace!("Cache lookup skipped for {:?}: parent hash is unknown", addr); + return false; + } +} + +#[cfg(test)] +mod tests { + +use util::{U256, H256, FixedHash, Address, DBTransaction}; +use tests::helpers::*; +use state::Account; +use util::log::init_log; + +#[test] +fn state_db_smoke() { + init_log(); + + let mut state_db_result = get_temp_state_db(); + let state_db = state_db_result.take(); + let root_parent = H256::random(); + let address = Address::random(); + let h0 = H256::random(); + let h1a = H256::random(); + let h1b = H256::random(); + let h2a = H256::random(); + let h2b = H256::random(); + let h3a = H256::random(); + let h3b = H256::random(); + let mut batch = DBTransaction::new(state_db.journal_db().backing()); + + // blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ] + // balance [ 5 5 4 3 2 2 ] + let mut s = state_db.boxed_clone_canon(&root_parent); + s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false); + s.commit(&mut batch, 0, &h0, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h0); + s.commit(&mut batch, 1, &h1a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h0); + s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true); + s.commit(&mut batch, 1, &h1b, None).unwrap(); + s.sync_cache(&[], &[], false); + + let mut s = state_db.boxed_clone_canon(&h1b); + s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true); + s.commit(&mut batch, 2, &h2b, None).unwrap(); + s.sync_cache(&[], &[], false); + + let mut s = state_db.boxed_clone_canon(&h1a); + s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true); + s.commit(&mut batch, 2, &h2a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h2a); + s.commit(&mut batch, 3, &h3a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let s = state_db.boxed_clone_canon(&h3a); + assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5)); + + let s = state_db.boxed_clone_canon(&h1a); + assert!(s.get_cached_account(&address).is_none()); + + let s = state_db.boxed_clone_canon(&h2b); + assert!(s.get_cached_account(&address).is_none()); + + let s = state_db.boxed_clone_canon(&h1b); + assert!(s.get_cached_account(&address).is_none()); + + // reorg to 3b + // blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ] + let mut s = state_db.boxed_clone_canon(&h2b); + s.commit(&mut batch, 3, &h3b, None).unwrap(); + s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true); + let s = state_db.boxed_clone_canon(&h3a); + assert!(s.get_cached_account(&address).is_none()); +} } diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 59e3699acc7..067f28d3918 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -57,7 +57,11 @@ fn should_return_registrar() { IoChannel::disconnected(), &db_config ).unwrap(); - assert_eq!(client.additional_params().get("registrar"), Some(&"52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d".to_owned())); + let params = client.additional_params(); + let address = params.get("registrar").unwrap(); + + assert_eq!(address.len(), 40); + assert!(U256::from_str(address).is_ok()); } #[test] diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index db36a37621f..4157e486dfe 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -16,4 +16,5 @@ pub mod helpers; mod client; +#[cfg(feature="ipc")] mod rpc; diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs index d5d88c08760..b021e750d24 100644 --- a/ethcore/src/tests/rpc.rs +++ b/ethcore/src/tests/rpc.rs @@ -19,7 +19,8 @@ use nanoipc; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; -use client::{Client, BlockChainClient, ClientConfig, RemoteClient, BlockID}; +use client::{Client, BlockChainClient, ClientConfig, BlockID}; +use client::remote::RemoteClient; use tests::helpers::*; use devtools::*; use miner::Miner; diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index b608ad68578..2cf14828a3b 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -256,16 +256,6 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { return; } - // at first, let's insert new block traces - { - let mut traces = self.traces.write(); - // it's important to use overwrite here, - // cause this value might be queried by hash later - batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); - // note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection - self.note_used(CacheID::Trace(request.block_hash.clone())); - } - // now let's rebuild the blooms if !request.enacted.is_empty() { let range_start = request.block_number as Number + 1 - request.enacted.len(); @@ -276,12 +266,25 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { // all traces are expected to be found here. That's why `expect` has been used // instead of `filter_map`. If some traces haven't been found, it meens that // traces database is corrupted or incomplete. - .map(|block_hash| self.traces(block_hash).expect("Traces database is incomplete.")) - .map(|block_traces| block_traces.bloom()) + .map(|block_hash| if block_hash == &request.block_hash { + request.traces.bloom() + } else { + self.traces(block_hash).expect("Traces database is incomplete.").bloom() + }) .map(blooms::Bloom::from) .map(Into::into) .collect(); + // insert new block traces into the cache and the database + { + let mut traces = self.traces.write(); + // it's important to use overwrite here, + // cause this value might be queried by hash later + batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); + // note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection + self.note_used(CacheID::Trace(request.block_hash.clone())); + } + let chain = BloomGroupChain::new(self.bloom_config, self); let trace_blooms = chain.replace(&replaced_range, enacted_blooms); let blooms_to_insert = trace_blooms.into_iter() diff --git a/ethcore/src/types/filter.rs b/ethcore/src/types/filter.rs index 6274d63f430..e3487e5f60f 100644 --- a/ethcore/src/types/filter.rs +++ b/ethcore/src/types/filter.rs @@ -22,7 +22,7 @@ use client::BlockID; use log_entry::LogEntry; /// Blockchain Filter. -#[derive(Binary)] +#[derive(Binary, Debug, PartialEq)] pub struct Filter { /// Blockchain will be searched from this block. pub from_block: BlockID, diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 3016412ebb4..e4d3b91c660 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -76,15 +76,14 @@ impl DiskDirectory { .map(|entry| entry.path()) .collect::>(); - let files: Result, _> = paths.iter() - .map(fs::File::open) - .collect(); - - let files = try!(files); - - files.into_iter() - .map(json::KeyFile::load) - .zip(paths.into_iter()) + paths + .iter() + .map(|p| ( + fs::File::open(p) + .map_err(Error::from) + .and_then(|r| json::KeyFile::load(r).map_err(|e| Error::Custom(format!("{:?}", e)))), + p + )) .map(|(file, path)| match file { Ok(file) => Ok((path.clone(), SafeAccount::from_file( file, Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned()) diff --git a/ipc/codegen/src/lib.rs b/ipc/codegen/src/lib.rs index 94959b058a8..dc58c6a8a91 100644 --- a/ipc/codegen/src/lib.rs +++ b/ipc/codegen/src/lib.rs @@ -56,7 +56,7 @@ pub fn expand(src: &std::path::Path, dst: &std::path::Path) { } #[cfg(feature = "with-syntex")] -pub fn register(reg: &mut syntex::Registry) { +pub fn register_cleaner(reg: &mut syntex::Registry) { use syntax::{ast, fold}; #[cfg(feature = "with-syntex")] @@ -66,6 +66,7 @@ pub fn register(reg: &mut syntex::Registry) { fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { match attr.node.value.node { ast::MetaItemKind::List(ref n, _) if n == &"ipc" => { return None; } + ast::MetaItemKind::Word(ref n) if n == &"ipc" => { return None; } _ => {} } @@ -80,13 +81,18 @@ pub fn register(reg: &mut syntex::Registry) { fold::Folder::fold_crate(&mut StripAttributeFolder, krate) } + reg.add_post_expansion_pass(strip_attributes); +} + +#[cfg(feature = "with-syntex")] +pub fn register(reg: &mut syntex::Registry) { reg.add_attr("feature(custom_derive)"); reg.add_attr("feature(custom_attribute)"); reg.add_decorator("ipc", codegen::expand_ipc_implementation); reg.add_decorator("derive_Binary", serialization::expand_serialization_implementation); - reg.add_post_expansion_pass(strip_attributes); + register_cleaner(reg); } #[cfg(not(feature = "with-syntex"))] @@ -104,7 +110,34 @@ pub fn register(reg: &mut rustc_plugin::Registry) { } #[derive(Debug)] -pub enum Error { InvalidFileName, ExpandFailure } +pub enum Error { InvalidFileName, ExpandFailure, Io(std::io::Error) } + +impl std::convert::From for Error { + fn from(err: std::io::Error) -> Self { + Error::Io(err) + } +} + +pub fn derive_ipc_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { + if has_feature { derive_ipc(src_path) } + else { cleanup_ipc(src_path) } +} + +pub fn cleanup_ipc(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let mut registry = syntex::Registry::new(); + register_cleaner(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &Path::new(&out_dir).join(&file_name)) + { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + Ok(()) +} pub fn derive_ipc(src_path: &str) -> Result<(), Error> { use std::env; @@ -113,11 +146,11 @@ pub fn derive_ipc(src_path: &str) -> Result<(), Error> { let out_dir = env::var_os("OUT_DIR").unwrap(); let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let final_path = Path::new(&out_dir).join(&file_name); + let mut intermediate_file_name = file_name.clone(); intermediate_file_name.push_str(".rpc.in"); - let intermediate_path = Path::new(&out_dir).join(&intermediate_file_name); - let final_path = Path::new(&out_dir).join(&file_name); { let mut registry = syntex::Registry::new(); diff --git a/ipc/hypervisor/Cargo.toml b/ipc/hypervisor/Cargo.toml index a4c462bd098..d730b9bcfc0 100644 --- a/ipc/hypervisor/Cargo.toml +++ b/ipc/hypervisor/Cargo.toml @@ -13,6 +13,7 @@ nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" } ethcore-ipc-nano = { path = "../nano" } semver = "0.2" log = "0.3" +time = "0.1" [build-dependencies] ethcore-ipc-codegen = { path = "../codegen" } diff --git a/ipc/hypervisor/src/lib.rs b/ipc/hypervisor/src/lib.rs index 78b8b04cee2..c7543ca91b3 100644 --- a/ipc/hypervisor/src/lib.rs +++ b/ipc/hypervisor/src/lib.rs @@ -22,6 +22,7 @@ extern crate ethcore_ipc as ipc; extern crate ethcore_ipc_nano as nanoipc; extern crate semver; #[macro_use] extern crate log; +extern crate time; pub mod service; @@ -187,23 +188,40 @@ impl Hypervisor { } /// Waits for every required module to check in - pub fn wait_for_shutdown(&self) { + pub fn wait_for_shutdown(&self) -> bool { + use time::{PreciseTime, Duration}; + let mut worker = self.ipc_worker.write().unwrap(); + let start = PreciseTime::now(); while !self.modules_shutdown() { - worker.poll() + worker.poll(); + if start.to(PreciseTime::now()) > Duration::seconds(30) { + warn!("Some modules failed to shutdown gracefully, they will be terminated."); + break; + } } + self.modules_shutdown() } /// Shutdown the ipc and all managed child processes pub fn shutdown(&self) { let mut childs = self.processes.write().unwrap(); - for (ref mut module, _) in childs.iter_mut() { + for (ref module, _) in childs.iter() { trace!(target: "hypervisor", "Stopping process module: {}", module); self.service.send_shutdown(**module); } trace!(target: "hypervisor", "Waiting for shutdown..."); - self.wait_for_shutdown(); - trace!(target: "hypervisor", "All modules reported shutdown"); + if self.wait_for_shutdown() { + trace!(target: "hypervisor", "All modules reported shutdown"); + return; + } + + for (ref module, ref mut process) in childs.iter_mut() { + if self.service.is_running(**module) { + process.kill().unwrap(); + trace!("Terminated {}", module); + } + } } } diff --git a/ipc/hypervisor/src/service.rs.in b/ipc/hypervisor/src/service.rs.in index 6996765ec88..e80a1ec30b1 100644 --- a/ipc/hypervisor/src/service.rs.in +++ b/ipc/hypervisor/src/service.rs.in @@ -39,7 +39,6 @@ pub struct ModuleState { shutdown: bool, } - #[ipc] pub trait ControlService { fn shutdown(&self) -> bool; @@ -106,6 +105,10 @@ impl HypervisorService { self.modules.read().unwrap().iter().filter(|&(_, module)| module.started && !module.shutdown).count() } + pub fn is_running(&self, id: IpcModuleId) -> bool { + self.modules.read().unwrap().get(&id).map(|module| module.started && !module.shutdown).unwrap_or(false) + } + pub fn send_shutdown(&self, module_id: IpcModuleId) { let modules = self.modules.read().unwrap(); modules.get(&module_id).map(|module| { diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index a411e6767b5..2363f1740fa 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,7 +67,8 @@ usd_per_eth = "auto" price_update_period = "hourly" gas_floor_target = "4700000" gas_cap = "6283184" -tx_queue_size = 1024 +tx_queue_size = 2048 +tx_queue_gas = "auto" tx_gas_limit = "6283184" extra_data = "Parity" remove_solved = false diff --git a/parity/cli/config.toml b/parity/cli/config.toml index a5ad55d403e..4ab6916797c 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -41,6 +41,7 @@ reseal_on_txs = "all" reseal_min_period = 4000 price_update_period = "hourly" tx_queue_size = 2048 +tx_queue_gas = "auto" [footprint] tracing = "on" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 082dbe8e4eb..10348b21b3e 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -193,8 +193,10 @@ usage! { or |c: &Config| otry!(c.mining).gas_cap.clone(), flag_extra_data: Option = None, or |c: &Config| otry!(c.mining).extra_data.clone().map(Some), - flag_tx_queue_size: usize = 1024usize, + flag_tx_queue_size: usize = 2048usize, or |c: &Config| otry!(c.mining).tx_queue_size.clone(), + flag_tx_queue_gas: String = "auto", + or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), flag_notify_work: Option = None, @@ -348,6 +350,7 @@ struct Mining { gas_cap: Option, extra_data: Option, tx_queue_size: Option, + tx_queue_gas: Option, remove_solved: Option, notify_work: Option>, } @@ -522,7 +525,8 @@ mod tests { flag_gas_floor_target: "4700000".into(), flag_gas_cap: "6283184".into(), flag_extra_data: Some("Parity".into()), - flag_tx_queue_size: 1024usize, + flag_tx_queue_size: 2048usize, + flag_tx_queue_gas: "auto".into(), flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -673,6 +677,7 @@ mod tests { gas_floor_target: None, gas_cap: None, tx_queue_size: Some(2048), + tx_queue_gas: Some("auto".into()), tx_gas_limit: None, extra_data: None, remove_solved: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 861b7dafcca..ca75c9ee034 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -44,7 +44,8 @@ Account Options: ACCOUNTS is a comma-delimited list of addresses. Implies --no-signer. (default: {flag_unlock:?}) --password FILE Provide a file containing a password for unlocking - an account. (default: {flag_password:?}) + an account. Leading and trailing whitespace is trimmed. + (default: {flag_password:?}) --keys-iterations NUM Specify the number of iterations to use when deriving key from the password (bigger is more secure) (default: {flag_keys_iterations}). @@ -183,6 +184,10 @@ Sealing/Mining Options: more than 32 characters. (default: {flag_extra_data:?}) --tx-queue-size LIMIT Maximum amount of transactions in the queue (waiting to be included in next block) (default: {flag_tx_queue_size}). + --tx-queue-gas LIMIT Maximum amount of total gas for external transactions in + the queue. LIMIT can be either an amount of gas or + 'auto' or 'off'. 'auto' sets the limit to be 2x + the current block gas limit. (default: {flag_tx_queue_gas}). --remove-solved Move solved blocks from the work package queue instead of cloning them. This gives a slightly faster import speed, but means that extra solutions diff --git a/parity/configuration.rs b/parity/configuration.rs index 811ba6097f4..54a72fab50f 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -30,7 +30,7 @@ use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; use cache::CacheConfig; use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, -geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address}; +geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use ethcore_logger::Config as LogConfig; use dir::Directories; @@ -125,7 +125,7 @@ impl Configuration { ImportFromGethAccounts { to: dirs.keys, testnet: self.args.flag_testnet - } + } ); Cmd::Account(account_cmd) } else if self.args.cmd_wallet { @@ -348,6 +348,7 @@ impl Configuration { None => U256::max_value(), }, tx_queue_size: self.args.flag_tx_queue_size, + tx_queue_gas_limit: try!(to_gas_limit(&self.args.flag_tx_queue_gas)), pending_set: try!(to_pending_set(&self.args.flag_relay_set)), reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period), work_queue_size: self.args.flag_work_queue_size, diff --git a/parity/helpers.rs b/parity/helpers.rs index abdd5daa53f..6f4f9095323 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -22,7 +22,7 @@ use std::fs::File; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig}; -use ethcore::miner::PendingSet; +use ethcore::miner::{PendingSet, GasLimit}; use cache::CacheConfig; use dir::DatabaseDirectories; use upgrade::upgrade; @@ -93,6 +93,14 @@ pub fn to_pending_set(s: &str) -> Result { } } +pub fn to_gas_limit(s: &str) -> Result { + match s { + "auto" => Ok(GasLimit::Auto), + "off" => Ok(GasLimit::None), + other => Ok(GasLimit::Fixed(try!(to_u256(other)))), + } +} + pub fn to_address(s: Option) -> Result { match s { Some(ref a) => clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)), @@ -273,9 +281,10 @@ pub fn password_prompt() -> Result { pub fn password_from_file

(path: P) -> Result where P: AsRef { let mut file = try!(File::open(path).map_err(|_| "Unable to open password file.")); let mut file_content = String::new(); - try!(file.read_to_string(&mut file_content).map_err(|_| "Unable to read password file.")); - // remove eof - Ok((&file_content[..file_content.len() - 1]).to_owned()) + match file.read_to_string(&mut file_content) { + Ok(_) => Ok(file_content.trim().into()), + Err(_) => Err("Unable to read password file.".into()), + } } /// Reads passwords from files. Treats each line as a separate password. @@ -294,10 +303,13 @@ pub fn passwords_from_files(files: Vec) -> Result, String> { #[cfg(test)] mod tests { use std::time::Duration; + use std::fs::File; + use std::io::Write; + use devtools::RandomTempPath; use util::{U256}; use ethcore::client::{Mode, BlockID}; use ethcore::miner::PendingSet; - use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes}; + use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes, password_from_file}; #[test] fn test_to_duration() { @@ -380,6 +392,14 @@ mod tests { ); } + #[test] + fn test_password() { + let path = RandomTempPath::new(); + let mut file = File::create(path.as_path()).unwrap(); + file.write_all(b"a bc ").unwrap(); + assert_eq!(password_from_file(path).unwrap().as_bytes(), b"a bc"); + } + #[test] #[cfg_attr(feature = "dev", allow(float_cmp))] fn test_to_price() { diff --git a/parity/main.rs b/parity/main.rs index b74af7b3d87..e0d6dfe3671 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -196,6 +196,9 @@ fn sync_main() -> bool { } fn main() { + // Always print backtrace on panic. + ::std::env::set_var("RUST_BACKTRACE", "1"); + if sync_main() { return; } diff --git a/parity/modules.rs b/parity/modules.rs index 53cef474161..39e05a293ec 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -68,8 +68,9 @@ pub type SyncModules = (Arc, Arc, Arc) #[cfg(feature="ipc")] mod ipc_deps { - pub use ethsync::{SyncClient, NetworkManagerClient, ServiceConfiguration}; - pub use ethcore::client::ChainNotifyClient; + pub use ethsync::remote::{SyncClient, NetworkManagerClient}; + pub use ethsync::ServiceConfiguration; + pub use ethcore::client::remote::ChainNotifyClient; pub use hypervisor::{SYNC_MODULE_ID, BootArgs, HYPERVISOR_IPC_URL}; pub use nanoipc::{GuardedSocket, NanoSocket, generic_client, fast_client}; pub use ipc::IpcSocket; diff --git a/parity/params.rs b/parity/params.rs index faba029b2b8..ee3038ebfac 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -206,7 +206,7 @@ impl Default for MinerExtras { extra_data: version_data(), gas_floor_target: U256::from(4_700_000), gas_ceil_target: U256::from(6_283_184), - transactions_limit: 1024, + transactions_limit: 2048, } } } diff --git a/parity/sync.rs b/parity/sync.rs index 85f7715469f..25f900b7865 100644 --- a/parity/sync.rs +++ b/parity/sync.rs @@ -19,8 +19,9 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use hypervisor::{SYNC_MODULE_ID, HYPERVISOR_IPC_URL, ControlService}; -use ethcore::client::{RemoteClient, ChainNotify}; -use ethcore::snapshot::{RemoteSnapshotService}; +use ethcore::client::ChainNotify; +use ethcore::client::remote::RemoteClient; +use ethcore::snapshot::remote::RemoteSnapshotService; use ethsync::{SyncProvider, EthSync, ManageNetwork, ServiceConfiguration}; use modules::service_urls; use boot; diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 885ec08f0b3..0d7902897af 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -21,7 +21,7 @@ macro_rules! rpc_unimplemented { } use std::fmt; -use ethcore::error::Error as EthcoreError; +use ethcore::error::{Error as EthcoreError, CallError}; use ethcore::account_provider::{Error as AccountError}; use fetch::FetchError; use jsonrpc_core::{Error, ErrorCode, Value}; @@ -34,6 +34,7 @@ mod codes { pub const NO_NEW_WORK: i64 = -32003; pub const UNKNOWN_ERROR: i64 = -32009; pub const TRANSACTION_ERROR: i64 = -32010; + pub const EXECUTION_ERROR: i64 = -32015; pub const ACCOUNT_LOCKED: i64 = -32020; pub const PASSWORD_INVALID: i64 = -32021; pub const ACCOUNT_ERROR: i64 = -32023; @@ -109,6 +110,14 @@ pub fn invalid_params(param: &str, details: T) -> Error { } } +pub fn execution(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::EXECUTION_ERROR), + message: "Transaction execution error.".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + pub fn state_pruned() -> Error { Error { code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), @@ -189,13 +198,13 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { AlreadyImported => "Transaction with the same hash was already imported.".into(), Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), TooCheapToReplace => { - "Transaction fee is too low. There is another transaction with same nonce in the queue. Try increasing the fee or incrementing the nonce.".into() + "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() }, LimitReached => { "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() }, InsufficientGasPrice { minimal, got } => { - format!("Transaction fee is too low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", minimal, got) + format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) }, InsufficientBalance { balance, cost } => { format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) @@ -219,4 +228,10 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { } } - +pub fn from_call_error(error: CallError) -> Error { + match error { + CallError::StatePruned => state_pruned(), + CallError::Execution(e) => execution(e), + CallError::TransactionNotFound => internal("{}, this should not be the case with eth_call, most likely a bug.", CallError::TransactionNotFound), + } +} diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index b174e406ef6..c13229222fa 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -33,7 +33,7 @@ use util::{FromHex, Mutex}; use rlp::{self, UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID}; -use ethcore::header::Header as BlockHeader; +use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; @@ -198,8 +198,8 @@ impl EthClient where } } -pub fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { - let receipts = miner.pending_receipts(); +pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFilter) -> Vec where M: MinerService { + let receipts = miner.pending_receipts(best_block); let pending_logs = receipts.into_iter() .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) @@ -426,7 +426,8 @@ impl Eth for EthClient where try!(self.active()); let hash: H256 = hash.into(); let miner = take_weak!(self.miner); - Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(&hash).map(Into::into))) + let client = take_weak!(self.client); + Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) } fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { @@ -445,8 +446,9 @@ impl Eth for EthClient where try!(self.active()); let miner = take_weak!(self.miner); + let best_block = take_weak!(self.client).chain_info().best_block_number; let hash: H256 = hash.into(); - match (miner.pending_receipt(&hash), self.options.allow_pending_receipt_query) { + match (miner.pending_receipt(best_block, &hash), self.options.allow_pending_receipt_query) { (Some(receipt), true) => Ok(Some(receipt.into())), _ => { let client = take_weak!(self.client); @@ -488,7 +490,8 @@ impl Eth for EthClient where .collect::>(); if include_pending { - let pending = pending_logs(&*take_weak!(self.miner), &filter); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending = pending_logs(&*take_weak!(self.miner), best_block, &filter); logs.extend(pending); } @@ -590,7 +593,10 @@ impl Eth for EthClient where num => take_weak!(self.client).call(&signed, num.into(), Default::default()), }; - Ok(r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]))) + match r { + Ok(b) => Ok(Bytes(b.output)), + Err(e) => Err(errors::from_call_error(e)), + } } fn estimate_gas(&self, request: CallRequest, num: Trailing) -> Result { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index 03d9d72159c..dd1c937ac43 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -81,7 +81,8 @@ impl EthFilter for EthFilterClient try!(self.active()); let mut polls = self.polls.lock(); - let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(best_block); let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); Ok(id.into()) } @@ -108,7 +109,8 @@ impl EthFilter for EthFilterClient }, PollFilter::PendingTransaction(ref mut previous_hashes) => { // get hashes of pending transactions - let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let current_hashes = take_weak!(self.miner).pending_transactions_hashes(best_block); let new_hashes = { @@ -149,7 +151,8 @@ impl EthFilter for EthFilterClient // additionally retrieve pending logs if include_pending { - let pending_logs = pending_logs(&*take_weak!(self.miner), &filter); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending_logs = pending_logs(&*take_weak!(self.miner), best_block, &filter); // remove logs about which client was already notified about let new_pending_logs: Vec<_> = pending_logs.iter() @@ -190,7 +193,8 @@ impl EthFilter for EthFilterClient .collect::>(); if include_pending { - logs.extend(pending_logs(&*take_weak!(self.miner), &filter)); + let best_block = take_weak!(self.client).chain_info().best_block_number; + logs.extend(pending_logs(&*take_weak!(self.miner), best_block, &filter)); } let logs = limit_logs(logs, filter.limit); diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 448fa473438..97e4d3bea5c 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; use ethcore::ethereum; -use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet}; +use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, GasLimit}; use ethcore::account_provider::AccountProvider; use devtools::RandomTempPath; use util::Hashable; @@ -58,6 +58,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { reseal_on_own_tx: true, tx_queue_size: 1024, tx_gas_limit: !U256::zero(), + tx_queue_gas_limit: GasLimit::None, pending_set: PendingSet::SealingOrElseQueue, reseal_min_period: Duration::from_secs(0), work_queue_size: 50, diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ddc0b057b14..0787f2102fd 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -21,6 +21,7 @@ use util::standard::*; use ethcore::error::{Error, CallError}; use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics}; use ethcore::block::{ClosedBlock, IsBlock}; +use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult}; @@ -162,7 +163,7 @@ impl MinerService for TestMinerService { } /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self) -> Vec { + fn pending_transactions_hashes(&self, _best_block: BlockNumber) -> Vec { vec![] } @@ -186,7 +187,7 @@ impl MinerService for TestMinerService { Some(f(&open_block.close())) } - fn transaction(&self, hash: &H256) -> Option { + fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { self.pending_transactions.lock().get(hash).cloned() } @@ -194,13 +195,13 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().values().cloned().collect() } - fn pending_transactions(&self) -> Vec { + fn pending_transactions(&self, _best_block: BlockNumber) -> Vec { self.pending_transactions.lock().values().cloned().collect() } - fn pending_receipt(&self, hash: &H256) -> Option { + fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option { // Not much point implementing this since the logic is complex and the only thing it relies on is pending_receipts, which is already tested. - self.pending_receipts().get(hash).map(|r| + self.pending_receipts(0).get(hash).map(|r| RichReceipt { transaction_hash: Default::default(), transaction_index: Default::default(), @@ -212,7 +213,7 @@ impl MinerService for TestMinerService { ) } - fn pending_receipts(&self) -> BTreeMap { + fn pending_receipts(&self, _best_block: BlockNumber) -> BTreeMap { self.pending_receipts.lock().clone() } diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs index b4a45272ba6..fc163c54be8 100644 --- a/rpc/src/v1/types/filter.rs +++ b/rpc/src/v1/types/filter.rs @@ -85,8 +85,14 @@ impl Into for Filter { VariadicValue::Null => None, VariadicValue::Single(t) => Some(vec![t.into()]), VariadicValue::Multiple(t) => Some(t.into_iter().map(Into::into).collect()) - }).filter_map(|m| m).collect()).into_iter(); - vec![iter.next(), iter.next(), iter.next(), iter.next()] + }).collect()).into_iter(); + + vec![ + iter.next().unwrap_or(None), + iter.next().unwrap_or(None), + iter.next().unwrap_or(None), + iter.next().unwrap_or(None) + ] }, limit: self.limit, } @@ -121,6 +127,8 @@ mod tests { use util::hash::*; use super::*; use v1::types::BlockNumber; + use ethcore::filter::Filter as EthFilter; + use ethcore::client::BlockID; #[test] fn topic_deserialization() { @@ -148,4 +156,33 @@ mod tests { limit: None, }); } + + #[test] + fn filter_conversion() { + let filter = Filter { + from_block: Some(BlockNumber::Earliest), + to_block: Some(BlockNumber::Latest), + address: Some(VariadicValue::Multiple(vec![])), + topics: Some(vec![ + VariadicValue::Null, + VariadicValue::Single("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b".into()), + VariadicValue::Null, + ]), + limit: None, + }; + + let eth_filter: EthFilter = filter.into(); + assert_eq!(eth_filter, EthFilter { + from_block: BlockID::Earliest, + to_block: BlockID::Latest, + address: Some(vec![]), + topics: vec![ + None, + Some(vec!["000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b".into()]), + None, + None, + ], + limit: None, + }); + } } diff --git a/sync/build.rs b/sync/build.rs index cdb717e0e0c..c465d5e34d3 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -17,5 +17,5 @@ extern crate ethcore_ipc_codegen; fn main() { - ethcore_ipc_codegen::derive_ipc("src/api.rs").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index beaa49c603a..ae2092f2572 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -184,8 +184,8 @@ impl BlockCollection { { let mut blocks = Vec::new(); let mut head = self.head; - while head.is_some() { - head = self.parents.get(&head.unwrap()).cloned(); + while let Some(h) = head { + head = self.parents.get(&h).cloned(); if let Some(head) = head { match self.blocks.get(&head) { Some(block) if block.body.is_some() => { @@ -201,7 +201,7 @@ impl BlockCollection { for block in blocks.drain(..) { let mut block_rlp = RlpStream::new_list(3); block_rlp.append_raw(&block.header, 1); - let body = Rlp::new(block.body.as_ref().unwrap()); // incomplete blocks are filtered out in the loop above + let body = Rlp::new(block.body.as_ref().expect("blocks contains only full blocks; qed")); block_rlp.append_raw(body.at(0).as_raw(), 1); block_rlp.append_raw(body.at(1).as_raw(), 1); drained.push(block_rlp.out()); diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 565c5382791..446fd549994 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -90,7 +90,6 @@ use util::*; use rlp::*; use network::*; -use std::mem::{replace}; use ethcore::views::{HeaderView, BlockView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError}; @@ -123,6 +122,7 @@ const MAX_ROUND_PARENTS: usize = 32; const MAX_NEW_HASHES: usize = 64; const MAX_TX_TO_IMPORT: usize = 512; const MAX_NEW_BLOCK_AGE: BlockNumber = 20; +const MAX_TRANSACTION_SIZE: usize = 300*1024; const STATUS_PACKET: u8 = 0x00; const NEW_BLOCK_HASHES_PACKET: u8 = 0x01; @@ -143,7 +143,7 @@ const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13; const SNAPSHOT_DATA_PACKET: u8 = 0x14; const HEADERS_TIMEOUT_SEC: f64 = 15f64; -const BODIES_TIMEOUT_SEC: f64 = 5f64; +const BODIES_TIMEOUT_SEC: f64 = 10f64; const FORK_HEADER_TIMEOUT_SEC: f64 = 3f64; const SNAPSHOT_MANIFEST_TIMEOUT_SEC: f64 = 3f64; const SNAPSHOT_DATA_TIMEOUT_SEC: f64 = 10f64; @@ -249,8 +249,6 @@ struct PeerInfo { network_id: U256, /// Peer best block hash latest_hash: H256, - /// Peer best block number if known - latest_number: Option, /// Peer total difficulty if known difficulty: Option, /// Type of data currenty being requested from peer. @@ -395,6 +393,8 @@ impl ChainSync { } self.syncing_difficulty = From::from(0u64); self.state = SyncState::Idle; + // Reactivate peers only if some progress has been made + // since the last sync round of if starting fresh. self.active_peers = self.peers.keys().cloned().collect(); } @@ -406,7 +406,8 @@ impl ChainSync { self.continue_sync(io); } - /// Remove peer from active peer set + /// Remove peer from active peer set. Peer will be reactivated on the next sync + /// round. fn deactivate_peer(&mut self, io: &mut SyncIo, peer_id: PeerId) { trace!(target: "sync", "Deactivating peer {}", peer_id); self.active_peers.remove(&peer_id); @@ -443,7 +444,6 @@ impl ChainSync { network_id: try!(r.val_at(1)), difficulty: Some(try!(r.val_at(2))), latest_hash: try!(r.val_at(3)), - latest_number: None, genesis: try!(r.val_at(4)), asking: PeerAsking::Nothing, asking_blocks: Vec::new(), @@ -480,7 +480,11 @@ impl ChainSync { } self.peers.insert(peer_id.clone(), peer); - self.active_peers.insert(peer_id.clone()); + // Don't activate peer immediatelly when searching for common block. + // Let the current sync round complete first. + if self.state != SyncState::ChainHead { + self.active_peers.insert(peer_id.clone()); + } debug!(target: "sync", "Connected {}:{}", peer_id, io.peer_info(peer_id)); if let Some((fork_block, _)) = self.fork_block { self.request_headers_by_number(io, peer_id, fork_block, 1, 0, false, PeerAsking::ForkHeader); @@ -496,7 +500,8 @@ impl ChainSync { let confirmed = match self.peers.get_mut(&peer_id) { Some(ref mut peer) if peer.asking == PeerAsking::ForkHeader => { let item_count = r.item_count(); - if item_count == 0 || (item_count == 1 && try!(r.at(0)).as_raw().sha3() == self.fork_block.unwrap().1) { + if item_count == 0 || (item_count == 1 && + try!(r.at(0)).as_raw().sha3() == self.fork_block.expect("ForkHeader state is only entered when fork_block is some; qed").1) { peer.asking = PeerAsking::Nothing; if item_count == 0 { trace!(target: "sync", "{}: Chain is too short to confirm the block", peer_id); @@ -562,7 +567,7 @@ impl ChainSync { continue; } - if self.highest_block == None || number > self.highest_block.unwrap() { + if self.highest_block.as_ref().map_or(true, |n| number > *n) { self.highest_block = Some(number); } let hash = info.hash(); @@ -594,9 +599,9 @@ impl ChainSync { } if headers.is_empty() { - // Peer does not have any new subchain heads, deactivate it nd try with another + // Peer does not have any new subchain heads, deactivate it and try with another. trace!(target: "sync", "{} Disabled for no data", peer_id); - io.disable_peer(peer_id); + self.deactivate_peer(io, peer_id); } match self.state { SyncState::ChainHead => { @@ -675,9 +680,9 @@ impl ChainSync { } let mut unknown = false; { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = header.hash(); - peer.latest_number = Some(header.number()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = header.hash(); + } } if self.last_imported_block > header.number() && self.last_imported_block - header.number() > MAX_NEW_BLOCK_AGE { trace!(target: "sync", "Ignored ancient new block {:?}", h); @@ -770,9 +775,9 @@ impl ChainSync { new_hashes.push(hash.clone()); if number > max_height { trace!(target: "sync", "New unknown block hash {:?}", hash); - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = hash.clone(); - peer.latest_number = Some(number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = hash.clone(); + } max_height = number; } }, @@ -942,19 +947,22 @@ impl ChainSync { return; } let (peer_latest, peer_difficulty, peer_snapshot_number, peer_snapshot_hash) = { - let peer = self.peers.get_mut(&peer_id).unwrap(); - if peer.asking != PeerAsking::Nothing || !peer.can_sync() { - return; - } - if self.state == SyncState::Waiting { - trace!(target: "sync", "Waiting for the block queue"); - return; - } - if self.state == SyncState::SnapshotWaiting { - trace!(target: "sync", "Waiting for the snapshot restoration"); + if let Some(ref peer) = self.peers.get_mut(&peer_id) { + if peer.asking != PeerAsking::Nothing || !peer.can_sync() { + return; + } + if self.state == SyncState::Waiting { + trace!(target: "sync", "Waiting for the block queue"); + return; + } + if self.state == SyncState::SnapshotWaiting { + trace!(target: "sync", "Waiting for the snapshot restoration"); + return; + } + (peer.latest_hash.clone(), peer.difficulty.clone(), peer.snapshot_number.as_ref().cloned(), peer.snapshot_hash.as_ref().cloned()) + } else { return; } - (peer.latest_hash.clone(), peer.difficulty.clone(), peer.snapshot_number.as_ref().cloned(), peer.snapshot_hash.as_ref().cloned()) }; let chain_info = io.chain().chain_info(); let td = chain_info.pending_total_difficulty; @@ -1042,14 +1050,18 @@ impl ChainSync { // check to see if we need to download any block bodies first let needed_bodies = self.blocks.needed_bodies(MAX_BODIES_TO_REQUEST, ignore_others); if !needed_bodies.is_empty() { - replace(&mut self.peers.get_mut(&peer_id).unwrap().asking_blocks, needed_bodies.clone()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_blocks = needed_bodies.clone(); + } self.request_bodies(io, peer_id, needed_bodies); return; } // find subchain to download if let Some((h, count)) = self.blocks.needed_headers(MAX_HEADERS_TO_REQUEST, ignore_others) { - replace(&mut self.peers.get_mut(&peer_id).unwrap().asking_blocks, vec![h.clone()]); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_blocks = vec![h.clone()]; + } self.request_headers_by_hash(io, peer_id, &h, count, 0, false, PeerAsking::BlockHeaders); } } @@ -1059,34 +1071,37 @@ impl ChainSync { self.clear_peer_download(peer_id); // find chunk data to download if let Some(hash) = self.snapshot.needed_chunk() { - self.peers.get_mut(&peer_id).unwrap().asking_snapshot_data = Some(hash.clone()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_snapshot_data = Some(hash.clone()); + } self.request_snapshot_chunk(io, peer_id, &hash); } } /// Clear all blocks/headers marked as being downloaded by a peer. fn clear_peer_download(&mut self, peer_id: PeerId) { - let peer = self.peers.get_mut(&peer_id).unwrap(); - match peer.asking { - PeerAsking::BlockHeaders | PeerAsking::Heads => { - for b in &peer.asking_blocks { - self.blocks.clear_header_download(b); - } - }, - PeerAsking::BlockBodies => { - for b in &peer.asking_blocks { - self.blocks.clear_body_download(b); - } - }, - PeerAsking::SnapshotData => { - if let Some(hash) = peer.asking_snapshot_data { - self.snapshot.clear_chunk_download(&hash); - } - }, - _ => (), + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + match peer.asking { + PeerAsking::BlockHeaders | PeerAsking::Heads => { + for b in &peer.asking_blocks { + self.blocks.clear_header_download(b); + } + }, + PeerAsking::BlockBodies => { + for b in &peer.asking_blocks { + self.blocks.clear_body_download(b); + } + }, + PeerAsking::SnapshotData => { + if let Some(hash) = peer.asking_snapshot_data { + self.snapshot.clear_chunk_download(&hash); + } + }, + _ => (), + } + peer.asking_blocks.clear(); + peer.asking_snapshot_data = None; } - peer.asking_blocks.clear(); - peer.asking_snapshot_data = None; } fn block_imported(&mut self, hash: &H256, number: BlockNumber, parent: &H256) { @@ -1211,30 +1226,34 @@ impl ChainSync { /// Reset peer status after request is complete. fn reset_peer_asking(&mut self, peer_id: PeerId, asking: PeerAsking) -> bool { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.expired = false; - if peer.asking != asking { - trace!(target:"sync", "Asking {:?} while expected {:?}", peer.asking, asking); - peer.asking = PeerAsking::Nothing; + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.expired = false; + if peer.asking != asking { + trace!(target:"sync", "Asking {:?} while expected {:?}", peer.asking, asking); + peer.asking = PeerAsking::Nothing; + false + } + else { + peer.asking = PeerAsking::Nothing; + true + } + } else { false } - else { - peer.asking = PeerAsking::Nothing; - true - } } /// Generic request sender fn send_request(&mut self, sync: &mut SyncIo, peer_id: PeerId, asking: PeerAsking, packet_id: PacketId, packet: Bytes) { - let peer = self.peers.get_mut(&peer_id).unwrap(); - if peer.asking != PeerAsking::Nothing { - warn!(target:"sync", "Asking {:?} while requesting {:?}", peer.asking, asking); - } - peer.asking = asking; - peer.ask_time = time::precise_time_s(); - if let Err(e) = sync.send(peer_id, packet_id, packet) { - debug!(target:"sync", "Error sending request: {:?}", e); - sync.disable_peer(peer_id); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if peer.asking != PeerAsking::Nothing { + warn!(target:"sync", "Asking {:?} while requesting {:?}", peer.asking, asking); + } + peer.asking = asking; + peer.ask_time = time::precise_time_s(); + if let Err(e) = sync.send(peer_id, packet_id, packet) { + debug!(target:"sync", "Error sending request: {:?}", e); + sync.disable_peer(peer_id); + } } } @@ -1261,7 +1280,12 @@ impl ChainSync { item_count = min(item_count, MAX_TX_TO_IMPORT); let mut transactions = Vec::with_capacity(item_count); for i in 0 .. item_count { - let tx = try!(r.at(i)).as_raw().to_vec(); + let rlp = try!(r.at(i)); + if rlp.as_raw().len() > MAX_TRANSACTION_SIZE { + debug!("Skipped oversized transaction of {} bytes", rlp.as_raw().len()); + continue; + } + let tx = rlp.as_raw().to_vec(); transactions.push(tx); } io.chain().queue_transactions(transactions); @@ -1604,7 +1628,7 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).unwrap(), 1); + rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); rlp_stream.append(&chain.chain_info().total_difficulty); rlp_stream.out() } @@ -1618,25 +1642,23 @@ impl ChainSync { } /// returns peer ids that have less blocks than our chain - fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec<(PeerId, BlockNumber)> { + fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { let latest_hash = chain_info.best_block_hash; - let latest_number = chain_info.best_block_number; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { - if peer_info.latest_number.is_none() { - peer_info.latest_number = Some(HeaderView::new(&io.chain().block_header(BlockID::Hash(peer_info.latest_hash.clone())).unwrap()).number()); + if peer_info.latest_hash != latest_hash { + Some(id) + } else { + None } - if peer_info.latest_hash != latest_hash && latest_number > peer_info.latest_number.unwrap() { - Some((id, peer_info.latest_number.unwrap())) - } else { None } }, _ => None }) .collect::>() } - fn select_random_lagging_peers(&mut self, peers: &[(PeerId, BlockNumber)]) -> Vec<(PeerId, BlockNumber)> { + fn select_random_lagging_peers(&mut self, peers: &[PeerId]) -> Vec { use rand::Rng; // take sqrt(x) peers let mut peers = peers.to_vec(); @@ -1649,46 +1671,42 @@ impl ChainSync { } /// propagates latest block to lagging peers - fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[(PeerId, BlockNumber)]) -> usize { + fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewBlocks to {:?}", peers); let mut sent = 0; - for &(peer_id, _) in peers { + for peer_id in peers { if sealed.is_empty() { let rlp = ChainSync::create_latest_block_rlp(io.chain()); - self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } else { for h in sealed { let rlp = ChainSync::create_new_block_rlp(io.chain(), h); - self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } } - self.peers.get_mut(&peer_id).unwrap().latest_hash = chain_info.best_block_hash.clone(); - self.peers.get_mut(&peer_id).unwrap().latest_number = Some(chain_info.best_block_number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = chain_info.best_block_hash.clone(); + } sent += 1; } sent } /// propagates new known hashes to all peers - fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[(PeerId, BlockNumber)]) -> usize { + fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewHashes to {:?}", peers); let mut sent = 0; - let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())).unwrap()).parent_hash(); - for &(peer_id, peer_number) in peers { - let peer_best = if chain_info.best_block_number - peer_number > MAX_PEER_LAG_PROPAGATION as BlockNumber { - // If we think peer is too far behind just send one latest hash - last_parent.clone() - } else { - self.peers.get(&peer_id).unwrap().latest_hash.clone() - }; - sent += match ChainSync::create_new_hashes_rlp(io.chain(), &peer_best, &chain_info.best_block_hash) { + let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())) + .expect("Best block always exists")).parent_hash(); + for peer_id in peers { + sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { Some(rlp) => { { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = chain_info.best_block_hash.clone(); - peer.latest_number = Some(chain_info.best_block_number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = chain_info.best_block_hash.clone(); + } } - self.send_packet(io, peer_id, NEW_BLOCK_HASHES_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_HASHES_PACKET, rlp); 1 }, None => 0 @@ -2001,7 +2019,6 @@ mod tests { genesis: H256::zero(), network_id: U256::zero(), latest_hash: peer_latest_hash, - latest_number: None, difficulty: None, asking: PeerAsking::Nothing, asking_blocks: Vec::new(), diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 0fa7d9f42b9..d8dc710f5cb 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -64,3 +64,9 @@ pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNet ServiceConfiguration, NetworkConfiguration}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; + +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use api::{SyncClient, NetworkManagerClient}; +} diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index d8d3d0711be..c54529beb1a 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -95,6 +95,27 @@ fn forked() { assert_eq!(&*net.peer(2).chain.numbers.read(), &peer1_chain); } +#[test] +fn forked_with_misbehaving_peer() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + // peer 0 is on a totally different chain with higher total difficulty + net.peer_mut(0).chain = TestBlockChainClient::new_with_extra_data(b"fork".to_vec()); + net.peer_mut(0).chain.add_blocks(500, EachBlockWith::Nothing); + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Nothing); + + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); + // peer 1 should sync to peer 2, others should not change + let peer0_chain = net.peer(0).chain.numbers.read().clone(); + let peer2_chain = net.peer(2).chain.numbers.read().clone(); + net.sync(); + assert_eq!(&*net.peer(0).chain.numbers.read(), &peer0_chain); + assert_eq!(&*net.peer(1).chain.numbers.read(), &peer2_chain); + assert_eq!(&*net.peer(2).chain.numbers.read(), &peer2_chain); +} + #[test] fn net_hard_fork() { ::env_logger::init().ok(); @@ -116,11 +137,12 @@ fn net_hard_fork() { #[test] fn restart() { + ::env_logger::init().ok(); let mut net = TestNet::new(3); net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); - net.sync_steps(8); + net.sync(); // make sure that sync has actually happened assert!(net.peer(0).chain.chain_info().best_block_number > 100); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index cbed49eff84..3558e557839 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -29,6 +29,7 @@ pub struct TestIo<'p> { pub snapshot_service: &'p TestSnapshotService, pub queue: &'p mut VecDeque, pub sender: Option, + pub to_disconnect: HashSet, } impl<'p> TestIo<'p> { @@ -37,16 +38,19 @@ impl<'p> TestIo<'p> { chain: chain, snapshot_service: ss, queue: queue, - sender: sender + sender: sender, + to_disconnect: HashSet::new(), } } } impl<'p> SyncIo for TestIo<'p> { - fn disable_peer(&mut self, _peer_id: PeerId) { + fn disable_peer(&mut self, peer_id: PeerId) { + self.disconnect_peer(peer_id); } - fn disconnect_peer(&mut self, _peer_id: PeerId) { + fn disconnect_peer(&mut self, peer_id: PeerId) { + self.to_disconnect.insert(peer_id); } fn is_expired(&self) -> bool { @@ -150,13 +154,30 @@ impl TestNet { pub fn sync_step(&mut self) { for peer in 0..self.peers.len() { if let Some(packet) = self.peers[peer].queue.pop_front() { - let mut p = self.peers.get_mut(packet.recipient).unwrap(); - trace!("--- {} -> {} ---", peer, packet.recipient); - ChainSync::dispatch_packet(&p.sync, &mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)), peer as PeerId, packet.packet_id, &packet.data); - trace!("----------------"); + let disconnecting = { + let mut p = self.peers.get_mut(packet.recipient).unwrap(); + trace!("--- {} -> {} ---", peer, packet.recipient); + let to_disconnect = { + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + ChainSync::dispatch_packet(&p.sync, &mut io, peer as PeerId, packet.packet_id, &packet.data); + io.to_disconnect + }; + for d in &to_disconnect { + // notify this that disconnecting peers are disconnecting + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(*d)); + p.sync.write().on_peer_aborting(&mut io, *d); + } + to_disconnect + }; + for d in &disconnecting { + // notify other peers that this peer is disconnecting + let mut p = self.peers.get_mut(*d).unwrap(); + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + p.sync.write().on_peer_aborting(&mut io, peer as PeerId); + } } - let mut p = self.peers.get_mut(peer).unwrap(); - p.sync.write().maintain_sync(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, None)); + + self.sync_step_peer(peer); } } diff --git a/util/bigint/Cargo.toml b/util/bigint/Cargo.toml index ee25ce846a0..c21b5239fdd 100644 --- a/util/bigint/Cargo.toml +++ b/util/bigint/Cargo.toml @@ -4,7 +4,7 @@ homepage = "http://ethcore.io" repository = "https://github.com/ethcore/parity" license = "GPL-3.0" name = "ethcore-bigint" -version = "0.1.0" +version = "0.1.1" authors = ["Ethcore "] build = "build.rs" diff --git a/util/io/src/lib.rs b/util/io/src/lib.rs index b2a16e19bb2..082192dfaf0 100644 --- a/util/io/src/lib.rs +++ b/util/io/src/lib.rs @@ -68,6 +68,8 @@ mod panics; use mio::{EventLoop, Token}; use std::fmt; +pub use worker::LOCAL_STACK_SIZE; + #[derive(Debug)] /// IO Error pub enum IoError { diff --git a/util/io/src/worker.rs b/util/io/src/worker.rs index 0176c467cad..f4f63919f14 100644 --- a/util/io/src/worker.rs +++ b/util/io/src/worker.rs @@ -22,9 +22,19 @@ use crossbeam::sync::chase_lev; use service::{HandlerId, IoChannel, IoContext}; use IoHandler; use panics::*; +use std::cell::Cell; use std::sync::{Condvar as SCondvar, Mutex as SMutex}; +const STACK_SIZE: usize = 16*1024*1024; + +thread_local! { + /// Stack size + /// Should be modified if it is changed in Rust since it is no way + /// to know or get it + pub static LOCAL_STACK_SIZE: Cell = Cell::new(::std::env::var("RUST_MIN_STACK").ok().and_then(|s| s.parse().ok()).unwrap_or(2 * 1024 * 1024)); +} + pub enum WorkType { Readable, Writable, @@ -66,8 +76,9 @@ impl Worker { deleting: deleting.clone(), wait_mutex: wait_mutex.clone(), }; - worker.thread = Some(thread::Builder::new().name(format!("IO Worker #{}", index)).spawn( + worker.thread = Some(thread::Builder::new().stack_size(STACK_SIZE).name(format!("IO Worker #{}", index)).spawn( move || { + LOCAL_STACK_SIZE.with(|val| val.set(STACK_SIZE)); panic_handler.catch_panic(move || { Worker::work_loop(stealer, channel.clone(), wait, wait_mutex.clone(), deleting) }).unwrap() diff --git a/util/network/src/host.rs b/util/network/src/host.rs index f530503c6d1..d982481f9cd 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -591,7 +591,8 @@ impl Host { } fn handshake_count(&self) -> usize { - self.sessions.read().count() - self.session_count() + // session_count < total_count is possible because of the data race. + self.sessions.read().count().saturating_sub(self.session_count()) } fn keep_alive(&self, io: &IoContext) { From 214916a4141ce5c0da3d0ebb71b8318593d17ab7 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 11 Oct 2016 18:38:05 +0100 Subject: [PATCH 040/382] new vote counter --- .../src/engines/tendermint/vote_collector.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ethcore/src/engines/tendermint/vote_collector.rs diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs new file mode 100644 index 00000000000..bcf347053eb --- /dev/null +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -0,0 +1,37 @@ +// 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 . + +//! Collects votes on hashes at each height and round. + +use super::vote::Vote; +use super::{Height, Round, BlockHash}; +use common::{HashSet, HashMap, RwLock, H256, Address, Error, Hashable}; +use ethkey::{Signature, recover}; + +/// Signed voting on hashes. +#[derive(Debug)] +pub struct VoteCollector { + /// Structure for storing all votes. + votes: RwLock>>, +} + +impl VoteCollector { + pub fn new() -> VoteCollector { + VoteCollector { votes: RwLock::new(HashMap::new()) } + } + + pub fn vote() {} +} From 4e3655089064e35bb6f8c7ce379dd3a464c489a0 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 8 Nov 2016 18:01:31 +0000 Subject: [PATCH 041/382] message revamp --- ethcore/src/engines/tendermint/message.rs | 120 +++++++++++++--------- 1 file changed, 74 insertions(+), 46 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index b14a07c1e21..69f414ec822 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -16,67 +16,95 @@ //! Tendermint message handling. -use super::{Height, Round, BlockHash}; +use std::cmp::Ordering; +use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug)] -pub enum ConsensusMessage { - Prevote(Height, Round, BlockHash), - Precommit(Height, Round, BlockHash), - Commit(Height, BlockHash), +#[derive(Debug, PartialEq, Eq)] +pub enum Step { + Prevote, + Precommit } -/// (height, step, ...) +#[derive(Debug, PartialEq, Eq)] +pub struct ConsensusMessage { + signature: H520, + height: Height, + round: Round, + step: Step, + block_hash: BlockHash +} + +impl PartialOrd for ConsensusMessage { + fn partial_cmp(&self, m: &ConsensusMessage) -> Option { + Some(self.cmp(m)) + } +} + +impl Ord for ConsensusMessage { + fn cmp(&self, m: &ConsensusMessage) -> Ordering { + if self.height != m.height { + self.height.cmp(&m.height) + } else if self.round != m.round { + self.round.cmp(&m.round) + } else if self.step != m.step { + match self.step { + Step::Prevote => Ordering::Less, + Step::Precommit => Ordering::Greater, + } + } else { + self.block_hash.cmp(&m.block_hash) + } + } +} + +impl Decodable for Step { + fn decode(decoder: &D) -> Result where D: Decoder { + match try!(decoder.as_rlp().as_val()) { + 0u8 => Ok(Step::Prevote), + 1 => Ok(Step::Precommit), + _ => Err(DecoderError::Custom("Unknown step.")), + } + } +} + + +impl Encodable for Step { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + Step::Prevote => s.append(&0u8), + Step::Precommit => s.append(&1u8), + _ => panic!("No encoding needed for other steps"), + }; + } +} + +/// (signature, height, round, step, block_hash) impl Decodable for ConsensusMessage { fn decode(decoder: &D) -> Result where D: Decoder { - // Handle according to step. let rlp = decoder.as_rlp(); if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - let height = try!(rlp.val_at(0)); - Ok(match try!(rlp.val_at(1)) { - 0u8 => ConsensusMessage::Prevote( - height, - try!(rlp.val_at(2)), - try!(rlp.val_at(3)) - ), - 1 => ConsensusMessage::Precommit( - height, - try!(rlp.val_at(2)), - try!(rlp.val_at(3)) - ), - 2 => ConsensusMessage::Commit( - height, - try!(rlp.val_at(2))), - _ => return Err(DecoderError::Custom("Unknown step.")), + let m = rlp.at(1); + Ok(ConsensusMessage { + signature: try!(rlp.val_at(0)), + height: try!(m.val_at(0)), + round: try!(m.val_at(1)), + step: try!(m.val_at(2)), + block_hash: try!(m.val_at(3)) }) } } impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { - match *self { - ConsensusMessage::Prevote(h, r, hash) => { - s.begin_list(4); - s.append(&h); - s.append(&0u8); - s.append(&r); - s.append(&hash); - }, - ConsensusMessage::Precommit(h, r, hash) => { - s.begin_list(4); - s.append(&h); - s.append(&1u8); - s.append(&r); - s.append(&hash); - }, - ConsensusMessage::Commit(h, hash) => { - s.begin_list(3); - s.append(&h); - s.append(&2u8); - s.append(&hash); - }, - } + s.begin_list(2); + s.append(&self.signature); + s.begin_list(4); + s.append(&self.height); + s.append(&self.round); + s.append(&self.step); + s.append(&self.block_hash); } } From 4bb4ed95516e8f8e31af774120fb434a6bf07855 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 13 Nov 2016 15:52:33 +0100 Subject: [PATCH 042/382] Initial checking. --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/src/client/client.rs | 62 +++++++++++++++++++++++++++++++++++- ethcore/src/lib.rs | 1 + util/src/misc.rs | 5 +++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c3fa096b3f1..b53b2842661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,6 +282,7 @@ dependencies = [ "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 667b40ace7f..43926379427 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -41,6 +41,7 @@ ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } lru-cache = "0.1.0" ethcore-bloom-journal = { path = "../util/bloom" } +ethabi = "0.2.2" [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ec59e01cf4d..400d53ed110 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -27,6 +27,7 @@ use util::{journaldb, TrieFactory, Trie}; use util::trie::TrieSpec; use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::kvdb::*; +use util::misc::code_hash; // other use io::*; @@ -42,7 +43,7 @@ use env_info::LastHashes; use verification; use verification::{PreverifiedBlock, Verifier}; use block::*; -use transaction::{LocalizedTransaction, SignedTransaction, Action}; +use transaction::{LocalizedTransaction, SignedTransaction, Transaction, Action}; use blockchain::extras::TransactionAddress; use types::filter::Filter; use types::mode::Mode as IpcMode; @@ -68,6 +69,7 @@ use factory::Factories; use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; +use ethabi::{Interface, Contract, Token}; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -634,10 +636,18 @@ impl Client { /// Tick the client. // TODO: manage by real events. pub fn tick(&self) { + self.check_garbage(); + self.check_snooze(); + self.check_updates(); + } + + fn check_garbage(&self) { self.chain.read().collect_garbage(); self.block_queue.collect_garbage(); self.tracedb.read().collect_garbage(); + } + fn check_snooze(&self) { let mode = self.mode.lock().clone(); match mode { Mode::Dark(timeout) => { @@ -671,6 +681,56 @@ impl Client { } } + fn call_contract(&self, address: Address, data: Bytes) -> Result { + let from = Address::default(); + let transaction = Transaction { + nonce: self.latest_nonce(&from), + action: Action::Call(address), + gas: U256::from(50_000_000), + gas_price: U256::default(), + value: U256::default(), + data: data, + }.fake_sign(from); + + self.call(&transaction, BlockID::Latest, Default::default()) + .map_err(|e| format!("{:?}", e)) + .map(|executed| { + executed.output + }) + } + + fn check_updates(&self) { + let operations_json = Interface::load(include_bytes!("../../res/Operations.json")).expect("Operations.json is valid ABI"); + let operations = Contract::new(operations_json); + + fn as_string(e: T) -> String { + format!("{:?}", e) + } + + let res = || { + let is_latest = try!(operations.function("isLatest".into()).map_err(as_string)); + let params = try!(is_latest.encode_call( + vec![Token::String("par".into()), Token::Address(code_hash().0)] + ).map_err(as_string)); + let output = try!(self.call_contract("0x4c1783B4FfB1A99eFC4cda632aA990F5138b26f1".into(), params)); + let result = try!(is_latest.decode_output(output).map_err(as_string)); + + match result.get(0) { + Some(&Token::Bool(answer)) => Ok(answer), + e => Err(format!("Invalid result: {:?}", e)), + } + }; + + match res() { + Ok(res) => { + info!("isLatest returned {}", res); + }, + Err(e) => { + warn!(target: "dapps", "Error while calling Operations.isLatest: {:?}", e); + } + } + } + /// Look up the block number for the given block ID. pub fn block_number(&self, id: BlockID) -> Option { match id { diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index bf3e591718d..59e45f381db 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -117,6 +117,7 @@ extern crate lru_cache; #[cfg(feature = "jit" )] extern crate evmjit; +extern crate ethabi; pub extern crate ethstore; diff --git a/util/src/misc.rs b/util/src/misc.rs index b0452e85e74..d9eab1af08a 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -32,6 +32,11 @@ pub enum Filth { Dirty, } +/// Get the (SHA1?) 160-bit hash of this build's code base. +pub fn code_hash() -> H160 { + sha().into() +} + /// Get the standard version string for this software. pub fn version() -> String { let sha3 = short_sha(); From 2fa2f8342ac5ecaa7793efda07dd910b569937e1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 14 Nov 2016 11:49:56 +0100 Subject: [PATCH 043/382] isLatest works. --- ethcore/res/Operations.json | 1 + ethcore/src/client/client.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 ethcore/res/Operations.json diff --git a/ethcore/res/Operations.json b/ethcore/res/Operations.json new file mode 100644 index 00000000000..573a45169ad --- /dev/null +++ b/ethcore/res/Operations.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"owners","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"forks","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"}],"name":"addRelease","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"clients","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"gas","type":"uint256"}],"name":"TransactionProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":false,"name":"success","type":"bool"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"},{"indexed":true,"name":"name","type":"bytes32"},{"indexed":false,"name":"spec","type":"bytes32"}],"name":"ForkProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkAcceptedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkRejectedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRatified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"forkBlock","type":"uint32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":false,"name":"track","type":"uint8"},{"indexed":false,"name":"semver","type":"uint24"}],"name":"ReleaseAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":true,"name":"platform","type":"bytes32"},{"indexed":false,"name":"checksum","type":"bytes32"}],"name":"ChecksumAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"ClientAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"}],"name":"ClientRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"now","type":"address"}],"name":"ClientOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"now","type":"bool"}],"name":"ClientRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old","type":"address"},{"indexed":false,"name":"now","type":"address"}],"name":"OwnerChanged","type":"event"}] \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 400d53ed110..83f1f3709d0 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -22,7 +22,7 @@ use std::time::{Instant}; use time::precise_time_ns; // util -use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock}; +use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, ToPretty}; use util::{journaldb, TrieFactory, Trie}; use util::trie::TrieSpec; use util::{U256, H256, Address, H2048, Uint, FixedHash}; @@ -710,8 +710,9 @@ impl Client { let res = || { let is_latest = try!(operations.function("isLatest".into()).map_err(as_string)); let params = try!(is_latest.encode_call( - vec![Token::String("par".into()), Token::Address(code_hash().0)] + vec![Token::FixedBytes(b"par"[..].to_owned()), Token::Address(code_hash().0)] ).map_err(as_string)); + println!("params: {}", params.pretty()); let output = try!(self.call_contract("0x4c1783B4FfB1A99eFC4cda632aA990F5138b26f1".into(), params)); let result = try!(is_latest.decode_output(output).map_err(as_string)); From 55a5402bf525328693ae1786f87b198a9a3ae844 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:20:14 +0000 Subject: [PATCH 044/382] simplify messages --- ethcore/src/client/client.rs | 12 +---- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint/message.rs | 60 ++++++++++++++--------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f7cf5fecce2..44bd0a743a3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1172,16 +1172,8 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - let full_rlp = UntrustedRlp::new(&message); - if let Ok(signature) = full_rlp.val_at::(0) { - if let Ok(message) = full_rlp.at(1) { - if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), signature, message) - { - self.notify(|notify| notify.broadcast(new_message.clone())); - } - } - } + if let Ok(new_message) = self.engine.handle_message(UntrustedRlp::new(&message)) { + self.notify(|notify| notify.broadcast(new_message.clone())); } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index f03e765a963..d7f0796a5d7 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -151,7 +151,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _sender: Address, _signature: H520, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // 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. diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 69f414ec822..4563c683e78 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -16,23 +16,31 @@ //! Tendermint message handling. -use std::cmp::Ordering; +use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug, PartialEq, Eq)] -pub enum Step { - Prevote, - Precommit -} - #[derive(Debug, PartialEq, Eq)] pub struct ConsensusMessage { - signature: H520, + pub signature: H520, height: Height, round: Round, - step: Step, - block_hash: BlockHash + pub step: Step, + block_hash: Option +} + +impl ConsensusMessage { + fn is_round(&self, height: Height, round: Round) -> bool { + self.height == height && self.round == round + } + + fn is_step(&self, height: Height, round: Round, step: Step) -> bool { + self.height == height && self.round == round && self.step == step + } + + pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { + self.height == height && self.round == round && self.block_hash == block_hash + } } impl PartialOrd for ConsensusMessage { @@ -41,6 +49,17 @@ impl PartialOrd for ConsensusMessage { } } +impl Step { + fn number(&self) -> i8 { + match *self { + Step::Propose => -1, + Step::Prevote => 0, + Step::Precommit => 1, + Step::Commit => 2, + } + } +} + impl Ord for ConsensusMessage { fn cmp(&self, m: &ConsensusMessage) -> Ordering { if self.height != m.height { @@ -48,10 +67,7 @@ impl Ord for ConsensusMessage { } else if self.round != m.round { self.round.cmp(&m.round) } else if self.step != m.step { - match self.step { - Step::Prevote => Ordering::Less, - Step::Precommit => Ordering::Greater, - } + self.step.number().cmp(&m.step.number()) } else { self.block_hash.cmp(&m.block_hash) } @@ -71,11 +87,7 @@ impl Decodable for Step { impl Encodable for Step { fn rlp_append(&self, s: &mut RlpStream) { - match *self { - Step::Prevote => s.append(&0u8), - Step::Precommit => s.append(&1u8), - _ => panic!("No encoding needed for other steps"), - }; + s.append(&(self.number() as u8)); } } @@ -86,13 +98,17 @@ impl Decodable for ConsensusMessage { if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - let m = rlp.at(1); + let m = try!(rlp.at(1)); + let block_message: H256 = try!(m.val_at(3)); Ok(ConsensusMessage { signature: try!(rlp.val_at(0)), height: try!(m.val_at(0)), round: try!(m.val_at(1)), step: try!(m.val_at(2)), - block_hash: try!(m.val_at(3)) + block_hash: match block_message.is_zero() { + true => None, + false => Some(block_message), + } }) } } @@ -105,6 +121,6 @@ impl Encodable for ConsensusMessage { s.append(&self.height); s.append(&self.round); s.append(&self.step); - s.append(&self.block_hash); + s.append(&self.block_hash.unwrap_or(H256::zero())); } } From dd8ed42270b3abb973ea888a166363dfcef63f87 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:20:42 +0000 Subject: [PATCH 045/382] update timeouts --- ethcore/src/engines/tendermint/timeout.rs | 41 +++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 47840d8b7ed..52d1bd2d05b 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -26,12 +26,6 @@ pub struct TimerHandler { engine: Weak, } -impl TimerHandler { - pub fn new(engine: Weak) -> Self { - TimerHandler { engine: engine } - } -} - /// Base timeout of each step in ms. #[derive(Debug, Clone)] pub struct DefaultTimeouts { @@ -61,31 +55,36 @@ pub struct NextStep; pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { + fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { - println!("Timeout: {:?}", get_time()); - // Can you release entering a clause? - let next_step = match *engine.s.try_read().unwrap() { - Step::Propose => Step::Propose, - Step::Prevote(_) => Step::Propose, - Step::Precommit(_, _) => Step::Propose, - Step::Commit(_, _) => { - engine.r.fetch_add(1, AtomicOrdering::Relaxed); + engine.step.fetch_add(1, AtomicOrdering::SeqCst); + engine.proposed.store(false, AtomicOrdering::SeqCst); + let next_step = match *engine.step.try_read().unwrap() { + Step::Propose => Step::Prevote, + Step::Prevote => Step::Precommit, + Step::Precommit => Step::Propose, + Step::Commit => { + engine.round.fetch_add(1, AtomicOrdering::Relaxed); Step::Propose }, }; - match next_step { - Step::Propose => engine.to_propose(), - _ => (), + + if let Some(ref channel) = *engine.message_channel.lock() { + 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.next_timeout()).expect("Failed to restart consensus step timer.") + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) } } } From 54e495634560244f01de2e35df54c43649548e22 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:21:18 +0000 Subject: [PATCH 046/382] return errors from constructor --- ethcore/src/engines/tendermint/mod.rs | 226 ++++++++++++++------------ ethcore/src/spec/spec.rs | 2 +- 2 files changed, 126 insertions(+), 102 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 70ba24a091a..efaffb50117 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -19,11 +19,11 @@ mod message; mod timeout; mod params; -mod vote; mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; +use basic_types::Seal; use error::{Error, BlockError}; use header::Header; use builtin::Builtin; @@ -42,16 +42,14 @@ use io::IoService; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; -use self::vote::Vote; +use self::vote_collector::VoteCollector; -#[derive(Debug)] -enum Step { +#[derive(Debug, PartialEq, Eq)] +pub enum Step { Propose, - Prevote(ProposeCollect), - /// Precommit step storing the precommit vote and accumulating seal. - Precommit(ProposeCollect, Signatures), - /// Commit step storing a complete valid seal. - Commit(BlockHash, Signatures) + Prevote, + Precommit, + Commit } pub type Height = usize; @@ -69,43 +67,45 @@ pub struct Tendermint { timeout_service: IoService, /// Address to be used as authority. authority: RwLock

, + /// Blockchain height. + height: AtomicUsize, /// Consensus round. - r: AtomicUsize, + round: AtomicUsize, /// Consensus step. - s: RwLock, + step: RwLock, /// Current step timeout in ms. timeout: AtomicMs, /// Used to swith proposer. proposer_nonce: AtomicUsize, + /// Vote accumulator. + votes: VoteCollector } impl Tendermint { /// Create a new instance of Tendermint engine - pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Result, Error> { let engine = Arc::new( Tendermint { params: params, timeout: AtomicUsize::new(our_params.timeouts.propose), our_params: our_params, builtins: builtins, - timeout_service: IoService::::start().expect("Error creating engine timeout service"), + timeout_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), - r: AtomicUsize::new(0), - s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0) + height: AtomicUsize::new(0), + round: AtomicUsize::new(0), + step: RwLock::new(Step::Propose), + proposer_nonce: AtomicUsize::new(0), + votes: VoteCollector::new() }); - let handler = TimerHandler::new(Arc::downgrade(&engine)); - engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); - engine - } - - fn is_proposer(&self, address: &Address) -> bool { - self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + try!(engine.timeout_service.register_handler(Arc::new(handler))); + Ok(engine) } fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; - p.authorities.get(proposer_nonce%p.authority_n).unwrap() + p.authorities.get(proposer_nonce % p.authority_n).unwrap() } fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { @@ -123,12 +123,12 @@ impl Tendermint { } fn to_step(&self, step: Step) { - let mut guard = self.s.try_write().unwrap(); + let mut guard = self.step.try_write().unwrap(); *guard = step; } fn to_propose(&self) { - trace!(target: "tendermint", "step: entering propose"); + trace!(target: "poa", "step: entering propose"); println!("step: entering propose"); self.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); self.to_step(Step::Propose); @@ -136,7 +136,7 @@ impl Tendermint { fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.s.try_read().unwrap() { + match *self.step.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } @@ -146,7 +146,7 @@ impl Tendermint { } fn to_prevote(&self, proposal: BlockHash) { - trace!(target: "tendermint", "step: entering prevote"); + trace!(target: "poa", "step: entering prevote"); println!("step: entering prevote"); // Proceed to the prevote step. self.to_step(Step::Prevote(self.new_vote(proposal))); @@ -154,8 +154,8 @@ impl Tendermint { fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - let hash = match *self.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => { + let hash = match *self.step.try_write().unwrap() { + Step::Prevote => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); @@ -178,43 +178,39 @@ impl Tendermint { } fn to_precommit(&self, proposal: BlockHash) { - trace!(target: "tendermint", "step: entering precommit"); + trace!(target: "poa", "step: entering precommit"); println!("step: entering precommit"); self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); } - fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - // Check if message is for correct step. - match *self.s.try_write().unwrap() { - Step::Precommit(ref mut vote, ref mut seal) => { - // Vote and accumulate seal if message is about the right block. - if vote.hash == try!(message.as_val()) { - if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } - // Commit if precommit is won. - if vote.is_won() { self.to_commit(vote.hash.clone(), seal.clone()); } - Ok(message.as_raw().to_vec()) - } else { - try!(Err(EngineError::WrongVote)) - } - }, - _ => try!(Err(EngineError::WrongStep)), - } - } - /// Move to commit step, when valid block is known and being distributed. pub fn to_commit(&self, block_hash: H256, seal: Vec) { - trace!(target: "tendermint", "step: entering commit"); + trace!(target: "poa", "step: entering commit"); println!("step: entering commit"); self.to_step(Step::Commit(block_hash, seal)); } fn threshold(&self) -> usize { - self.our_params.authority_n*2/3 + self.our_params.authority_n * 2/3 } fn next_timeout(&self) -> u64 { self.timeout.load(AtomicOrdering::Relaxed) as u64 } + + /// Round proposer switching. + fn is_proposer(&self, address: &Address) -> bool { + self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + } + + fn is_current(&self, message: &ConsensusMessage) -> bool { + message.is_step(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), self.step.load(AtomicOrdering::SeqCst)) + } +} + +/// Block hash including the consensus round. +fn block_hash(header: &Header) -> H256 { + header.rlp(Seal::WithSome(1)).sha3() } impl Engine for Tendermint { @@ -253,60 +249,79 @@ impl Engine for Tendermint { } } - /// Set author to proposer and set the correct round in the seal. + /// Set the correct round in the seal. /// 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 on_close_block(&self, block: &mut ExecutedBlock) { + let round = self.round.load(AtomicOrdering::SeqCst); + vec![::rlp::encode(&round).to_vec()] + } + + /// Round proposer switching. + fn is_sealer(&self, address: &Address) -> Option { + Some(self.is_proposer(address)) } /// Attempt to seal the block internally using all available signatures. /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) { + if let (Some(ap), Some(step)) = (accounts, self.step.try_read()) { let header = block.header(); let author = header.author(); match *step { - Step::Commit(hash, ref seal) if hash == header.bare_hash() => + Step::Commit => { // Commit the block using a complete signature set. - return Some(seal.clone()), - Step::Propose if self.is_proposer(header.author()) => + let round = self.round.load(AtomicOrdering::SeqCst); + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, block_hash(header)).split_first() { + if votes.len() + 1 > self.threshold() { + return Some(vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(proposer).to_vec(), + ::rlp::encode(&votes).to_vec() + ]); + } + } + }, + Step::Propose if self.is_proposer(author) => // Seal block with propose signature. - if let Some(proposal) = Vote::propose(header, &ap) { - return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()]) + + if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + return Some(vec![ + ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&signature.into()).to_vec(), + Vec::new() + ]) } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + trace!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); }, _ => {}, } } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + trace!(target: "poa", "generate_seal: FAIL: accounts not provided"); } None } - fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - let message: ConsensusMessage = try!(message.as_val()); - - if self.is_authority(&sender) { - //match message { - // ConsensusMessage::Prevote - //} + fn handle_message(&self, rlp: UntrustedRlp) -> Result { + let message: ConsensusMessage = try!(rlp.as_val()); + let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); + // TODO: Do not admit old messages. + if !self.is_authority(&sender) { + try!(Err(BlockError::InvalidSeal)); } - try!(Err(EngineError::UnknownStep)) - - // Check if correct round. - //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { - // try!(Err(EngineError::WrongRound)) - //} - // Handle according to step. -// match try!(message.val_at(1)) { -// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), -// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))), -// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), -// _ => try!(Err(EngineError::UnknownStep)), -// } + // Check if the message affects the current step. + if self.is_current(message) { + match self.step.load(AtomicOrdering::SeqCst) { + Step::Prevote => { + let votes = aligned_signatures(message); + if votes.len() > self.threshold() { + } + }, + Step::Precommit => , + } + } + self.votes.vote(message, sender); } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -322,21 +337,28 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let to_address = |b: &Vec| { - let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); - Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) - }; - let authority_set = self.our_params.authorities.iter().cloned().collect(); - let seal_set = try!(header - .seal() - .iter() - .map(to_address) - .collect::, Error>>()); - if seal_set.intersection(&authority_set).count() <= self.threshold() { + let proposal_signature: H520 = try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()); + let proposer = public_to_address(&try!(recover(&proposal_signature.into(), &block_hash(header)))); + if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) - } else { - Ok(()) } + let proposal = ConsensusMessage { + signature: proposal_signature, + height: header.number() as usize, + round: try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()), + step: Step::Propose, + block_hash: Some(block_hash(header)) + }; + self.votes.vote(proposal, proposer); + let votes_rlp = UntrustedRlp::new(&header.seal()[2]); + for rlp in votes_rlp.iter() { + let sig: H520 = try!(rlp.as_val()); + let address = public_to_address(&try!(recover(&sig.into(), &block_hash(header)))); + if !self.our_params.authorities.contains(a) { + try!(Err(BlockError::InvalidSeal)) + } + } + Ok(()) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -368,7 +390,9 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len() + let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + new_signatures > best_signatures } } @@ -377,7 +401,7 @@ mod tests { use std::thread::sleep; use std::time::{Duration}; use util::*; - use rlp::{UntrustedRlp, RlpStream, Stream, View, encode}; + use rlp::{UntrustedRlp, RlpStream, Stream, View}; use block::*; use error::{Error, BlockError}; use header::Header; @@ -416,11 +440,11 @@ mod tests { let v0 = tap.insert_account("0".sha3(), "0").unwrap(); let sig0 = tap.sign(v0, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig0 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig0 as &[u8])).to_vec()); let v1 = tap.insert_account("1".sha3(), "1").unwrap(); let sig1 = tap.sign(v1, Some("1".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); seal } @@ -475,7 +499,7 @@ mod tests { let v1 = tap.insert_account("0".sha3(), "0").unwrap(); let sig1 = tap.sign(v1, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); header.set_seal(seal.clone()); @@ -484,7 +508,7 @@ mod tests { let v2 = tap.insert_account("101".sha3(), "101").unwrap(); let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig2 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig2 as &[u8])).to_vec()); header.set_seal(seal); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index e6ca8a8c4f8..b5303e513a9 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -144,7 +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::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins), + ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins).expect("Failed to start the Tendermint consensus engine."), } } From ff2dc5dd57e7c8d97af9573a4d28d825616ff5f5 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:21:49 +0000 Subject: [PATCH 047/382] vote counting --- .../src/engines/tendermint/vote_collector.rs | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 448a4b209b4..31a43c16936 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -16,22 +16,49 @@ //! Collects votes on hashes at each height and round. -use super::vote::Vote; -use super::{Height, Round}; +use util::*; +use super::message::ConsensusMessage; +use super::{Height, Round, Step}; use ethkey::recover; -use util::{RwLock, HashMap, HashSet}; -/// Signed voting on hashes. #[derive(Debug)] pub struct VoteCollector { - /// Structure for storing all votes. - votes: RwLock>>, + /// Storing all Proposals, Prevotes and Precommits. + votes: RwLock> } impl VoteCollector { pub fn new() -> VoteCollector { - VoteCollector { votes: RwLock::new(HashMap::new()) } + VoteCollector { votes: RwLock::new(BTreeMap::new()) } } - pub fn vote() {} + pub fn vote(&self, message: ConsensusMessage, voter: Address) { + if let Some(mut guard) = self.votes.write() { + *guard.insert(message, voter); + } + } + + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Vec { + self.votes + .read() + .keys() + // Get only Propose and Precommits. + .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) + .map(|m| m.signature) + .collect() + } + + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { + self.seal_signatures(message.height, message.round, message.block_hash) + } + + pub fn count_signatures(&self, height: Height, round: Round) -> usize { + self.votes + .read() + .keys() + // Get only Propose and Precommits. + .filter(|m| m.is_round(height, round) && m.step != Step::Prevote) + .map(|m| m.signature) + .collect() + } } From 06e5416537cb99eb76b9b87422e68c371f2c62d1 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 11:27:09 +0000 Subject: [PATCH 048/382] header fns, extra_info --- ethcore/src/engines/tendermint/mod.rs | 28 ++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index efaffb50117..4a789a08808 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -213,6 +213,14 @@ fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } +fn proposer_signature(header: &Header) -> H520 { + try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()) +} + +fn consensus_round(header: &Header) -> Round { + try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()) +} + impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } @@ -223,7 +231,14 @@ impl Engine for Tendermint { 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 extra_info(&self, header: &Header) -> BTreeMap { + map![ + "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "height".into() => header.number().to_string(), + "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "block_hash".into() => block_hash(header).as_ref().map(ToString::to_string).unwrap_or("".into()) + ] + } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { Schedule::new_homestead() @@ -253,7 +268,7 @@ impl Engine for Tendermint { /// 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 round = self.round.load(AtomicOrdering::SeqCst); - vec![::rlp::encode(&round).to_vec()] + block.header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); } /// Round proposer switching. @@ -272,7 +287,7 @@ impl Engine for Tendermint { Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, block_hash(header)).split_first() { + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))).split_first() { if votes.len() + 1 > self.threshold() { return Some(vec![ ::rlp::encode(&round).to_vec(), @@ -337,15 +352,14 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let proposal_signature: H520 = try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()); - let proposer = public_to_address(&try!(recover(&proposal_signature.into(), &block_hash(header)))); + let proposer = public_to_address(&try!(recover(&proposal_signature(header).into(), &block_hash(header)))); if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) } let proposal = ConsensusMessage { signature: proposal_signature, - height: header.number() as usize, - round: try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()), + height: header.number() as Height, + round: consensus_round(header), step: Step::Propose, block_hash: Some(block_hash(header)) }; From 7d0eafd5cde7f244e9695f362d0aa245ee1e392a Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 13:33:11 +0000 Subject: [PATCH 049/382] fix extra_info --- ethcore/src/engines/tendermint/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4a789a08808..e3ccb855b8c 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -233,10 +233,10 @@ impl Engine for Tendermint { /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { map![ - "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "signature".into() => proposer_signature(header).to_string(), "height".into() => header.number().to_string(), - "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "block_hash".into() => block_hash(header).as_ref().map(ToString::to_string).unwrap_or("".into()) + "round".into() => consensus_round(header).to_string(), + "block_hash".into() => block_hash(header).to_string().unwrap_or("".into()) ] } @@ -268,7 +268,7 @@ impl Engine for Tendermint { /// 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 round = self.round.load(AtomicOrdering::SeqCst); - block.header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); + block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); } /// Round proposer switching. From 1c958695c3adf77b3da55fc22857be8aab952f14 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 15:25:30 +0000 Subject: [PATCH 050/382] timeout loading --- ethcore/src/engines/tendermint/params.rs | 12 ++++-- ethcore/src/engines/tendermint/timeout.rs | 51 ++++++++++++++--------- json/src/spec/tendermint.rs | 12 ++++++ 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 904499c0467..4e3996c998d 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use super::timeout::DefaultTimeouts; +use super::timeout::TendermintTimeouts; use util::{Address, U256}; /// `Tendermint` params. @@ -30,7 +30,7 @@ pub struct TendermintParams { /// Number of authorities. pub authority_n: usize, /// Timeout durations for different steps. - pub timeouts: DefaultTimeouts, + pub timeouts: TendermintTimeouts, } impl Default for TendermintParams { @@ -54,7 +54,13 @@ impl From for TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), authorities: val, authority_n: val_n, - timeouts: DefaultTimeouts::default() + let dt = TendermintTimeouts::default(); + timeouts: TendermintTimeouts { + propose: p.timeout_propose.unwrap_or(dt.propose), + prevote: p.timeout_prevote.unwrap_or(dt.prevote), + precommit: p.timeout_precommit.unwrap_or(dt.precommit), + commit: p.timeout_commit.unwrap_or(dt.commit) + } } } } diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 52d1bd2d05b..f107d06eecf 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -20,7 +20,8 @@ use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; -use time::get_time; +use time::{get_time, Duration}; +use service::ClientIoMessage; pub struct TimerHandler { engine: Weak, @@ -28,41 +29,50 @@ pub struct TimerHandler { /// Base timeout of each step in ms. #[derive(Debug, Clone)] -pub struct DefaultTimeouts { - pub propose: Ms, - pub prevote: Ms, - pub precommit: Ms, - pub commit: Ms +pub struct TendermintTimeouts { + propose: Duration, + prevote: Duartion, + precommit: Duration, + commit: Duration } -impl Default for DefaultTimeouts { +impl TendermintTimeouts { + pub fn for_step(step: Step) -> Duration { + match step { + Step::Propose => self.propose, + Step::Prevote => self.prevote, + Step::Precommit => self.precommit, + Step::Commit => self.commit, + } + } +} + +impl Default for TendermintTimeouts { fn default() -> Self { DefaultTimeouts { - propose: 1000, - prevote: 1000, - precommit: 1000, - commit: 1000 + propose: Duration::milliseconds(1000), + prevote: Duration::milliseconds(1000), + precommit: Duration::milliseconds(1000), + commit: Duration::milliseconds(1000) } } } -pub type Ms = usize; - #[derive(Clone)] pub struct NextStep; /// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { + 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()) .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + 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); @@ -77,14 +87,17 @@ impl IoHandler for TimerHandler { }, }; + + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) + if let Some(ref channel) = *engine.message_channel.lock() { match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.), 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.next_timeout().as_millis()) - .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) + } } } diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 3d1a5a06d7b..6858602da71 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -27,6 +27,18 @@ pub struct TendermintParams { pub gas_limit_bound_divisor: Uint, /// Valid authorities pub authorities: Vec
, + /// Propose step timeout in milliseconds. + #[serde(rename="timeoutPropose")] + pub timeout_propose: Option, + /// Prevote step timeout in milliseconds. + #[serde(rename="timeoutPrevote")] + pub timeout_prevote: Option, + /// Precommit step timeout in milliseconds. + #[serde(rename="timeoutPrecommit")] + pub timeout_precommit: Option, + /// Commit step timeout in milliseconds. + #[serde(rename="timeoutCommit")] + pub timeout_commit: Option, } /// Tendermint engine deserialization. From 2fa34fd6a874f1fc8341f2fb162cebe5083cc0ae Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 12:43:21 +0000 Subject: [PATCH 051/382] step transition messaging --- ethcore/src/engines/tendermint/mod.rs | 24 +++++++++++---- ethcore/src/engines/tendermint/timeout.rs | 37 ++++++++++++----------- json/src/spec/mod.rs | 5 +-- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 558479ad50d..cc3283b5fac 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -38,13 +38,14 @@ use engines::{Engine, EngineError, ProposeCollect}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; -use io::IoService; +use io::{IoService, IoChannel}; +use service::ClientIoMessage; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; use self::vote_collector::VoteCollector; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Step { Propose, Prevote, @@ -64,7 +65,7 @@ pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - timeout_service: IoService, + step_service: IoService, /// Address to be used as authority. authority: RwLock
, /// Blockchain height. @@ -77,6 +78,8 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, + /// Proposed block held until seal is gathered. + proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>> } @@ -89,20 +92,30 @@ impl Tendermint { params: params, our_params: our_params, builtins: builtins, - timeout_service: try!(IoService::::start()), + step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), height: AtomicUsize::new(0), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), + proposed_block: Mutex::new(None), message_channel: Mutex::new(None) }); let handler = TimerHandler { engine: Arc::downgrade(&engine) }; - try!(engine.timeout_service.register_handler(Arc::new(handler))); + try!(engine.step_service.register_handler(Arc::new(handler))); Ok(engine) } + fn update_sealing(&self) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() @@ -191,7 +204,6 @@ impl Engine for Tendermint { } /// Set the correct round in the seal. - /// 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 round = self.round.load(AtomicOrdering::SeqCst); block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 9739861f49d..64a21a299c7 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -16,9 +16,10 @@ //! Tendermint timeout handling. +use util::Mutex; use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; -use io::{IoContext, IoHandler, TimerToken}; +use io::{IoContext, IoHandler, TimerToken, IoChannel}; use super::{Tendermint, Step}; use time::{get_time, Duration}; use service::ClientIoMessage; @@ -59,7 +60,7 @@ impl Default for TendermintTimeouts { } #[derive(Clone)] -pub struct NextStep; +pub struct NextStep(Step); /// Timer token representing the consensus step timeouts. pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; @@ -69,15 +70,6 @@ fn set_timeout(io: &IoContext, timeout: Duration) { .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -fn update_sealing(io_channel: Mutex>>) { - if let Some(ref channel) = *io_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for round {}.", engine.round.load(AtomicOrdering::SeqCst)), - Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for round {}.", err, engine.round.load(AtomicOrdering::SeqCst)), - } - } -} - impl IoHandler for TimerHandler { fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { @@ -112,18 +104,29 @@ impl IoHandler for TimerHandler { }; if let Some(step) = next_step { - *engine.step.write() = step + *engine.step.write() = step; + if step == Step::Propose { + engine.update_sealing(); + } } - } } } - fn message(&self, io: &IoContext, _net_message: &NextStep) { + fn message(&self, io: &IoContext, message: &NextStep) { if let Some(engine) = self.engine.upgrade() { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + io.clear_timer(ENGINE_TIMEOUT_TOKEN); + let NextStep(next_step) = *message; + *engine.step.write() = next_step; + match next_step { + Step::Propose => { + engine.update_sealing(); + set_timeout(io, engine.our_params.timeouts.propose) + }, + Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote), + Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), + Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), + }; } } } diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 741e847acf4..7fd123da406 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -39,8 +39,5 @@ pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; -<<<<<<< HEAD -pub use self::tendermint::{Tendermint, TendermintParams}; -======= pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; ->>>>>>> parity/master +pub use self::tendermint::{Tendermint, TendermintParams}; From 3b0d5503b14258c95e39995158f8ee3cec12e043 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 13:13:21 +0000 Subject: [PATCH 052/382] fix compilation --- ethcore/src/engines/tendermint/mod.rs | 10 ++++++---- ethcore/src/engines/tendermint/timeout.rs | 23 ++++++++++++----------- sync/src/api.rs | 2 +- sync/src/infinity.rs | 6 +++--- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index cc3283b5fac..47ed8f8289e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -41,7 +41,7 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::{TimerHandler, NextStep}; +use self::timeout::{TransitionHandler, NextStep}; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -102,7 +102,7 @@ impl Tendermint { proposed_block: Mutex::new(None), message_channel: Mutex::new(None) }); - let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.step_service.register_handler(Arc::new(handler))); Ok(engine) } @@ -241,7 +241,7 @@ impl Engine for Tendermint { if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { return Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&signature.into()).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), Vec::new() ]) } else { @@ -271,10 +271,12 @@ impl Engine for Tendermint { if votes.len() > self.threshold() { } }, - Step::Precommit => , + Step::Precommit => {}, + _ => {}, } } self.votes.vote(message, sender); + Err(BlockError::InvalidSeal.into()) } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 64a21a299c7..7897550778c 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -16,16 +16,14 @@ //! Tendermint timeout handling. -use util::Mutex; use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; -use io::{IoContext, IoHandler, TimerToken, IoChannel}; +use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; -use time::{get_time, Duration}; -use service::ClientIoMessage; +use time::Duration; -pub struct TimerHandler { - engine: Weak, +pub struct TransitionHandler { + pub engine: Weak, } /// Base timeout of each step in ms. @@ -70,7 +68,7 @@ fn set_timeout(io: &IoContext, timeout: Duration) { .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -impl IoHandler for TimerHandler { +impl IoHandler for TransitionHandler { fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { set_timeout(io, engine.our_params.timeouts.propose) @@ -104,7 +102,7 @@ impl IoHandler for TimerHandler { }; if let Some(step) = next_step { - *engine.step.write() = step; + *engine.step.write() = step.clone(); if step == Step::Propose { engine.update_sealing(); } @@ -115,9 +113,12 @@ impl IoHandler for TimerHandler { fn message(&self, io: &IoContext, message: &NextStep) { if let Some(engine) = self.engine.upgrade() { - io.clear_timer(ENGINE_TIMEOUT_TOKEN); - let NextStep(next_step) = *message; - *engine.step.write() = next_step; + match io.clear_timer(ENGINE_TIMEOUT_TOKEN) { + Ok(_) => {}, + Err(io_err) => warn!(target: "poa", "Could not remove consensus timer {}.", io_err), + }; + let NextStep(next_step) = message.clone(); + *engine.step.write() = next_step.clone(); match next_step { Step::Propose => { engine.update_sealing(); diff --git a/sync/src/api.rs b/sync/src/api.rs index bba26e8cce7..078cac74cb2 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -280,7 +280,7 @@ impl ChainNotify for EthSync { } fn stop(&self) { - self.handler.snapshot_service.abort_restore(); + self.eth_handler.snapshot_service.abort_restore(); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs index 810db32ed6c..936060a1da7 100644 --- a/sync/src/infinity.rs +++ b/sync/src/infinity.rs @@ -37,7 +37,7 @@ const GENERIC_PACKET: u8 = 0x01; pub struct NetworkStatus { pub protocol_version: u8, /// The underlying p2p network version. - pub network_id: U256, + pub network_id: usize, /// Total number of connected peers pub num_peers: usize, /// Total number of active peers @@ -52,7 +52,7 @@ struct PeerInfo { /// Peer chain genesis hash genesis: H256, /// Peer network id - network_id: U256, + network_id: usize, } /// Infinity protocol handler. @@ -61,7 +61,7 @@ pub struct InfinitySync { /// All connected peers peers: HashMap, /// Network ID - network_id: U256, + network_id: usize, } impl InfinitySync { From 51bbad66d06d4ecc7dbaeba3fe01740d74076866 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 15:56:16 +0000 Subject: [PATCH 053/382] add a path to submit seal from engine --- ethcore/src/client/client.rs | 7 +++++++ ethcore/src/engines/tendermint/mod.rs | 9 +++++++++ ethcore/src/miner/miner.rs | 6 +++--- ethcore/src/service.rs | 8 +++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e0f33af6a4d..e7aa716bd4b 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -569,6 +569,13 @@ impl Client { self.miner.update_sealing(self) } + /// Used by PoA to submit gathered signatures. + pub fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 47ed8f8289e..bb87b8ab256 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -116,6 +116,15 @@ impl Tendermint { } } + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) { + Ok(_) => trace!(target: "poa", "timeout: SubmitSeal message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 84e29458de9..f93203b00c8 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1008,7 +1008,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1016,9 +1016,9 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| &b.hash() == &block_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 36b5e715729..b19de72e97d 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -50,6 +50,8 @@ pub enum ClientIoMessage { TakeSnapshot(u64), /// Trigger sealing update (useful for internal sealing). UpdateSealing, + /// Submit seal (useful for internal sealing). + SubmitSeal(H256, Vec), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -219,9 +221,13 @@ impl IoHandler for ClientIoHandler { } }, ClientIoMessage::UpdateSealing => { - trace!(target: "authorityround", "message: UpdateSealing"); + trace!(target: "poa", "message: UpdateSealing"); self.client.update_sealing() }, + ClientIoMessage::SubmitSeal(ref hash, ref seal) => { + trace!(target: "poa", "message: SubmitSeal"); + self.client.submit_seal(*hash, seal.clone()) + }, _ => {} // ignore other messages } } From 802d5c669d0c2b9e84ba378c9c8b9d1d6d9758da Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 18:01:09 +0000 Subject: [PATCH 054/382] transition rules --- ethcore/src/engines/tendermint/message.rs | 12 ++-- ethcore/src/engines/tendermint/mod.rs | 67 ++++++++++++++----- ethcore/src/engines/tendermint/timeout.rs | 35 ++++------ .../src/engines/tendermint/vote_collector.rs | 2 +- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 0a7bcb1df46..5e9cbb1a334 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -20,7 +20,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { pub signature: H520, pub height: Height, @@ -30,12 +30,16 @@ pub struct ConsensusMessage { } impl ConsensusMessage { + pub fn is_height(&self, height: Height) -> bool { + self.height == height + } + pub fn is_round(&self, height: Height, round: Round) -> bool { self.height == height && self.round == round } - pub fn is_step(&self, height: Height, round: Round, step: &Step) -> bool { - self.height == height && self.round == round && &self.step == step + pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool { + self.height == height && self.round == round && self.step == step } pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { @@ -79,7 +83,7 @@ impl Decodable for Step { match try!(decoder.as_rlp().as_val()) { 0u8 => Ok(Step::Prevote), 1 => Ok(Step::Precommit), - _ => Err(DecoderError::Custom("Unknown step.")), + _ => Err(DecoderError::Custom("Invalid step.")), } } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bb87b8ab256..a4261569380 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -41,11 +41,11 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::{TransitionHandler, NextStep}; +use self::timeout::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Step { Propose, Prevote, @@ -65,7 +65,7 @@ pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - step_service: IoService, + step_service: IoService, /// Address to be used as authority. authority: RwLock
, /// Blockchain height. @@ -92,7 +92,7 @@ impl Tendermint { params: params, our_params: our_params, builtins: builtins, - step_service: try!(IoService::::start()), + step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), height: AtomicUsize::new(0), round: AtomicUsize::new(0), @@ -125,6 +125,14 @@ impl Tendermint { } } + fn to_step(&self, step: Step) { + *self.step.write() = step; + match step { + Step::Propose => self.update_sealing(), + _ => {}, + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() @@ -148,11 +156,19 @@ impl Tendermint { } fn is_current(&self, message: &ConsensusMessage) -> bool { - message.is_step(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), &self.step.read()) + message.is_height(self.height.load(AtomicOrdering::SeqCst)) } fn has_enough_any_votes(&self) -> bool { - self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), &self.step.read()) > self.threshold() + self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() + } + + fn has_enough_step_votes(&self, message: &ConsensusMessage) -> bool { + self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + } + + fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { + self.votes.aligned_signatures(&message).len() > self.threshold() } } @@ -272,19 +288,38 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - // Check if the message affects the current step. + self.votes.vote(message.clone(), sender); + + // Check if the message should be handled right now. if self.is_current(&message) { - match *self.step.read() { - Step::Prevote => { - let votes = self.votes.aligned_signatures(&message); - if votes.len() > self.threshold() { - } - }, - Step::Precommit => {}, - _ => {}, + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(&message) => { + if message.block_hash.is_none() { + self.round.fetch_add(1, AtomicOrdering::SeqCst); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Prevote) + }, + _ => None, + }; + + if let Some(step) = next_step { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) } + } } - self.votes.vote(message, sender); + Err(BlockError::InvalidSeal.into()) } diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 7897550778c..3291459840e 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -57,25 +57,22 @@ impl Default for TendermintTimeouts { } } -#[derive(Clone)] -pub struct NextStep(Step); - /// Timer token representing the consensus step timeouts. pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; -fn set_timeout(io: &IoContext, timeout: Duration) { +fn set_timeout(io: &IoContext, timeout: Duration) { io.register_timer_once(ENGINE_TIMEOUT_TOKEN, timeout.num_milliseconds() as u64) .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -impl IoHandler for TransitionHandler { - fn initialize(&self, io: &IoContext) { +impl IoHandler for TransitionHandler { + fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { set_timeout(io, engine.our_params.timeouts.propose) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { let next_step = match *engine.step.read() { @@ -102,32 +99,24 @@ impl IoHandler for TransitionHandler { }; if let Some(step) = next_step { - *engine.step.write() = step.clone(); - if step == Step::Propose { - engine.update_sealing(); - } + engine.to_step(step) } } } } - fn message(&self, io: &IoContext, message: &NextStep) { + fn message(&self, io: &IoContext, next_step: &Step) { if let Some(engine) = self.engine.upgrade() { - match io.clear_timer(ENGINE_TIMEOUT_TOKEN) { - Ok(_) => {}, - Err(io_err) => warn!(target: "poa", "Could not remove consensus timer {}.", io_err), - }; - let NextStep(next_step) = message.clone(); - *engine.step.write() = next_step.clone(); - match next_step { - Step::Propose => { - engine.update_sealing(); - set_timeout(io, engine.our_params.timeouts.propose) - }, + if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) { + warn!(target: "poa", "Could not remove consensus timer {}.", io_err) + } + match *next_step { + Step::Propose => set_timeout(io, engine.our_params.timeouts.propose), Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote), Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), }; + engine.to_step(*next_step); } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index c5db224b0cf..29906f999e9 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -50,7 +50,7 @@ impl VoteCollector { self.seal_signatures(message.height, message.round, message.block_hash) } - pub fn count_step_votes(&self, height: Height, round: Round, step: &Step) -> usize { + pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { self.votes .read() .keys() From 45027ea3060048de67bfdc8cfe49d88638e69274 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 12:17:48 +0000 Subject: [PATCH 055/382] add new client messaging --- ethcore/src/client/client.rs | 9 ++++++--- ethcore/src/service.rs | 10 ++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e7aa716bd4b..6eda89a7c14 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -576,6 +576,11 @@ impl Client { } } + /// Used by PoA to communicate with peers. + pub fn broadcast_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. @@ -1231,9 +1236,7 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - if let Ok(new_message) = self.engine.handle_message(UntrustedRlp::new(&message)) { - self.notify(|notify| notify.broadcast(new_message.clone())); - } + self.engine.handle_message(UntrustedRlp::new(&message)); } fn signing_network_id(&self) -> Option { diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b19de72e97d..1962bec5f5f 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -52,6 +52,8 @@ pub enum ClientIoMessage { UpdateSealing, /// Submit seal (useful for internal sealing). SubmitSeal(H256, Vec), + /// Broadcast a message to the network. + BroadcastMessage(Bytes) } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -222,11 +224,15 @@ impl IoHandler for ClientIoHandler { }, ClientIoMessage::UpdateSealing => { trace!(target: "poa", "message: UpdateSealing"); - self.client.update_sealing() + self.client.update_sealing(); }, ClientIoMessage::SubmitSeal(ref hash, ref seal) => { trace!(target: "poa", "message: SubmitSeal"); - self.client.submit_seal(*hash, seal.clone()) + self.client.submit_seal(*hash, seal.clone()); + }, + ClientIoMessage::BroadcastMessage(ref message) => { + trace!(target: "poa", "message: BroadcastMessage"); + self.client.broadcast_message(message.clone()); }, _ => {} // ignore other messages } From 9563ccfbd299ce6eded385c31f94e96bf96cdfce Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 12:18:20 +0000 Subject: [PATCH 056/382] message broadcasting methods --- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 83 ++++++++++--------- .../src/engines/tendermint/vote_collector.rs | 7 +- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 64e5cb16dab..283014a50de 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -159,7 +159,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _message: UntrustedRlp) -> Result<(), Error> { Err(EngineError::UnexpectedMessage.into()) } // 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. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a4261569380..78e08db1bb7 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -125,11 +125,37 @@ impl Tendermint { } } + fn broadcast_message(&self, message: Bytes) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => self.update_sealing(), - _ => {}, + Step::Prevote => { + self.broadcast_message() + }, + Step::Precommit => { + self.broadcast_message() + }, + Step::Commit => { + // Commit the block using a complete signature set. + let round = self.round.load(AtomicOrdering::SeqCst); + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))) { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(proposer).to_vec(), + ::rlp::encode(&votes).to_vec() + ]; + self.submit_seal(seal) + } + }, } } @@ -240,47 +266,27 @@ impl Engine for Tendermint { } /// Attempt to seal the block internally using all available signatures. - /// - /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let (Some(ap), Some(step)) = (accounts, self.step.try_read()) { + if let Some(ap) = accounts { let header = block.header(); let author = header.author(); - match *step { - Step::Commit => { - // Commit the block using a complete signature set. - let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))).split_first() { - if votes.len() + 1 > self.threshold() { - return Some(vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(proposer).to_vec(), - ::rlp::encode(&votes).to_vec() - ]); - } - } - }, - Step::Propose if self.is_proposer(author) => - // Seal block with propose signature. - - if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { - return Some(vec![ - ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&H520::from(signature)).to_vec(), - Vec::new() - ]) - } else { - trace!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); - }, - _ => {}, + if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + Some(vec![ + ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), + Vec::new() + ]) + } else { + warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); + None } } else { - trace!(target: "poa", "generate_seal: FAIL: accounts not provided"); + warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); + None } - None } - fn handle_message(&self, rlp: UntrustedRlp) -> Result { + fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); // TODO: Do not admit old messages. @@ -288,10 +294,8 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - self.votes.vote(message.clone(), sender); - - // Check if the message should be handled right now. - if self.is_current(&message) { + // Check if the message is known and should be handled right now. + if self.votes.vote(message.clone(), sender).is_none() && self.is_current(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { @@ -319,8 +323,7 @@ impl Engine for Tendermint { } } } - - Err(BlockError::InvalidSeal.into()) + Ok(()) } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 29906f999e9..075fda641a7 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -32,11 +32,11 @@ impl VoteCollector { VoteCollector { votes: RwLock::new(BTreeMap::new()) } } - pub fn vote(&self, message: ConsensusMessage, voter: Address) { - self.votes.write().insert(message, voter); + pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ + self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Vec { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> (H520, Vec) { self.votes .read() .keys() @@ -44,6 +44,7 @@ impl VoteCollector { .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) .map(|m| m.signature) .collect() + .split_first() } pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { From 51ac38318a8ee9f0979a063e54ecd415d30852dc Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 13:26:57 +0000 Subject: [PATCH 057/382] save proposal hash --- ethcore/src/engines/tendermint/mod.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 78e08db1bb7..28b67fa8ca6 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -81,7 +81,11 @@ pub struct Tendermint { /// Proposed block held until seal is gathered. proposed_block: Mutex>, /// Channel for updating the sealing. - message_channel: Mutex>> + message_channel: Mutex>>, + /// Last round when PoLC was seen. + last_lock_round: RwLock, + /// Proposed block. + proposal: RwLock> } impl Tendermint { @@ -100,7 +104,9 @@ impl Tendermint { proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), proposed_block: Mutex::new(None), - message_channel: Mutex::new(None) + message_channel: Mutex::new(None), + last_lock_round: AtomicUsize::new(0), + proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.step_service.register_handler(Arc::new(handler))); @@ -137,7 +143,10 @@ impl Tendermint { fn to_step(&self, step: Step) { *self.step.write() = step; match step { - Step::Propose => self.update_sealing(), + Step::Propose => { + self.proposal.write() = None; + self.update_sealing() + }, Step::Prevote => { self.broadcast_message() }, @@ -153,7 +162,7 @@ impl Tendermint { ::rlp::encode(proposer).to_vec(), ::rlp::encode(&votes).to_vec() ]; - self.submit_seal(seal) + self.submit_seal(self.proposal.read(), seal) } }, } @@ -198,7 +207,7 @@ impl Tendermint { } } -/// Block hash including the consensus round. +/// Block hash including the consensus round, gets signed and included in the seal. fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } @@ -271,6 +280,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + self.proposal.write() = Some(block.hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), From ce711e321ad97b9cf1ed2b36fe8b315575e0822a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 13:28:29 +0000 Subject: [PATCH 058/382] remove unused vote accumulators --- ethcore/src/engines/mod.rs | 5 - ethcore/src/engines/propose_collect.rs | 109 --------------------- ethcore/src/engines/signed_vote.rs | 125 ------------------------- 3 files changed, 239 deletions(-) delete mode 100644 ethcore/src/engines/propose_collect.rs delete mode 100644 ethcore/src/engines/signed_vote.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 283014a50de..2f43792e55f 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -21,17 +21,12 @@ mod instant_seal; mod basic_authority; mod authority_round; mod tendermint; -mod signed_vote; -mod propose_collect; - pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::authority_round::AuthorityRound; pub use self::tendermint::Tendermint; -pub use self::signed_vote::SignedVote; -pub use self::propose_collect::ProposeCollect; use rlp::UntrustedRlp; use util::*; diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs deleted file mode 100644 index c60457c3af9..00000000000 --- a/ethcore/src/engines/propose_collect.rs +++ /dev/null @@ -1,109 +0,0 @@ -// 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 . - -//! Voting on a hash, where each vote has to come from a set of addresses. - -use std::sync::atomic::{AtomicBool, Ordering}; -use util::{HashSet, RwLock, H256, Address}; - -/// Collect votes on a hash. -#[derive(Debug)] -pub struct ProposeCollect { - /// Proposed hash. - pub hash: H256, - /// Allowed voter addresses. - pub voters: HashSet
, - /// Threshold vote number for success. - pub threshold: usize, - /// Votes. - votes: RwLock>, - /// Was enough votes reached. - is_won: AtomicBool -} - -impl ProposeCollect { - /// Create a new instance of BFT engine - pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { - assert!(voters.len() > threshold); - ProposeCollect { - hash: hash, - voters: voters, - threshold: threshold, - votes: RwLock::new(HashSet::new()), - is_won: AtomicBool::new(false) - } - } - - /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, voter: Address) -> bool { - let is_known = self.votes.try_read().unwrap().contains(&voter); - if !is_known && self.voters.contains(&voter) { - self.votes.try_write().unwrap().insert(voter); - true - } else { - false - } - } - - /// Some winner if voting threshold was reached. - pub fn is_won(&self) -> bool { - let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { - true => { self.is_won.store(true, Ordering::Relaxed); true }, - false => false, - }; - self.is_won.load(Ordering::Relaxed) || threshold_checker() - } -} - -#[cfg(test)] -mod tests { - use engines::propose_collect::ProposeCollect; - use account_provider::AccountProvider; - use util::*; - use header::Header; - - #[test] - fn simple_propose_collect() { - let tap = AccountProvider::transient_provider(); - let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); - tap.unlock_account_permanently(addr1, "1".into()).unwrap(); - - let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); - tap.unlock_account_permanently(addr2, "2".into()).unwrap(); - - let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); - tap.unlock_account_permanently(addr3, "3".into()).unwrap(); - - let header = Header::default(); - let bare_hash = header.bare_hash(); - let voters: HashSet<_> = vec![addr1.clone(), addr2.clone(), Address::default()].into_iter().map(Into::into).collect(); - let vote = ProposeCollect::new(bare_hash, voters.into(), 2); - assert!(!vote.is_won()); - - // Unapproved voter. - assert!(!vote.vote(addr3)); - assert!(!vote.is_won()); - // First good vote. - assert!(vote.vote(addr1.clone())); - assert!(!vote.is_won()); - // Voting again is ineffective. - assert!(!vote.vote(addr1)); - assert!(!vote.is_won()); - // Second valid vote thus win. - assert!(vote.vote(addr2)); - assert!(vote.is_won()); - } -} diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs deleted file mode 100644 index 0cef5c21469..00000000000 --- a/ethcore/src/engines/signed_vote.rs +++ /dev/null @@ -1,125 +0,0 @@ -// 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 . - -//! Voting on hashes, where each vote has to come from a set of public keys. - -use super::EngineError; -use util::*; -use error::Error; -use ethkey::{Signature, recover}; - -/// Signed voting on hashes. -#[derive(Debug)] -pub struct SignedVote { - /// Voter public keys. - pub voters: HashSet
, - /// Number of voters. - pub voter_n: usize, - /// Threshold vote number for success. - pub threshold: usize, - /// Votes. - votes: RwLock>>, - /// Winner hash, set after enough votes are reached. - winner: RwLock> -} - -impl SignedVote { - /// Create a new instance of BFT engine - pub fn new(voters: HashSet
, threshold: usize) -> Self { - let voters_n = voters.len(); - assert!(voters_n > threshold); - SignedVote { - voter_n: voters_n, - voters: voters, - threshold: threshold, - votes: RwLock::new(HashMap::new()), - winner: RwLock::new(None) - } - } - - /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, bare_hash: H256, signature: Signature) -> bool { - if !self.can_vote(&bare_hash, &signature).is_ok() { return false; } - let mut guard = self.votes.try_write().unwrap(); - let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); - if !set.insert(signature) { return false; } - // Set the winner if threshold is reached. - if set.len() >= self.threshold { - let mut guard = self.winner.try_write().unwrap(); - *guard = Some(bare_hash); - } - true - } - - fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(recover(&signature, bare_hash)).sha3()); - match self.voters.contains(&signer) { - false => try!(Err(EngineError::UnauthorisedVoter)), - true => Ok(()), - } - } - - /// Some winner if voting threshold was reached. - pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } - - /// Get signatures backing given hash. - pub fn votes(&self, bare_hash: &H256) -> Option> { - self.votes.try_read().unwrap().get(bare_hash).cloned() - } -} - -#[cfg(test)] -mod tests { - use util::*; - use header::Header; - use engines::signed_vote::SignedVote; - use account_provider::AccountProvider; - - #[test] - fn simple_vote() { - let tap = AccountProvider::transient_provider(); - let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); - tap.unlock_account_permanently(addr1, "1".into()).unwrap(); - - let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); - tap.unlock_account_permanently(addr2, "2".into()).unwrap(); - - let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); - tap.unlock_account_permanently(addr3, "3".into()).unwrap(); - - let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); - let vote = SignedVote::new(voters.into(), 1); - assert!(vote.winner().is_none()); - let header = Header::default(); - let bare_hash = header.bare_hash(); - - // Unapproved voter. - let signature = tap.sign(addr3, None, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, signature)); - assert!(vote.winner().is_none()); - // First good vote. - let signature = tap.sign(addr1, None, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); - // Voting again is ineffective. - let signature = tap.sign(addr1, None, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, signature)); - // Second valid vote. - let signature = tap.sign(addr2, None, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); - } -} From 3bac68419ae43d70403f10d6619d9ce876f9ba95 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 17:12:37 +0000 Subject: [PATCH 059/382] last_lock --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 96 +++++++++++-------- .../src/engines/tendermint/vote_collector.rs | 18 +++- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 5e9cbb1a334..f957c77858c 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -114,7 +114,7 @@ impl Decodable for ConsensusMessage { false => Some(block_message), } }) - } + } } impl Encodable for ConsensusMessage { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 28b67fa8ca6..494e838732b 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -34,7 +34,7 @@ use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError, ProposeCollect}; +use engines::{Engine, EngineError}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; @@ -82,9 +82,9 @@ pub struct Tendermint { proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, - /// Last round when PoLC was seen. - last_lock_round: RwLock, - /// Proposed block. + /// Message for the last PoLC. + last_lock: RwLock>, + /// Bare hash of the proposed block, used for seal submission. proposal: RwLock> } @@ -105,7 +105,7 @@ impl Tendermint { votes: VoteCollector::new(), proposed_block: Mutex::new(None), message_channel: Mutex::new(None), - last_lock_round: AtomicUsize::new(0), + last_lock: RwLock::new(None), proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -140,18 +140,28 @@ impl Tendermint { } } + fn generate_message(&self, block_hash: Option) -> ConsensusMessage { + Ok(signature) = ap.sign(*author, None, block_hash(header)) + ConsensusMessage { signatue + + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => { - self.proposal.write() = None; + *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { self.broadcast_message() }, Step::Precommit => { - self.broadcast_message() + let message = match self.last_lock.read() { + Some(m) => + None => ConsensusMessage { signature: signature, height + } + self.broadcast_message(::rlp::encode(message)) }, Step::Commit => { // Commit the block using a complete signature set. @@ -162,8 +172,11 @@ impl Tendermint { ::rlp::encode(proposer).to_vec(), ::rlp::encode(&votes).to_vec() ]; - self.submit_seal(self.proposal.read(), seal) + if let Some(block_hash) = *self.proposal.read() { + self.submit_seal(block_hash, seal); + } } + *self.last_lock.write() = None; }, } } @@ -203,7 +216,7 @@ impl Tendermint { } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.aligned_signatures(&message).len() > self.threshold() + self.votes.aligned_votes(&message).len() > self.threshold() } } @@ -258,9 +271,7 @@ impl Engine for Tendermint { /// Get the address to be used as authority. fn on_new_block(&self, block: &mut ExecutedBlock) { - if let Some(mut authority) = self.authority.try_write() { - *authority = *block.header().author() - } + *self.authority.write() = *block.header().author() } /// Set the correct round in the seal. @@ -280,7 +291,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { - self.proposal.write() = Some(block.hash()); + *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), @@ -304,32 +315,41 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - // Check if the message is known and should be handled right now. - if self.votes.vote(message.clone(), sender).is_none() && self.is_current(&message) { - let next_step = match *self.step.read() { - Step::Precommit if self.has_enough_aligned_votes(&message) => { - if message.block_hash.is_none() { - self.round.fetch_add(1, AtomicOrdering::SeqCst); - Some(Step::Propose) - } else { - Some(Step::Commit) + // Check if the message is known. + if self.votes.vote(message.clone(), sender).is_none() { + let is_newer_than_lock = self.last_lock.read().map_or(true, |lock| message > lock); + if is_newer_than_lock + && message.step == Step::Prevote + && self.has_enough_aligned_votes(&message) { + *self.last_lock.write() = Some(message); + } + // Check if it can affect step transition. + if self.is_current(&message) { + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(&message) => { + if message.block_hash.is_none() { + self.round.fetch_add(1, AtomicOrdering::SeqCst); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Prevote) + }, + _ => None, + }; + + if let Some(step) = next_step { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) } - }, - Step::Precommit if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); - Some(Step::Precommit) - }, - Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); - Some(Step::Prevote) - }, - _ => None, - }; - - if let Some(step) = next_step { - if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to next step {}.", io_err) } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 075fda641a7..e7ea0a5d5d7 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -36,19 +36,29 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> (H520, Vec) { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option<(&H520, &[H520])> { self.votes .read() .keys() + .cloned() // Get only Propose and Precommits. .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) .map(|m| m.signature) - .collect() + .collect::>() .split_first() } - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { - self.seal_signatures(message.height, message.round, message.block_hash) + pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec<&ConsensusMessage> { + self.votes + .read() + .keys() + // Get only Propose and Precommits. + .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) + .collect() + } + + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> &[H520] { + self.seal_signatures(message.height, message.round, message.block_hash).map_or(&[], |s| s.1) } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { From 11ccacd6d0a9989d07b941a77f7a4e9f5a6f7bb3 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 18:32:12 +0000 Subject: [PATCH 060/382] dont keep account provider in miner --- ethcore/src/engines/authority_round.rs | 14 ++++++++++---- ethcore/src/engines/basic_authority.rs | 10 ++++++++-- ethcore/src/engines/instant_seal.rs | 3 +-- ethcore/src/engines/mod.rs | 5 +++-- ethcore/src/miner/miner.rs | 24 ++++++------------------ parity/run.rs | 8 ++++---- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9bed99e8ba7..2d7a5d69b28 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -68,6 +68,7 @@ pub struct AuthorityRound { builtins: BTreeMap, transition_service: IoService, message_channel: Mutex>>, + account_provider: Mutex>>, step: AtomicUsize, proposed: AtomicBool, } @@ -101,6 +102,7 @@ impl AuthorityRound { builtins: builtins, transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), + account_provider: Mutex::new(None), step: AtomicUsize::new(initial_step), proposed: AtomicBool::new(false) }); @@ -219,12 +221,12 @@ impl Engine for AuthorityRound { /// /// 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> { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { if self.proposed.load(AtomicOrdering::SeqCst) { return None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { - if let Some(ap) = accounts { + if let Some(ref ap) = *self.account_provider.lock() { // Account should be permanently unlocked, otherwise sealing will fail. if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); @@ -307,8 +309,12 @@ impl Engine for AuthorityRound { } fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 23a97967ccf..82a590b3898 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,6 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, + account_provider: Mutex>> } impl BasicAuthority { @@ -67,6 +68,7 @@ impl BasicAuthority { params: params, our_params: our_params, builtins: builtins, + account_provider: Mutex::new(None) } } } @@ -113,8 +115,8 @@ impl Engine for BasicAuthority { /// /// 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 { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail @@ -179,6 +181,10 @@ impl Engine for BasicAuthority { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn register_account_provider(&self, ap: Arc) { + *self.account_provider.lock() = Some(ap); + } } #[cfg(test)] diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 3dc78d1a2df..7351ac90d85 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -23,7 +23,6 @@ use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; use util::Bytes; -use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -60,7 +59,7 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { Some(Vec::new()) } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c70a19de856..e2e194d3c6c 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -91,7 +91,7 @@ pub trait Engine : Sync + Send { /// /// 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 } + fn generate_seal(&self, _block: &ExecutedBlock) -> 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. @@ -146,5 +146,6 @@ pub trait Engine : Sync + Send { /// 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. + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 84e29458de9..845d6aeed70 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,6 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; @@ -220,14 +219,13 @@ pub struct Miner { extra_data: RwLock, engine: Arc, - accounts: Option>, work_poster: Option, gas_pricer: Mutex, } impl Miner { /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Miner { let work_poster = match options.new_work_notify.is_empty() { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) @@ -261,26 +259,20 @@ impl Miner { author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, - accounts: accounts, engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), } } - /// 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. + /// Creates new instance of miner 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) + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec) } /// Creates new instance of a miner Arc. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Arc { + Arc::new(Miner::new_raw(options, gas_pricer, spec)) } fn forced_sealing(&self) -> bool { @@ -461,10 +453,7 @@ impl Miner { /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); + let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); block.lock().try_seal(&*self.engine, seal).or_else(|_| { @@ -1170,7 +1159,6 @@ mod tests { }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), - None, // accounts provider )).ok().expect("Miner was just created.") } diff --git a/parity/run.rs b/parity/run.rs index 2cc791f8c2c..aae4db74852 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -203,11 +203,8 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { sync_config.fork_block = spec.fork_block(); sync_config.warp_sync = cmd.warp_sync; - // prepare account provider - let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); - // create miner - let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); + let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec); miner.set_author(cmd.miner_extras.author); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); @@ -241,6 +238,9 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create supervisor let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path()); + // prepare account provider + let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); + // create client service. let service = try!(ClientService::start( client_config, From 11b6578bc324d91b38038e0e035900303da086e9 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 18:46:50 +0000 Subject: [PATCH 061/382] update tests --- ethcore/src/engines/authority_round.rs | 9 +++++---- ethcore/src/engines/basic_authority.rs | 3 ++- ethcore/src/engines/instant_seal.rs | 9 ++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 2d7a5d69b28..ee07b026284 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -388,6 +388,7 @@ mod tests { let spec = Spec::new_test_round(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db1 = get_temp_state_db().take(); spec.ensure_db_good(&mut db1).unwrap(); @@ -399,16 +400,16 @@ mod tests { let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = b2.close_and_lock(); - if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b1.block()).is_none()); } - if let Some(seal) = engine.generate_seal(b2.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b2.block()).is_none()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 82a590b3898..c43495967ae 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -259,6 +259,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); @@ -266,7 +267,7 @@ mod tests { 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(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 7351ac90d85..7491d47f397 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -68,16 +68,12 @@ impl Engine for InstantSeal { mod tests { use util::*; use tests::helpers::*; - use account_provider::AccountProvider; use spec::Spec; use header::Header; use block::*; #[test] fn instant_can_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); @@ -85,10 +81,9 @@ mod tests { 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 = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - // Seal with empty AccountProvider. - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } From a521fda24e667e5d8432dfa9a8dd41dfa98e4017 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 19:04:02 +0000 Subject: [PATCH 062/382] update rpc module test --- rpc/src/v1/tests/eth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 2f5131f3262..1ff5e17719e 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -50,7 +50,7 @@ fn sync_provider() -> Arc { })) } -fn miner_service(spec: &Spec, accounts: Arc) -> Arc { +fn miner_service(spec: &Spec) -> Arc { Miner::new( MinerOptions { new_work_notify: vec![], @@ -69,7 +69,6 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { }, GasPricer::new_fixed(20_000_000_000u64.into()), &spec, - Some(accounts), ) } @@ -116,7 +115,8 @@ impl EthTester { fn from_spec(spec: Spec) -> Self { let dir = RandomTempPath::new(); let account_provider = account_provider(); - let miner_service = miner_service(&spec, account_provider.clone()); + spec.engine.register_account_provider(account_provider.clone()); + let miner_service = miner_service(&spec); let snapshot_service = snapshot_service(); let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS); From 9d8ac7a09b49a1e5263209ae1130181c1708c149 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 19:08:00 +0000 Subject: [PATCH 063/382] extra line [ci skip] --- ethcore/src/engines/authority_round.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ee07b026284..ab2b41aec16 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -312,7 +312,6 @@ impl Engine for AuthorityRound { *self.message_channel.lock() = Some(message_channel); } - fn register_account_provider(&self, account_provider: Arc) { *self.account_provider.lock() = Some(account_provider); } From c62795d09beeec17b32615bc05243ae85c97281c Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 23:36:24 +0000 Subject: [PATCH 064/382] ap registration --- ethcore/src/engines/tendermint/message.rs | 12 ++++- ethcore/src/engines/tendermint/mod.rs | 65 +++++++++++++++-------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index f957c77858c..32faec72b33 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,8 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +use rlp::*; +use error::Error; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -30,6 +31,15 @@ pub struct ConsensusMessage { } impl ConsensusMessage { + pub fn new_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { + let mut s = RlpStream::new_list(4); + s.append(&height); + s.append(&round); + s.append(&step); + s.append(&block_hash.unwrap_or(H256::zero())); + Some(s.out()) + } + pub fn is_height(&self, height: Height) -> bool { self.height == height } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 494e838732b..8bbed8c848a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -82,6 +82,8 @@ pub struct Tendermint { proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, + /// Used to sign messages and proposals. + account_provider: Mutex>>, /// Message for the last PoLC. last_lock: RwLock>, /// Bare hash of the proposed block, used for seal submission. @@ -105,6 +107,7 @@ impl Tendermint { votes: VoteCollector::new(), proposed_block: Mutex::new(None), message_channel: Mutex::new(None), + account_provider: Mutex::new(None), last_lock: RwLock::new(None), proposal: RwLock::new(None) }); @@ -131,19 +134,31 @@ impl Tendermint { } } - fn broadcast_message(&self, message: Bytes) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + fn broadcast_message(&self, block_hash: Option) { + if let Some(message) = self.generate_message(block_hash) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } } + } else { + warn!(target: "poa", "broadcast_message: Message could not be generated."); } } - fn generate_message(&self, block_hash: Option) -> ConsensusMessage { - Ok(signature) = ap.sign(*author, None, block_hash(header)) - ConsensusMessage { signatue - + fn generate_message(&self, block_hash: Option) -> Option { + if let Some(ref ap) = *self.account_provider.lock() { + ConsensusMessage::new_rlp( + |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), + self.height.load(AtomicOrdering::SeqCst), + self.round.load(AtomicOrdering::SeqCst), + *self.step.read(), + block_hash + ) + } else { + None + } } fn to_step(&self, step: Step) { @@ -154,19 +169,19 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - self.broadcast_message() + self.broadcast_message(None) }, Step::Precommit => { - let message = match self.last_lock.read() { - Some(m) => - None => ConsensusMessage { signature: signature, height - } - self.broadcast_message(::rlp::encode(message)) + let block_hash = match *self.last_lock.read() { + Some(ref m) => None, + None => None, + }; + self.broadcast_message(block_hash); }, Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))) { + if let Some((proposer, votes)) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { let seal = vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(proposer).to_vec(), @@ -286,8 +301,8 @@ impl Engine for Tendermint { } /// Attempt to seal the block internally using all available signatures. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { @@ -317,11 +332,14 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { - let is_newer_than_lock = self.last_lock.read().map_or(true, |lock| message > lock); + let is_newer_than_lock = match *self.last_lock.read() { + Some(ref lock) => &message > lock, + None => true, + }; if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - *self.last_lock.write() = Some(message); + *self.last_lock.write() = Some(message.clone()); } // Check if it can affect step transition. if self.is_current(&message) { @@ -428,8 +446,11 @@ impl Engine for Tendermint { } fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } From 401a4a37c1e8f4bba921135e28a4c5281a362519 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 18 Nov 2016 19:14:52 +0800 Subject: [PATCH 065/382] Initial structure for auto-updater. - Add auto-gen'ed Operations and Registry ABIs. - Add Updater for managing updates. - Add fields in Client to enable update checking and registry. --- ethcore/src/client/client.rs | 73 +++---- ethcore/src/client/error.rs | 16 ++ ethcore/src/client/mod.rs | 3 + ethcore/src/client/operations.rs | 336 +++++++++++++++++++++++++++++++ ethcore/src/client/registry.rs | 264 ++++++++++++++++++++++++ ethcore/src/client/updater.rs | 45 +++++ 6 files changed, 696 insertions(+), 41 deletions(-) create mode 100644 ethcore/src/client/operations.rs create mode 100644 ethcore/src/client/registry.rs create mode 100644 ethcore/src/client/updater.rs diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 14558f6c646..b8111d77cf1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -13,7 +13,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . + use std::collections::{HashSet, HashMap, BTreeMap, VecDeque}; +use std::str::FromStr; use std::sync::{Arc, Weak}; use std::path::{Path}; use std::fmt; @@ -22,12 +24,11 @@ use std::time::{Instant}; use time::precise_time_ns; // util -use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, ToPretty}; +use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, MutexGuard, Hashable}; use util::{journaldb, TrieFactory, Trie}; use util::trie::TrieSpec; use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::kvdb::*; -use util::misc::code_hash; // other use io::*; @@ -69,7 +70,8 @@ use factory::Factories; use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; -use ethabi::{Interface, Contract, Token}; +use client::updater::Updater; +use client::registry::Registry; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -140,6 +142,7 @@ pub struct Client { panic_handler: Arc, verifier: Box, miner: Arc, + updater: Mutex>, sleep_state: Mutex, liveness: AtomicBool, io_channel: Mutex>, @@ -150,6 +153,7 @@ pub struct Client { history: u64, rng: Mutex, on_mode_change: Mutex>>, + registrar: Mutex>, } impl Client { @@ -222,7 +226,7 @@ impl Client { accountdb: Default::default(), }; - let client = Client { + let client = Arc::new(Client { sleep_state: Mutex::new(SleepState::new(awake)), liveness: AtomicBool::new(awake), mode: Mutex::new(config.mode.clone()), @@ -239,6 +243,7 @@ impl Client { import_lock: Mutex::new(()), panic_handler: panic_handler, miner: miner, + updater: Mutex::new(None), io_channel: Mutex::new(message_channel), notify: RwLock::new(Vec::new()), queue_transactions: AtomicUsize::new(0), @@ -247,8 +252,19 @@ impl Client { history: history, rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), on_mode_change: Mutex::new(None), - }; - Ok(Arc::new(client)) + registrar: Mutex::new(None), + }); + if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { + let weak = Arc::downgrade(&client); + let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); + if let Ok(operations) = registrar.get_address(&(&b"operations"[..]).sha3(), "A") { + if !operations.is_zero() { + *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), operations)); + } + } + *client.registrar.lock() = Some(registrar); + } + Ok(client) } /// Adds an actor to be notified on certain events @@ -264,6 +280,11 @@ impl Client { } } + /// Get the Registry object - useful for looking up names. + pub fn registrar(&self) -> MutexGuard> { + self.registrar.lock() + } + /// Register an action to be done if a mode change happens. pub fn on_mode_change(&self, f: F) where F: 'static + FnMut(&Mode) + Send { *self.on_mode_change.lock() = Some(Box::new(f)); @@ -644,7 +665,9 @@ impl Client { pub fn tick(&self) { self.check_garbage(); self.check_snooze(); - self.check_updates(); + if let Some(ref mut updater) = *self.updater.lock() { + updater.tick(); + } } fn check_garbage(&self) { @@ -687,7 +710,8 @@ impl Client { } } - fn call_contract(&self, address: Address, data: Bytes) -> Result { + /// Like `call`, but with various defaults. Designed to be used for calling contracts. + pub fn call_contract(&self, address: Address, data: Bytes) -> Result { let from = Address::default(); let transaction = Transaction { nonce: self.latest_nonce(&from), @@ -705,39 +729,6 @@ impl Client { }) } - fn check_updates(&self) { - let operations_json = Interface::load(include_bytes!("../../res/Operations.json")).expect("Operations.json is valid ABI"); - let operations = Contract::new(operations_json); - - fn as_string(e: T) -> String { - format!("{:?}", e) - } - - let res = || { - let is_latest = try!(operations.function("isLatest".into()).map_err(as_string)); - let params = try!(is_latest.encode_call( - vec![Token::FixedBytes(b"par"[..].to_owned()), Token::Address(code_hash().0)] - ).map_err(as_string)); - println!("params: {}", params.pretty()); - let output = try!(self.call_contract("0x4c1783B4FfB1A99eFC4cda632aA990F5138b26f1".into(), params)); - let result = try!(is_latest.decode_output(output).map_err(as_string)); - - match result.get(0) { - Some(&Token::Bool(answer)) => Ok(answer), - e => Err(format!("Invalid result: {:?}", e)), - } - }; - - match res() { - Ok(res) => { - info!("isLatest returned {}", res); - }, - Err(e) => { - warn!(target: "dapps", "Error while calling Operations.isLatest: {:?}", e); - } - } - } - /// Look up the block number for the given block ID. pub fn block_number(&self, id: BlockID) -> Option { match id { diff --git a/ethcore/src/client/error.rs b/ethcore/src/client/error.rs index 8dcc2b7738a..86297a26c1d 100644 --- a/ethcore/src/client/error.rs +++ b/ethcore/src/client/error.rs @@ -1,3 +1,19 @@ +// 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 . + use trace::Error as TraceError; use util::UtilError; use std::fmt::{Display, Formatter, Error as FmtError}; diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3898ab6cda5..b49301bbd17 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -16,11 +16,14 @@ //! Blockchain database client. +mod operations; +mod registry; mod config; mod error; mod test_client; mod trace; mod client; +mod updater; pub use self::client::*; pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; diff --git a/ethcore/src/client/operations.rs b/ethcore/src/client/operations.rs new file mode 100644 index 00000000000..bdc142b594b --- /dev/null +++ b/ethcore/src/client/operations.rs @@ -0,0 +1,336 @@ +// Autogenerated from JSON contract definition using Rust contract convertor. + +use std::string::String; +use std::result::Result; +use std::fmt; +use {util, ethabi}; +use util::FixedHash; +use util::Uint; + +pub struct Operations { + contract: ethabi::Contract, + address: util::Address, + do_call: Box) -> Result, String> + Send + 'static>, +} +impl Operations { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + Operations { + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"owners\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"findChecksum\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"forks\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"}],\"name\":\"addRelease\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"clients\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"inputs\":[],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"Received\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"gas\",\"type\":\"uint256\"}],\"name\":\"TransactionProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"TransactionRelayed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"spec\",\"type\":\"bytes32\"}],\"name\":\"ForkProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkAcceptedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkRejectedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRatified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"forkBlock\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"track\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"semver\",\"type\":\"uint24\"}],\"name\":\"ReleaseAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"platform\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"checksum\",\"type\":\"bytes32\"}],\"name\":\"ChecksumAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ClientAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"}],\"name\":\"ClientRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"ClientOwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"bool\"}],\"name\":\"ClientRequiredChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + address: address, + do_call: Box::new(do_call), + } + } + fn as_string(e: T) -> String { format!("{:?}", e) } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"owners","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` + #[allow(dead_code)] + pub fn owners(&self, _1: &util::Address) -> Result { + let call = self.contract.function("owners".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_1.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn reset_client_owner(&self, _client: &util::H256, _new_owner: &util::Address) -> Result<(), String> { + let call = self.contract.function("resetClientOwner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Address(_new_owner.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"type":"function"}` + #[allow(dead_code)] + pub fn is_latest(&self, _client: &str, _release: &util::H256) -> Result { + let call = self.contract.function("isLatest".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn reject_transaction(&self, _txid: &util::H256) -> Result<(), String> { + let call = self.contract.function("rejectTransaction".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_txid.as_ref().to_owned())] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn set_owner(&self, _new_owner: &util::Address) -> Result<(), String> { + let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_new_owner.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn remove_client(&self, _client: &util::H256) -> Result<(), String> { + let call = self.contract.function("removeClient".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned())] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn reject_fork(&self) -> Result<(), String> { + let call = self.contract.function("rejectFork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"}` + #[allow(dead_code)] + pub fn find_checksum(&self, _client: &util::H256, _checksum: &util::H256) -> Result<(util::H256, util::H256), String> { + let call = self.contract.function("findChecksum".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn propose_fork(&self, _number: u32, _name: &util::H256, _spec: &util::H256) -> Result<(), String> { + let call = self.contract.function("proposeFork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_number as u64).to_big_endian(&mut r); r }), ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::FixedBytes(_spec.as_ref().to_owned())] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn set_client_owner(&self, _new_owner: &util::Address) -> Result<(), String> { + let call = self.contract.function("setClientOwner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_new_owner.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn add_checksum(&self, _release: &util::H256, _platform: &util::H256, _checksum: &util::H256) -> Result<(), String> { + let call = self.contract.function("addChecksum".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_ref().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"}` + #[allow(dead_code)] + pub fn confirm_transaction(&self, _txid: &util::H256) -> Result { + let call = self.contract.function("confirmTransaction".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_txid.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"type":"function"}` + #[allow(dead_code)] + pub fn proxy(&self, _1: &util::H256) -> Result<(util::U256, util::Address, Vec, util::U256, util::U256), String> { + let call = self.contract.function("proxy".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_1.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bytes().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn add_client(&self, _client: &util::H256, _owner: &util::Address) -> Result<(), String> { + let call = self.contract.function("addClient".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Address(_owner.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"forks","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"type":"function"}` + #[allow(dead_code)] + pub fn forks(&self, _1: u32) -> Result<(util::H256, util::H256, bool, util::U256), String> { + let call = self.contract.function("forks".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_1 as u64).to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"}` + #[allow(dead_code)] + pub fn owner(&self) -> Result { + let call = self.contract.function("owner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"}` + #[allow(dead_code)] + pub fn propose_transaction(&self, _txid: &util::H256, _to: &util::Address, _data: &[u8], _value: util::U256, _gas: util::U256) -> Result { + let call = self.contract.function("proposeTransaction".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_txid.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0), ethabi::Token::Bytes(_data.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; _gas.to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn accept_fork(&self) -> Result<(), String> { + let call = self.contract.function("acceptFork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + #[allow(dead_code)] + pub fn clients_required(&self) -> Result { + let call = self.contract.function("clientsRequired".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"type":"function"}` + #[allow(dead_code)] + pub fn track(&self, _client: &util::H256, _release: &util::H256) -> Result { + let call = self.contract.function("track".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u8 })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn set_client_required(&self, _client: &util::H256, _r: bool) -> Result<(), String> { + let call = self.contract.function("setClientRequired".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Bool(_r)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"}],"name":"addRelease","outputs":[],"type":"function"}` + #[allow(dead_code)] + pub fn add_release(&self, _release: &util::H256, _fork_block: u32, _track: u8, _semver: u32) -> Result<(), String> { + let call = self.contract.function("addRelease".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_fork_block as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_track as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_semver as u64).to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + #[allow(dead_code)] + pub fn latest_fork(&self) -> Result { + let call = self.contract.function("latestFork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` + #[allow(dead_code)] + pub fn latest_in_track(&self, _client: &util::H256, _track: u8) -> Result { + let call = self.contract.function("latestInTrack".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_track as u64).to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"clients","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"type":"function"}` + #[allow(dead_code)] + pub fn clients(&self, _1: &util::H256) -> Result<(util::Address, bool), String> { + let call = self.contract.function("clients".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_1.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + #[allow(dead_code)] + pub fn proposed_fork(&self) -> Result { + let call = self.contract.function("proposedFork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + } +} \ No newline at end of file diff --git a/ethcore/src/client/registry.rs b/ethcore/src/client/registry.rs new file mode 100644 index 00000000000..f65661d8890 --- /dev/null +++ b/ethcore/src/client/registry.rs @@ -0,0 +1,264 @@ +// Autogenerated from JSON contract definition using Rust contract convertor. + +use std::string::String; +use std::result::Result; +use std::fmt; +use {util, ethabi}; +use util::FixedHash; +use util::Uint; + +pub struct Registry { + contract: ethabi::Contract, + address: util::Address, + do_call: Box) -> Result, String> + Send + 'static>, +} +impl Registry { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + Registry { + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"confirmReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"bytes32\"}],\"name\":\"set\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"drop\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserved\",\"outputs\":[{\"name\":\"reserved\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"drain\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"proposeReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"reverse\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setUint\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"removeReverse\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Drained\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Reserved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"Transferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Dropped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"key\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"plainKey\",\"type\":\"string\"}],\"name\":\"DataChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"current\",\"type\":\"address\"}],\"name\":\"NewOwner\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + address: address, + do_call: Box::new(do_call), + } + } + fn as_string(e: T) -> String { format!("{:?}", e) } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set_owner(&self, _new: &util::Address) -> Result<(), String> { + let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_new.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn confirm_reverse(&self, _name: &str) -> Result { + let call = self.contract.function("confirmReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::String(_name.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn reserve(&self, _name: &util::H256) -> Result { + let call = self.contract.function("reserve".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set(&self, _name: &util::H256, _key: &str, _value: &util::H256) -> Result { + let call = self.contract.function("set".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::FixedBytes(_value.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn drop(&self, _name: &util::H256) -> Result { + let call = self.contract.function("drop".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_address(&self, _name: &util::H256, _key: &str) -> Result { + let call = self.contract.function("getAddress".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set_fee(&self, _amount: util::U256) -> Result<(), String> { + let call = self.contract.function("setFee".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; _amount.to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn transfer(&self, _name: &util::H256, _to: &util::Address) -> Result { + let call = self.contract.function("transfer".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn owner(&self) -> Result { + let call = self.contract.function("owner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn reserved(&self, _name: &util::H256) -> Result { + let call = self.contract.function("reserved".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn drain(&self) -> Result<(), String> { + let call = self.contract.function("drain".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn propose_reverse(&self, _name: &str, _who: &util::Address) -> Result { + let call = self.contract.function("proposeReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::String(_name.to_owned()), ethabi::Token::Address(_who.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_uint(&self, _name: &util::H256, _key: &str) -> Result { + let call = self.contract.function("getUint".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get(&self, _name: &util::H256, _key: &str) -> Result { + let call = self.contract.function("get".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn fee(&self) -> Result { + let call = self.contract.function("fee".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_owner(&self, _name: &util::H256) -> Result { + let call = self.contract.function("getOwner".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn reverse(&self, _1: &util::Address) -> Result { + let call = self.contract.function("reverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_1.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_string().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set_uint(&self, _name: &util::H256, _key: &str, _value: util::U256) -> Result { + let call = self.contract.function("setUint".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn remove_reverse(&self) -> Result<(), String> { + let call = self.contract.function("removeReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set_address(&self, _name: &util::H256, _key: &str, _value: &util::Address) -> Result { + let call = self.contract.function("setAddress".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Address(_value.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } +} \ No newline at end of file diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs new file mode 100644 index 00000000000..aaa6526be12 --- /dev/null +++ b/ethcore/src/client/updater.rs @@ -0,0 +1,45 @@ +// 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 . + +//use util::{U256, H256, Address, H2048, Uint, FixedHash}; +use std::sync::Weak; +use util::misc::code_hash; +use util::Address; +use client::operations::Operations; +use client::client::Client; + +pub struct Updater { + operations: Operations, +} + +impl Updater { + pub fn new(client: Weak, operations: Address) -> Self { + Updater { + operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), + } + } + + pub fn tick(&mut self) { + match self.operations.is_latest("par", &code_hash().into()) { + Ok(res) => { + info!("isLatest returned {}", res); + }, + Err(e) => { + warn!(target: "dapps", "Error while calling Operations.isLatest: {:?}", e); + } + } + } +} From cd770490fffc1e6f0f5d46830d84b0417f28d1cb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 18 Nov 2016 19:22:47 +0800 Subject: [PATCH 066/382] Remove unneeded file. --- ethcore/res/Operations.json | 1 - ethcore/src/client/updater.rs | 1 - 2 files changed, 2 deletions(-) delete mode 100644 ethcore/res/Operations.json diff --git a/ethcore/res/Operations.json b/ethcore/res/Operations.json deleted file mode 100644 index 573a45169ad..00000000000 --- a/ethcore/res/Operations.json +++ /dev/null @@ -1 +0,0 @@ -[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"owners","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"forks","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"}],"name":"addRelease","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"clients","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"gas","type":"uint256"}],"name":"TransactionProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":false,"name":"success","type":"bool"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"},{"indexed":true,"name":"name","type":"bytes32"},{"indexed":false,"name":"spec","type":"bytes32"}],"name":"ForkProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkAcceptedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkRejectedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRatified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"forkBlock","type":"uint32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":false,"name":"track","type":"uint8"},{"indexed":false,"name":"semver","type":"uint24"}],"name":"ReleaseAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":true,"name":"platform","type":"bytes32"},{"indexed":false,"name":"checksum","type":"bytes32"}],"name":"ChecksumAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"ClientAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"}],"name":"ClientRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"now","type":"address"}],"name":"ClientOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"now","type":"bool"}],"name":"ClientRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old","type":"address"},{"indexed":false,"name":"now","type":"address"}],"name":"OwnerChanged","type":"event"}] \ No newline at end of file diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index aaa6526be12..d3b483ee6aa 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//use util::{U256, H256, Address, H2048, Uint, FixedHash}; use std::sync::Weak; use util::misc::code_hash; use util::Address; From 4fd575b5ecdd2513c34949aa28668633ef7bad2b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 18 Nov 2016 19:52:11 +0800 Subject: [PATCH 067/382] Add traces. --- ethcore/src/client/client.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b8111d77cf1..3676f0a0a20 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -255,11 +255,13 @@ impl Client { registrar: Mutex::new(None), }); if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { + trace!(target: "client", "Found registrar at {}", reg_addr); let weak = Arc::downgrade(&client); let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); - if let Ok(operations) = registrar.get_address(&(&b"operations"[..]).sha3(), "A") { - if !operations.is_zero() { - *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), operations)); + if let Ok(ops_addr) = registrar.get_address(&(&b"operations"[..]).sha3(), "A") { + if !ops_addr.is_zero() { + trace!(target: "client", "Found operations at {}", ops_addr); + *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), ops_addr)); } } *client.registrar.lock() = Some(registrar); From e90d81419380fa391ed3d365839a368d5877e032 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 12:27:00 +0000 Subject: [PATCH 068/382] lock rounds --- ethcore/src/engines/tendermint/mod.rs | 50 ++++++++++++------- ethcore/src/engines/tendermint/timeout.rs | 1 + .../src/engines/tendermint/vote_collector.rs | 33 ++++++------ 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8bbed8c848a..02cd114b501 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -78,14 +78,14 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, - /// Proposed block held until seal is gathered. - proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, /// Used to sign messages and proposals. account_provider: Mutex>>, /// Message for the last PoLC. - last_lock: RwLock>, + lock_change: RwLock>, + /// Last lock round. + last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock> } @@ -105,10 +105,10 @@ impl Tendermint { step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), - proposed_block: Mutex::new(None), message_channel: Mutex::new(None), account_provider: Mutex::new(None), - last_lock: RwLock::new(None), + lock_change: RwLock::new(None), + last_lock: AtomicUsize::new(0), proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -169,29 +169,41 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - self.broadcast_message(None) + let should_unlock = |lock_change_round| { + self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round + && lock_change_round < self.round.load(AtomicOrdering::SeqCst) + }; + let block_hash = match *self.lock_change.read() { + Some(ref m) if should_unlock(m.round) => self.proposal.read().clone(), + Some(ref m) => m.block_hash, + None => None, + }; + self.broadcast_message(block_hash) }, Step::Precommit => { - let block_hash = match *self.last_lock.read() { - Some(ref m) => None, - None => None, + let block_hash = match *self.lock_change.read() { + Some(ref m) if self.is_round(m) => { + self.last_lock.store(m.round, AtomicOrdering::SeqCst); + m.block_hash + }, + _ => None, }; self.broadcast_message(block_hash); }, Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { + if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { let seal = vec![ ::rlp::encode(&round).to_vec(), - ::rlp::encode(proposer).to_vec(), - ::rlp::encode(&votes).to_vec() + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() ]; if let Some(block_hash) = *self.proposal.read() { self.submit_seal(block_hash, seal); } } - *self.last_lock.write() = None; + *self.lock_change.write() = None; }, } } @@ -218,10 +230,14 @@ impl Tendermint { self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is_current(&self, message: &ConsensusMessage) -> bool { + fn is__height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } + fn is_round(&self, message: &ConsensusMessage) -> bool { + message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) + } + fn has_enough_any_votes(&self) -> bool { self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() } @@ -332,17 +348,17 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { - let is_newer_than_lock = match *self.last_lock.read() { + let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, }; if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - *self.last_lock.write() = Some(message.clone()); + *self.lock_change.write() = Some(message.clone()); } // Check if it can affect step transition. - if self.is_current(&message) { + if self.is__height(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 3291459840e..7cd94350ba4 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -91,6 +91,7 @@ impl IoHandler for TransitionHandler { }, Step::Commit => { set_timeout(io, engine.our_params.timeouts.propose); + engine.last_lock.store(0, AtomicOrdering::SeqCst); engine.round.store(0, AtomicOrdering::SeqCst); engine.height.fetch_add(1, AtomicOrdering::SeqCst); Some(Step::Propose) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index e7ea0a5d5d7..42475967806 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -27,6 +27,11 @@ pub struct VoteCollector { votes: RwLock> } +pub struct SealSignatures { + pub proposal: H520, + pub votes: Vec +} + impl VoteCollector { pub fn new() -> VoteCollector { VoteCollector { votes: RwLock::new(BTreeMap::new()) } @@ -36,29 +41,29 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option<(&H520, &[H520])> { - self.votes - .read() - .keys() - .cloned() + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { + let guard = self.votes.read(); // Get only Propose and Precommits. + let mut correct_signatures = guard.keys() .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature) - .collect::>() - .split_first() + .map(|m| m.signature.clone()); + correct_signatures.next().map(|proposal| SealSignatures { + proposal: proposal, + votes: correct_signatures.collect() + }) } - pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec<&ConsensusMessage> { - self.votes - .read() - .keys() + pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec { + let guard = self.votes.read(); + guard.keys() // Get only Propose and Precommits. .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) + .cloned() .collect() } - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> &[H520] { - self.seal_signatures(message.height, message.round, message.block_hash).map_or(&[], |s| s.1) + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { + self.seal_signatures(message.height, message.round, message.block_hash).map_or(Vec::new(), |s| s.votes) } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { From 2f3b80129661f0fe87f50b5804dc7cedaf9c2237 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 13:37:47 +0000 Subject: [PATCH 069/382] rename transition --- .../src/engines/tendermint/{timeout.rs => transition.rs} | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) rename ethcore/src/engines/tendermint/{timeout.rs => transition.rs} (92%) diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/transition.rs similarity index 92% rename from ethcore/src/engines/tendermint/timeout.rs rename to ethcore/src/engines/tendermint/transition.rs index 7cd94350ba4..a5cb02763cc 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -16,7 +16,6 @@ //! Tendermint timeout handling. -use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; @@ -86,14 +85,12 @@ impl IoHandler for TransitionHandler { }, Step::Precommit if engine.has_enough_any_votes() => { set_timeout(io, engine.our_params.timeouts.propose); - engine.round.fetch_add(1, AtomicOrdering::SeqCst); + engine.increment_round(1); Some(Step::Propose) }, Step::Commit => { set_timeout(io, engine.our_params.timeouts.propose); - engine.last_lock.store(0, AtomicOrdering::SeqCst); - engine.round.store(0, AtomicOrdering::SeqCst); - engine.height.fetch_add(1, AtomicOrdering::SeqCst); + engine.reset_round(); Some(Step::Propose) }, _ => None, From 49cbd6ef983d50737d8875a1b99010bf4863c2ea Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 13:38:04 +0000 Subject: [PATCH 070/382] unused imports, proposer_nonce --- ethcore/src/client/client.rs | 4 +- ethcore/src/engines/tendermint/message.rs | 9 +++- ethcore/src/engines/tendermint/mod.rs | 54 ++++++++++++------- ethcore/src/engines/tendermint/params.rs | 2 +- .../src/engines/tendermint/vote_collector.rs | 1 - 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 6eda89a7c14..d55d884a4a7 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -24,13 +24,11 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock}; use util::{journaldb, TrieFactory, Trie}; -use util::{U256, H256, H520, Address, H2048, Uint, FixedHash}; -use util::sha3::*; +use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::trie::TrieSpec; use util::kvdb::*; // other -use ethkey::recover; use io::*; use views::{HeaderView, BodyView, BlockView}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 32faec72b33..4fcbd3e52bd 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -19,7 +19,6 @@ use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::*; -use error::Error; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -37,7 +36,13 @@ impl ConsensusMessage { s.append(&round); s.append(&step); s.append(&block_hash.unwrap_or(H256::zero())); - Some(s.out()) + let block_info = s.out(); + signer(block_info.sha3()).map(|ref signature| { + let mut s = RlpStream::new_list(2); + s.append(signature); + s.append(&block_info); + s.out() + }) } pub fn is_height(&self, height: Height) -> bool { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 02cd114b501..1974858e58e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -17,7 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. mod message; -mod timeout; +mod transition; mod params; mod vote_collector; @@ -29,7 +29,7 @@ use header::Header; use builtin::Builtin; use env_info::EnvInfo; use transaction::SignedTransaction; -use rlp::{UntrustedRlp, View, encode}; +use rlp::{UntrustedRlp, View}; use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; @@ -41,7 +41,7 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::TransitionHandler; +use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -57,7 +57,6 @@ pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; -pub type AtomicMs = AtomicUsize; type Signatures = Vec; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. @@ -169,12 +168,8 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - let should_unlock = |lock_change_round| { - self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round - && lock_change_round < self.round.load(AtomicOrdering::SeqCst) - }; let block_hash = match *self.lock_change.read() { - Some(ref m) if should_unlock(m.round) => self.proposal.read().clone(), + Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, None => None, }; @@ -210,7 +205,7 @@ impl Tendermint { fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; - p.authorities.get(proposer_nonce % p.authority_n).unwrap() + p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed") } fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { @@ -230,7 +225,7 @@ impl Tendermint { self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is__height(&self, message: &ConsensusMessage) -> bool { + fn is_height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } @@ -238,12 +233,31 @@ impl Tendermint { message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) } + fn increment_round(&self, n: Round) { + self.proposer_nonce.fetch_add(n, AtomicOrdering::SeqCst); + self.round.fetch_add(n, AtomicOrdering::SeqCst); + } + + fn reset_round(&self) { + self.last_lock.store(0, AtomicOrdering::SeqCst); + self.proposer_nonce.fetch_add(1, AtomicOrdering::SeqCst); + self.height.fetch_add(1, AtomicOrdering::SeqCst); + self.round.store(0, AtomicOrdering::SeqCst); + } + + fn should_unlock(&self, lock_change_round: Round) -> bool { + self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round + && lock_change_round < self.round.load(AtomicOrdering::SeqCst) + } + + fn has_enough_any_votes(&self) -> bool { self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() } - fn has_enough_step_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool { + message.round > self.round.load(AtomicOrdering::SeqCst) + && self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { @@ -357,24 +371,24 @@ impl Engine for Tendermint { && self.has_enough_aligned_votes(&message) { *self.lock_change.write() = Some(message.clone()); } - // Check if it can affect step transition. - if self.is__height(&message) { + // Check if it can affect the step transition. + if self.is_height(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { - self.round.fetch_add(1, AtomicOrdering::SeqCst); + self.increment_round(1); Some(Step::Propose) } else { Some(Step::Commit) } }, - Step::Precommit if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); + Step::Precommit if self.has_enough_future_step_votes(&message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); Some(Step::Precommit) }, Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); + Step::Prevote if self.has_enough_future_step_votes(&message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); Some(Step::Prevote) }, _ => None, diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index dbed9b54170..3752ae3bd28 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use super::timeout::TendermintTimeouts; +use super::transition::TendermintTimeouts; use util::{Address, U256}; use time::Duration; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 42475967806..0f4553502ce 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -19,7 +19,6 @@ use util::*; use super::message::ConsensusMessage; use super::{Height, Round, Step}; -use ethkey::recover; #[derive(Debug)] pub struct VoteCollector { From 27a8608624f906be606a9c79fad9580aeada55b6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 20 Nov 2016 13:18:56 +0100 Subject: [PATCH 071/382] More information in the updater. --- ethcore/src/client/operations.rs | 80 +++++++++++++++++++++----------- ethcore/src/client/updater.rs | 33 +++++++++---- 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/ethcore/src/client/operations.rs b/ethcore/src/client/operations.rs index bdc142b594b..3141f1c250e 100644 --- a/ethcore/src/client/operations.rs +++ b/ethcore/src/client/operations.rs @@ -4,8 +4,7 @@ use std::string::String; use std::result::Result; use std::fmt; use {util, ethabi}; -use util::FixedHash; -use util::Uint; +use util::{FixedHash, Uint}; pub struct Operations { contract: ethabi::Contract, @@ -15,7 +14,7 @@ pub struct Operations { impl Operations { pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { Operations { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"owners\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"findChecksum\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"forks\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"}],\"name\":\"addRelease\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"clients\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"inputs\":[],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"Received\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"gas\",\"type\":\"uint256\"}],\"name\":\"TransactionProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"TransactionRelayed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"spec\",\"type\":\"bytes32\"}],\"name\":\"ForkProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkAcceptedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkRejectedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRatified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"forkBlock\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"track\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"semver\",\"type\":\"uint24\"}],\"name\":\"ReleaseAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"platform\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"checksum\",\"type\":\"bytes32\"}],\"name\":\"ChecksumAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ClientAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"}],\"name\":\"ClientRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"ClientOwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"bool\"}],\"name\":\"ClientRequiredChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"owners\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"findRelease\",\"outputs\":[{\"name\":\"o_forkBlock\",\"type\":\"uint32\"},{\"name\":\"o_track\",\"type\":\"uint8\"},{\"name\":\"o_semver\",\"type\":\"uint24\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"findBuild\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"forks\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"}],\"name\":\"findChecksum\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"}],\"name\":\"addRelease\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"clients\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"inputs\":[],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"Received\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"gas\",\"type\":\"uint256\"}],\"name\":\"TransactionProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"TransactionRelayed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"spec\",\"type\":\"bytes32\"}],\"name\":\"ForkProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkAcceptedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkRejectedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRatified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"forkBlock\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"track\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"semver\",\"type\":\"uint24\"}],\"name\":\"ReleaseAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"platform\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"checksum\",\"type\":\"bytes32\"}],\"name\":\"ChecksumAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ClientAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"}],\"name\":\"ClientRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"ClientOwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"bool\"}],\"name\":\"ClientRequiredChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), address: address, do_call: Box::new(do_call), } @@ -36,10 +35,10 @@ impl Operations { /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"type":"function"}` #[allow(dead_code)] - pub fn reset_client_owner(&self, _client: &util::H256, _new_owner: &util::Address) -> Result<(), String> { + pub fn reset_client_owner(&self, _client: &str, _new_owner: &util::Address) -> Result<(), String> { let call = self.contract.function("resetClientOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Address(_new_owner.clone().0)] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::Address(_new_owner.clone().0)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -58,6 +57,19 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) } + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"findRelease","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"}],"type":"function"}` + #[allow(dead_code)] + pub fn find_release(&self, _client: &str, _release: &util::H256) -> Result<(u32, u8, u32), String> { + let call = self.contract.function("findRelease".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = (self.do_call)(self.address.clone(), data)?; + let returned = call.decode_output(output).map_err(Self::as_string)?; + let mut result = returned.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u8 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) + } + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"type":"function"}` #[allow(dead_code)] pub fn reject_transaction(&self, _txid: &util::H256) -> Result<(), String> { @@ -84,10 +96,10 @@ impl Operations { /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"type":"function"}` #[allow(dead_code)] - pub fn remove_client(&self, _client: &util::H256) -> Result<(), String> { + pub fn remove_client(&self, _client: &str) -> Result<(), String> { let call = self.contract.function("removeClient".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned())] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -106,12 +118,12 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findBuild","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"}` #[allow(dead_code)] - pub fn find_checksum(&self, _client: &util::H256, _checksum: &util::H256) -> Result<(util::H256, util::H256), String> { - let call = self.contract.function("findChecksum".into()).map_err(Self::as_string)?; + pub fn find_build(&self, _client: &str, _checksum: &util::H256) -> Result<(util::H256, util::H256), String> { + let call = self.contract.function("findBuild".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); @@ -123,7 +135,7 @@ impl Operations { pub fn propose_fork(&self, _number: u32, _name: &util::H256, _spec: &util::H256) -> Result<(), String> { let call = self.contract.function("proposeFork".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_number as u64).to_big_endian(&mut r); r }), ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::FixedBytes(_spec.as_ref().to_owned())] + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_number as u64).to_big_endian(&mut r); r }), ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::FixedBytes(_spec.as_ref().to_owned())] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -144,10 +156,10 @@ impl Operations { /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"type":"function"}` #[allow(dead_code)] - pub fn add_checksum(&self, _release: &util::H256, _platform: &util::H256, _checksum: &util::H256) -> Result<(), String> { + pub fn add_checksum(&self, _release: &util::H256, _platform: &str, _checksum: &util::H256) -> Result<(), String> { let call = self.contract.function("addChecksum".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_ref().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_bytes().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -180,10 +192,10 @@ impl Operations { /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"type":"function"}` #[allow(dead_code)] - pub fn add_client(&self, _client: &util::H256, _owner: &util::Address) -> Result<(), String> { + pub fn add_client(&self, _client: &str, _owner: &util::Address) -> Result<(), String> { let call = self.contract.function("addClient".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Address(_owner.clone().0)] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::Address(_owner.clone().0)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -195,7 +207,7 @@ impl Operations { pub fn forks(&self, _1: u32) -> Result<(util::H256, util::H256, bool, util::U256), String> { let call = self.contract.function("forks".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_1 as u64).to_big_endian(&mut r); r })] + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_1 as u64).to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); @@ -238,6 +250,18 @@ impl Operations { Ok(()) } + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` + #[allow(dead_code)] + pub fn find_checksum(&self, _client: &str, _release: &util::H256, _platform: &str) -> Result { + let call = self.contract.function("findChecksum".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_bytes().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + } + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"type":"function"}` #[allow(dead_code)] pub fn clients_required(&self) -> Result { @@ -247,27 +271,27 @@ impl Operations { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"type":"function"}` #[allow(dead_code)] - pub fn track(&self, _client: &util::H256, _release: &util::H256) -> Result { + pub fn track(&self, _client: &str, _release: &util::H256) -> Result { let call = self.contract.function("track".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u8 })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u8 })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"type":"function"}` #[allow(dead_code)] - pub fn set_client_required(&self, _client: &util::H256, _r: bool) -> Result<(), String> { + pub fn set_client_required(&self, _client: &str, _r: bool) -> Result<(), String> { let call = self.contract.function("setClientRequired".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Bool(_r)] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::Bool(_r)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -279,7 +303,7 @@ impl Operations { pub fn add_release(&self, _release: &util::H256, _fork_block: u32, _track: u8, _semver: u32) -> Result<(), String> { let call = self.contract.function("addRelease".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_fork_block as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_track as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_semver as u64).to_big_endian(&mut r); r })] + vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_fork_block as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_track as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_semver as u64).to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; @@ -295,15 +319,15 @@ impl Operations { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` #[allow(dead_code)] - pub fn latest_in_track(&self, _client: &util::H256, _track: u8) -> Result { + pub fn latest_in_track(&self, _client: &str, _track: u8) -> Result { let call = self.contract.function("latestInTrack".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U128::from(_track as u64).to_big_endian(&mut r); r })] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_track as u64).to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); @@ -331,6 +355,6 @@ impl Operations { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U128::from(r.as_ref()).as_u64() as u32 })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) } } \ No newline at end of file diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index d3b483ee6aa..4fb210ca635 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -16,7 +16,7 @@ use std::sync::Weak; use util::misc::code_hash; -use util::Address; +use util::{Address, H160}; use client::operations::Operations; use client::client::Client; @@ -24,6 +24,10 @@ pub struct Updater { operations: Operations, } +fn platform() -> &'static str { + "linux_x64" +} + impl Updater { pub fn new(client: Weak, operations: Address) -> Self { Updater { @@ -32,13 +36,24 @@ impl Updater { } pub fn tick(&mut self) { - match self.operations.is_latest("par", &code_hash().into()) { - Ok(res) => { - info!("isLatest returned {}", res); - }, - Err(e) => { - warn!(target: "dapps", "Error while calling Operations.isLatest: {:?}", e); - } - } + (|| -> Result<(), String> { + let code_hash = H160::from("0x080ec8043f41e25ee8aa4ee6112906ac6d82ea74").into();//code_hash().into(); + let client = "parity"; + + let (fork, track, semver) = self.operations.find_release(client, &code_hash)?; + let track_name = match track { 1 => "stable", 2 => "beta", 3 => "nightly", _ => "unknown" }; + info!(target: "updater", "Current release ({}) is {}.{}.{}-{} and latest fork it supports is at block #{}", H160::from(code_hash), semver >> 16, (semver >> 8) & 0xff, semver & 0xff, track_name, fork); + + let latest_fork = self.operations.latest_fork()?; + info!(target: "updater", "Latest fork is at block #{}", latest_fork); + + let latest = self.operations.latest_in_track(client, track)?; + let (fork, _, semver) = self.operations.find_release(client, &latest)?; + info!(target: "updater", "Latest release in our track is {}.{}.{}-{} ({:?}); supports fork at block #{}", semver >> 16, (semver >> 8) & 0xff, semver & 0xff, track_name, H160::from(latest), fork); + + let exe_hash = self.operations.find_checksum(client, &latest, platform())?; + info!(target: "updater", "Latest release's binary on {} is {}", platform(), exe_hash); + Ok(()) + })().unwrap_or_else(|e| warn!("{}", e)); } } From e69be670de55187a31e47fb12b8b6f9a1dd15dd0 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 11:36:07 +0000 Subject: [PATCH 072/382] message serialization --- ethcore/src/engines/tendermint/message.rs | 30 +++++++++++------------ ethcore/src/engines/tendermint/mod.rs | 15 ++++++------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 4fcbd3e52bd..26e4716c1c0 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -30,21 +30,6 @@ pub struct ConsensusMessage { } impl ConsensusMessage { - pub fn new_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { - let mut s = RlpStream::new_list(4); - s.append(&height); - s.append(&round); - s.append(&step); - s.append(&block_hash.unwrap_or(H256::zero())); - let block_info = s.out(); - signer(block_info.sha3()).map(|ref signature| { - let mut s = RlpStream::new_list(2); - s.append(signature); - s.append(&block_info); - s.out() - }) - } - pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -143,3 +128,18 @@ impl Encodable for ConsensusMessage { s.append(&self.block_hash.unwrap_or(H256::zero())); } } + +pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { + let mut s = RlpStream::new_list(4); + s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + s.out() +} + +pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { + let vote_info = message_info_rlp(height, round, step, block_hash); + signer(vote_info.sha3()).map(|ref signature| { + let mut s = RlpStream::new_list(2); + s.append(signature).append(&vote_info); + s.out() + }) +} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1974858e58e..81b764de7c4 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -40,7 +40,7 @@ use views::HeaderView; use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; -use self::message::ConsensusMessage; +use self::message::{ConsensusMessage, message_info_rlp, message_full_rlp}; use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -148,7 +148,7 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - ConsensusMessage::new_rlp( + message_full_rlp( |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), @@ -335,7 +335,8 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(block_hash(header))); + if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), @@ -418,10 +419,6 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let signature = try!(proposer_signature(header)); - let proposer = public_to_address(&try!(recover(&signature.into(), &block_hash(header)))); - if !self.is_proposer(&proposer) { - try!(Err(BlockError::InvalidSeal)) - } let proposal = ConsensusMessage { signature: signature, height: header.number() as Height, @@ -429,6 +426,10 @@ impl Engine for Tendermint { step: Step::Propose, block_hash: Some(block_hash(header)) }; + let proposer = public_to_address(&try!(recover(&signature.into(), &::rlp::encode(&proposal)))); + if !self.is_proposer(&proposer) { + try!(Err(BlockError::InvalidSeal)) + } self.votes.vote(proposal, proposer); let votes_rlp = UntrustedRlp::new(&header.seal()[2]); for rlp in votes_rlp.iter() { From 42ef7767dab880654903173298c61eb29ee48083 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 12:23:27 +0000 Subject: [PATCH 073/382] delete unused message type --- ethcore/src/engines/tendermint/vote.rs | 72 -------------------------- 1 file changed, 72 deletions(-) delete mode 100644 ethcore/src/engines/tendermint/vote.rs diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs deleted file mode 100644 index b01e14460cb..00000000000 --- a/ethcore/src/engines/tendermint/vote.rs +++ /dev/null @@ -1,72 +0,0 @@ -// 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 . - -//! Tendermint block seal. - -use util::*; -use header::Header; -use account_provider::AccountProvider; -use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -use basic_types::Seal; -use super::BlockHash; - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Vote { - block_hash: BlockHash, - signature: H520 -} - -fn block_hash(header: &Header) -> H256 { - header.rlp(Seal::WithSome(1)).sha3() -} - -impl Vote { - fn new(block_hash: BlockHash, signature: H520) -> Vote { - Vote { block_hash: block_hash, signature: signature } - } - - /// Try to use the author address to create a vote. - pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { - Self::validate(header, accounts, *header.author()) - } - - /// Use any unlocked validator account to create a vote. - pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { - let message = block_hash(&header); - accounts.sign(validator, None, message) - .ok() - .map(Into::into) - .map(|sig| Self::new(message, sig)) - } -} - -impl Decodable for Vote { - fn decode(decoder: &D) -> Result where D: Decoder { - let rlp = decoder.as_rlp(); - if decoder.as_raw().len() != try!(rlp.payload_info()).total() { - return Err(DecoderError::RlpIsTooBig); - } - Ok(Self::new(try!(rlp.val_at(0)), try!(rlp.val_at(1)))) - } -} - -impl Encodable for Vote { - fn rlp_append(&self, s: &mut RlpStream) { - let Vote { ref block_hash, ref signature } = *self; - s.append(block_hash); - s.append(signature); - } -} From 12dbdc1d6e0c9455bc8dad1bcb78d8fee220033b Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:01:34 +0000 Subject: [PATCH 074/382] dont pass ap --- ethcore/src/miner/miner.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 05cc14b64c8..664b2f4e919 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,6 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; +use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -221,13 +222,14 @@ pub struct Miner { extra_data: RwLock, engine: Arc, + accounts: Option>, work_poster: Option, gas_pricer: Mutex, } impl Miner { /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Miner { + fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { let work_poster = match options.new_work_notify.is_empty() { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) @@ -261,20 +263,26 @@ impl Miner { author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, + accounts: accounts, engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), } } - /// Creates new instance of miner with given spec. + /// 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) + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) } /// Creates new instance of a miner Arc. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec)) + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { + Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) } fn forced_sealing(&self) -> bool { @@ -1029,7 +1037,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1037,9 +1045,9 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &block_hash + |b| &b.hash() == &pow_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) @@ -1191,6 +1199,7 @@ mod tests { }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), + None, // accounts provider )).ok().expect("Miner was just created.") } From 841d0941e09ad1c1125eadff2969806ef72b02a1 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:01:52 +0000 Subject: [PATCH 075/382] remove WithSome block hash --- ethcore/src/basic_types.rs | 4 +--- ethcore/src/header.rs | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5c0100de3b9..79f009fd1c9 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -25,12 +25,10 @@ pub type LogBloom = H2048; pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); #[cfg_attr(feature="dev", allow(enum_variant_names))] -/// Enum for when a seal/signature is included. +/// Semantic boolean for when a seal/signature is included. pub enum Seal { /// The seal/signature is included. With, /// The seal/signature is not included. Without, - /// First N fields of seal are included. - WithSome(usize), } diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 0aea4efd7ff..228933570cd 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -228,8 +228,7 @@ impl Header { // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 }; - s.begin_list(13 + seal_n); + s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 }); s.append(&self.parent_hash); s.append(&self.uncles_hash); s.append(&self.author); @@ -243,8 +242,10 @@ impl Header { s.append(&self.gas_used); s.append(&self.timestamp); s.append(&self.extra_data); - for b in self.seal.iter().take(seal_n) { - s.append_raw(b, 1); + if let Seal::With = with_seal { + for b in &self.seal { + s.append_raw(b, 1); + } } } From 84fdaf966a6617a4b823ef922a1029377dc3139d Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:02:26 +0000 Subject: [PATCH 076/382] correct seal verification --- ethcore/src/engines/tendermint/message.rs | 39 +++++++++++++++-- ethcore/src/engines/tendermint/mod.rs | 52 ++++++----------------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 26e4716c1c0..cdcadb5b5cd 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,10 @@ use util::*; use super::{Height, Round, BlockHash, Step}; +use error::Error; +use header::Header; use rlp::*; +use ethkey::{recover, public_to_address}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -29,7 +32,23 @@ pub struct ConsensusMessage { pub block_hash: Option } + +fn consensus_round(header: &Header) -> Result { + UntrustedRlp::new(header.seal()[0].as_slice()).as_val() +} + impl ConsensusMessage { + pub fn new_proposal(header: &Header) -> Result { + Ok(ConsensusMessage { + signature: try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()), + height: header.number() as Height, + round: try!(consensus_round(header)), + step: Step::Propose, + block_hash: Some(header.bare_hash()) + }) + } + + pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -45,6 +64,13 @@ impl ConsensusMessage { pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { self.height == height && self.round == round && self.block_hash == block_hash } + + pub fn verify(&self) -> Result { + let full_rlp = ::rlp::encode(self); + let block_info = Rlp::new(&full_rlp).at(1); + let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3())); + Ok(public_to_address(&public_key)) + } } impl PartialOrd for ConsensusMessage { @@ -56,10 +82,10 @@ impl PartialOrd for ConsensusMessage { impl Step { fn number(&self) -> i8 { match *self { - Step::Propose => -1, - Step::Prevote => 0, - Step::Precommit => 1, - Step::Commit => 2, + Step::Propose => 0, + Step::Prevote => 1, + Step::Precommit => 2, + Step::Commit => 3, } } } @@ -135,6 +161,11 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op s.out() } +pub fn message_info_rlp_from_header(header: &Header) -> Result { + let round = try!(consensus_round(header)); + Ok(message_info_rlp(header.number() as Height, round, Step::Precommit, Some(header.bare_hash()))) +} + pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 81b764de7c4..4385b82648c 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -23,7 +23,6 @@ mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; -use basic_types::Seal; use error::{Error, BlockError}; use header::Header; use builtin::Builtin; @@ -40,7 +39,7 @@ use views::HeaderView; use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; -use self::message::{ConsensusMessage, message_info_rlp, message_full_rlp}; +use self::message::*; use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -265,19 +264,6 @@ impl Tendermint { } } -/// Block hash including the consensus round, gets signed and included in the seal. -fn block_hash(header: &Header) -> H256 { - header.rlp(Seal::WithSome(1)).sha3() -} - -fn proposer_signature(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[1].as_slice()).as_val() -} - -fn consensus_round(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[0].as_slice()).as_val() -} - impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } @@ -289,11 +275,12 @@ impl Engine for Tendermint { /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { + let message = ConsensusMessage::new_proposal(header).expect("Invalid header."); map![ - "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "height".into() => header.number().to_string(), - "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "block_hash".into() => block_hash(header).to_string() + "signature".into() => message.signature.to_string(), + "height".into() => message.height.to_string(), + "round".into() => message.round.to_string(), + "block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into()) ] } @@ -319,12 +306,6 @@ impl Engine for Tendermint { *self.authority.write() = *block.header().author() } - /// Set the correct round in the seal. - fn on_close_block(&self, block: &mut ExecutedBlock) { - let round = self.round.load(AtomicOrdering::SeqCst); - block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); - } - /// Round proposer switching. fn is_sealer(&self, address: &Address) -> Option { Some(self.is_proposer(address)) @@ -335,7 +316,7 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(block_hash(header))); + let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -418,23 +399,16 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let signature = try!(proposer_signature(header)); - let proposal = ConsensusMessage { - signature: signature, - height: header.number() as Height, - round: try!(consensus_round(header)), - step: Step::Propose, - block_hash: Some(block_hash(header)) - }; - let proposer = public_to_address(&try!(recover(&signature.into(), &::rlp::encode(&proposal)))); + let proposal = try!(ConsensusMessage::new_proposal(header)); + let proposer = try!(proposal.verify()); if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) } self.votes.vote(proposal, proposer); - let votes_rlp = UntrustedRlp::new(&header.seal()[2]); - for rlp in votes_rlp.iter() { - let sig: H520 = try!(rlp.as_val()); - let address = public_to_address(&try!(recover(&sig.into(), &block_hash(header)))); + let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { + let signature: H520 = try!(rlp.as_val()); + let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { try!(Err(BlockError::InvalidSeal)) } From 66526af5a8b379c165dfc02dc8ecd532f84e17ce Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 18:53:53 +0000 Subject: [PATCH 077/382] pass engine in tests --- ethcore/src/engines/null_engine.rs | 6 ++++++ ethcore/src/snapshot/tests/blocks.rs | 22 +++++++++------------- ethcore/src/spec/spec.rs | 3 +-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ethcore/src/engines/null_engine.rs b/ethcore/src/engines/null_engine.rs index e0906ce2239..bd5a4474ad2 100644 --- a/ethcore/src/engines/null_engine.rs +++ b/ethcore/src/engines/null_engine.rs @@ -38,6 +38,12 @@ impl NullEngine { } } +impl Default for NullEngine { + fn default() -> Self { + Self::new(Default::default(), Default::default()) + } +} + impl Engine for NullEngine { fn name(&self) -> &str { "NullEngine" diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 34f1accca44..3d9390d2ef5 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -31,23 +31,20 @@ use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::AtomicBool; -use spec::Spec; - fn chunk_and_restore(amount: u64) { let mut canon_chain = ChainGenerator::default(); let mut finalizer = BlockFinalizer::default(); let genesis = canon_chain.generate(&mut finalizer).unwrap(); let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let engine = Arc::new(::engines::NullEngine::default()); let orig_path = RandomTempPath::create_dir(); let new_path = RandomTempPath::create_dir(); let mut snapshot_path = new_path.as_path().to_owned(); snapshot_path.push("SNAP"); - let new_chain = |db| BlockChain::new(Default::default(), &genesis, db, Spec::new_null().engine); - let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); - let bc = new_chain(old_db.clone()); + let bc = BlockChain::new(Default::default(), &genesis, old_db.clone(), engine.clone()); // build the blockchain. let mut batch = old_db.transaction(); @@ -77,21 +74,20 @@ fn chunk_and_restore(amount: u64) { // restore it. let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); - let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), Spec::new_null().engine); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), engine.clone()); let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); let flag = AtomicBool::new(true); for chunk_hash in &reader.manifest().block_hashes { let compressed = reader.chunk(*chunk_hash).unwrap(); let chunk = snappy::decompress(&compressed).unwrap(); - rebuilder.feed(&chunk, &engine, &flag).unwrap(); + rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap(); } rebuilder.finalize(HashMap::new()).unwrap(); // and test it. - let new_chain = BlockChain::new(Default::default(), &genesis, new_db, Spec::new_null().engine); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db, engine); assert_eq!(new_chain.best_block_hash(), best_hash); } @@ -125,8 +121,8 @@ fn checks_flag() { let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap()); - let chain = BlockChain::new(Default::default(), &genesis, db.clone()); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); + let engine = Arc::new(::engines::NullEngine::default()); + let chain = BlockChain::new(Default::default(), &genesis, db.clone(), engine.clone()); let manifest = ::snapshot::ManifestData { state_hashes: Vec::new(), @@ -138,8 +134,8 @@ fn checks_flag() { let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap(); - match rebuilder.feed(&chunk, &engine, &AtomicBool::new(false)) { + match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) { Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {} _ => panic!("Wrong result on abort flag set") } -} \ No newline at end of file +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index ca5970ecf37..b789df22240 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -30,8 +30,7 @@ use ethjson; use rlp::{Rlp, RlpStream, View, Stream}; /// Parameters common to all engines. -#[derive(Debug, PartialEq, Clone)] -#[cfg_attr(test, derive(Default))] +#[derive(Debug, PartialEq, Clone, Default)] pub struct CommonParams { /// Account start nonce. pub account_start_nonce: U256, From 32bcd08b94b1a4648d1b1a29c5ad143da3889dd2 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 18:54:16 +0000 Subject: [PATCH 078/382] test utilities --- ethcore/src/engines/tendermint/mod.rs | 147 ++++++++------------------ 1 file changed, 46 insertions(+), 101 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4385b82648c..48e3b359992 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -461,11 +461,10 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { - use std::thread::sleep; - use std::time::{Duration}; use util::*; use rlp::{UntrustedRlp, RlpStream, Stream, View}; use block::*; + use state_db::StateDB; use error::{Error, BlockError}; use header::Header; use env_info::EnvInfo; @@ -473,46 +472,38 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError}; - use super::Tendermint; + use super::*; use super::params::TendermintParams; + use super::message::*; - fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { - let mut s = RlpStream::new_list(3); - let header = Header::default(); - s.append(&round).append(&0u8).append(&header.bare_hash()); - let drain = s.out(); - let propose_rlp = UntrustedRlp::new(&drain); - - engine.handle_message(proposer, H520::default(), propose_rlp) + fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { + let mut header = Header::default(); + let last_hashes = Arc::new(vec![]); + let b = OpenBlock::new(engine.as_ref(), Default::default(), false, db.boxed_clone(), &header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = b.close_and_lock(); + engine.generate_seal(b.block()) } - fn vote_default(engine: &Arc, round: u8, voter: Address) -> Result { - let mut s = RlpStream::new_list(3); - let header = Header::default(); - s.append(&round).append(&1u8).append(&header.bare_hash()); - let drain = s.out(); - let vote_rlp = UntrustedRlp::new(&drain); - - engine.handle_message(voter, H520::default(), vote_rlp) + fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { + let m = message_full_rlp(signer, height, round, step, Some(Default::default())).unwrap(); + engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } - fn good_seal(header: &Header) -> Vec { + fn proposal_seal(header: &Header, round: Round) -> Vec { let tap = AccountProvider::transient_provider(); - let mut seal = Vec::new(); - - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let sig0 = tap.sign(v0, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig0 as &[u8])).to_vec()); - - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - let sig1 = tap.sign(v1, Some("1".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); - seal + let author = header.author(); + let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); + let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); + vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), + Vec::new() + ] } - fn default_block() -> Vec { - vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] + fn default_seal() -> Vec { + vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] } #[test] @@ -593,7 +584,7 @@ mod tests { let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); - let seal = good_seal(&header); + let seal = proposal_seal(&header, 0); header.set_seal(seal); // Enough signatures. @@ -607,7 +598,7 @@ mod tests { fn can_generate_seal() { let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()).unwrap(); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); @@ -617,82 +608,55 @@ mod tests { let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - tender.to_commit(b.hash(), good_seal(&b.header())); - - let seal = tender.generate_seal(b.block(), None).unwrap(); + let seal = tender.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } #[test] fn propose_step() { - let engine = Spec::new_test_tendermint().engine; + let spec = Spec::new_test_tendermint(); + let engine = spec.engine.clone(); let tap = AccountProvider::transient_provider(); - let r = 0; + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, r, not_authority_addr).is_err()); + assert!(propose_default(&engine, &db, not_authority_addr).is_none()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, r, not_proposer_addr).is_err()); + assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_block(), propose_default(&engine, r, proposer_addr).unwrap()); - - assert!(propose_default(&engine, r, proposer_addr).is_err()); - assert!(propose_default(&engine, r, not_proposer_addr).is_err()); - } - - #[test] - fn proposer_switching() { - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); - - // Currently not a proposer. - let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, 0, not_proposer_addr).is_err()); + assert_eq!(default_seal(), propose_default(&engine, &db, proposer_addr).unwrap()); - sleep(Duration::from_millis(TendermintParams::default().timeouts.propose as u64)); - - // Becomes proposer after timeout. - assert_eq!(default_block(), propose_default(&engine, 0, not_proposer_addr).unwrap()); + assert!(propose_default(&engine, &db, proposer_addr).is_none()); + assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); } #[test] fn prevote_step() { + let spec = Spec::new_test_tendermint(); let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let r = 0; + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); let v0 = tap.insert_account("0".sha3(), "0").unwrap(); let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - // Propose. - assert!(propose_default(&engine, r, v1.clone()).is_ok()); + // Propose + assert!(propose_default(&engine, &db, v1.clone()).is_some()); - // Prevote. - assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); - - assert!(vote_default(&engine, r, v0).is_err()); - assert!(vote_default(&engine, r, v1).is_err()); - } - - #[test] - fn precommit_step() { - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); + let h = 0; let r = 0; - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - - // Propose. - assert!(propose_default(&engine, r, v1.clone()).is_ok()); - // Prevote. - assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); - assert!(vote_default(&engine, r, v0).is_err()); - assert!(vote_default(&engine, r, v1).is_err()); + vote_default(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); } #[test] @@ -703,24 +667,5 @@ mod tests { }; println!("Waiting for timeout"); - sleep(Duration::from_secs(10)); - } - - #[test] - fn increments_round() { - let spec = Spec::new_test_tendermint(); - let ref engine = *spec.engine; - let def_params = TendermintParams::default(); - let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); - let header = Header::default(); - - tender.to_commit(header.bare_hash(), good_seal(&header)); - - sleep(Duration::from_millis(def_params.timeouts.commit as u64)); - - match propose_default(&(tender as Arc), 0, Address::default()) { - Err(Error::Engine(EngineError::WrongRound)) => {}, - _ => panic!("Should be EngineError::WrongRound"), - } } } From 45dead9d49b02d8d9a948bde4fc994e2d96d6b12 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 22 Nov 2016 10:24:22 +0100 Subject: [PATCH 079/382] Naming consistency and make Updater improvements. - ID -> Id (consistency with rust libs) --- Cargo.lock | 20 +++ ethcore/light/src/client.rs | 6 +- ethcore/src/client/client.rs | 119 ++++++++-------- ethcore/src/client/operations.rs | 197 +++++++++++++------------- ethcore/src/client/test_client.rs | 86 +++++------ ethcore/src/client/traits.rs | 70 ++++----- ethcore/src/client/updater.rs | 86 +++++++---- ethcore/src/miner/miner.rs | 6 +- ethcore/src/snapshot/error.rs | 4 +- ethcore/src/snapshot/mod.rs | 4 +- ethcore/src/snapshot/service.rs | 4 +- ethcore/src/snapshot/tests/service.rs | 6 +- ethcore/src/snapshot/watcher.rs | 4 +- ethcore/src/tests/client.rs | 20 +-- ethcore/src/tests/rpc.rs | 4 +- ethcore/src/types/filter.rs | 28 ++-- ethcore/src/types/ids.rs | 12 +- ethcore/src/types/trace_filter.rs | 4 +- ethstore/src/dir/disk.rs | 4 +- ethstore/src/ethstore.rs | 4 +- ethstore/src/json/error.rs | 4 +- ethstore/src/json/id.rs | 48 +++---- ethstore/src/json/key_file.rs | 8 +- ethstore/src/json/mod.rs.in | 2 +- ethstore/src/secret_store.rs | 4 +- parity/blockchain.rs | 8 +- parity/cli/usage.txt | 8 ++ parity/configuration.rs | 10 +- parity/dapps.rs | 4 +- parity/helpers.rs | 22 +-- parity/informant.rs | 4 +- parity/snapshot.rs | 4 +- rpc/src/v1/impls/eth.rs | 28 ++-- rpc/src/v1/impls/eth_filter.rs | 10 +- rpc/src/v1/impls/parity.rs | 4 +- rpc/src/v1/impls/traces.rs | 8 +- rpc/src/v1/tests/eth.rs | 4 +- rpc/src/v1/tests/mocked/eth.rs | 4 +- rpc/src/v1/types/block_number.rs | 24 ++-- rpc/src/v1/types/filter.rs | 12 +- rpc/src/v1/types/trace_filter.rs | 6 +- sync/src/block_sync.rs | 6 +- sync/src/blocks.rs | 10 +- sync/src/chain.rs | 34 ++--- sync/src/tests/chain.rs | 12 +- util/Cargo.toml | 1 + util/src/lib.rs | 1 + util/src/misc.rs | 122 ++++++++++++++-- 48 files changed, 634 insertions(+), 466 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 300929e6eeb..d5e7fab1e12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,6 +581,7 @@ dependencies = [ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "sha3 0.1.0", "table 0.1.0", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1574,6 +1575,23 @@ dependencies = [ "nom 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "semver" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "serde" version = "0.8.4" @@ -2112,6 +2130,8 @@ dependencies = [ "checksum rustls 0.1.2 (git+https://github.com/ctz/rustls)" = "" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f" +"checksum semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2ff60ecdb19c255841c066cbfa5f8c2a4ada1eb3ae47c77ab6667128da71f5" +"checksum semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e88e43a5a74dd2a11707f9c21dfd4a423c66bd871df813227bb0a3e78f3a1ae9" "checksum serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b1dfda9ebb31d29fa8b94d7eb3031a86a8dcec065f0fe268a30f98867bf45775" "checksum serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e422ae53d7933f59c6ff57e7b5870b5c9094b1f473f78ec33d89f8a692c3ec02" "checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21" diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index e3b5745b2f7..3a7706bef6a 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use ethcore::engines::Engine; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::service::ClientIoMessage; use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; @@ -51,7 +51,7 @@ impl Client { } /// Whether the block is already known (but not necessarily part of the canonical chain) - pub fn is_known(&self, _id: BlockID) -> bool { + pub fn is_known(&self, _id: BlockId) -> bool { false } @@ -61,7 +61,7 @@ impl Client { } /// Inquire about the status of a given block. - pub fn status(&self, _id: BlockID) -> BlockStatus { + pub fn status(&self, _id: BlockId) -> BlockStatus { BlockStatus::Unknown } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 66fad24d29d..a4fa5aa57c5 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use log_entry::LocalizedLogEntry; use verification::queue::BlockQueue; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ - BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, + BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, }; @@ -594,13 +594,13 @@ impl Client { /// Attempt to get a copy of a specific block's final state. /// - /// This will not fail if given BlockID::Latest. + /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. - pub fn state_at(&self, id: BlockID) -> Option { + pub fn state_at(&self, id: BlockId) -> Option { // fast path for latest state. match id.clone() { - BlockID::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), - BlockID::Latest => return Some(self.state()), + BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), + BlockId::Latest => return Some(self.state()), _ => {}, } @@ -625,15 +625,15 @@ impl Client { /// Attempt to get a copy of a specific block's beginning state. /// - /// This will not fail if given BlockID::Latest. + /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. - pub fn state_at_beginning(&self, id: BlockID) -> Option { + pub fn state_at_beginning(&self, id: BlockId) -> Option { // fast path for latest state. match id { - BlockID::Pending => self.state_at(BlockID::Latest), + BlockId::Pending => self.state_at(BlockId::Latest), id => match self.block_number(id) { None | Some(0) => None, - Some(n) => self.state_at(BlockID::Number(n - 1)), + Some(n) => self.state_at(BlockId::Number(n - 1)), } } } @@ -724,30 +724,31 @@ impl Client { data: data, }.fake_sign(from); - self.call(&transaction, BlockID::Latest, Default::default()) + self.call(&transaction, BlockId::Latest, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output }) } + /// Get the updater object. pub fn updater(&self) -> MutexGuard> { self.updater.lock() } - /// Look up the block number for the given block ID. - pub fn block_number(&self, id: BlockID) -> Option { + /// Look up the block number for the given block Id. + pub fn block_number(&self, id: BlockId) -> Option { match id { - BlockID::Number(number) => Some(number), - BlockID::Hash(ref hash) => self.chain.read().block_number(hash), - BlockID::Earliest => Some(0), - BlockID::Latest | BlockID::Pending => Some(self.chain.read().best_block_number()), + BlockId::Number(number) => Some(number), + BlockId::Hash(ref hash) => self.chain.read().block_number(hash), + BlockId::Earliest => Some(0), + BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), } } /// Take a snapshot at the given block. - /// If the ID given is "latest", this will default to 1000 blocks behind. - pub fn take_snapshot(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), EthcoreError> { + /// If the Id given is "latest", this will default to 1000 blocks behind. + pub fn take_snapshot(&self, writer: W, at: BlockId, p: &snapshot::Progress) -> Result<(), EthcoreError> { let db = self.state_db.lock().journal_db().boxed_clone(); let best_block_number = self.chain_info().best_block_number; let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); @@ -759,13 +760,13 @@ impl Client { let history = ::std::cmp::min(self.history, 1000); let start_hash = match at { - BlockID::Latest => { + BlockId::Latest => { let start_num = match db.earliest_era() { Some(era) => ::std::cmp::max(era, best_block_number - history), None => best_block_number - history, }; - match self.block_hash(BlockID::Number(start_num)) { + match self.block_hash(BlockId::Number(start_num)) { Some(h) => h, None => return Err(snapshot::Error::InvalidStartingBlock(at).into()), } @@ -786,19 +787,19 @@ impl Client { self.history } - fn block_hash(chain: &BlockChain, id: BlockID) -> Option { + fn block_hash(chain: &BlockChain, id: BlockId) -> Option { match id { - BlockID::Hash(hash) => Some(hash), - BlockID::Number(number) => chain.block_hash(number), - BlockID::Earliest => chain.block_hash(0), - BlockID::Latest | BlockID::Pending => Some(chain.best_block_hash()), + BlockId::Hash(hash) => Some(hash), + BlockId::Number(number) => chain.block_hash(number), + BlockId::Earliest => chain.block_hash(0), + BlockId::Latest | BlockId::Pending => Some(chain.best_block_hash()), } } - fn transaction_address(&self, id: TransactionID) -> Option { + fn transaction_address(&self, id: TransactionId) -> Option { match id { - TransactionID::Hash(ref hash) => self.chain.read().transaction_address(hash), - TransactionID::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { + TransactionId::Hash(ref hash) => self.chain.read().transaction_address(hash), + TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { block_hash: hash, index: index, }) @@ -852,7 +853,7 @@ impl snapshot::DatabaseRestore for Client { impl BlockChainClient for Client { - fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result { + fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { let header = try!(self.block_header(block).ok_or(CallError::StatePruned)); let view = HeaderView::new(&header); let last_hashes = self.build_last_hashes(view.parent_hash()); @@ -888,11 +889,11 @@ impl BlockChainClient for Client { Ok(ret) } - fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result { + fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { let address = try!(self.transaction_address(id).ok_or(CallError::TransactionNotFound)); - let header_data = try!(self.block_header(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let body_data = try!(self.block_body(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let mut state = try!(self.state_at_beginning(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let header_data = try!(self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let body_data = try!(self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let mut state = try!(self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); let txs = BodyView::new(&body_data).transactions(); if address.index >= txs.len() { @@ -965,18 +966,18 @@ impl BlockChainClient for Client { self.chain.read().best_block_header() } - fn block_header(&self, id: BlockID) -> Option { + fn block_header(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash)) } - fn block_body(&self, id: BlockID) -> Option { + fn block_body(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash)) } - fn block(&self, id: BlockID) -> Option { - if let BlockID::Pending = id { + fn block(&self, id: BlockId) -> Option { + if let BlockId::Pending = id { if let Some(block) = self.miner.pending_block() { return Some(block.rlp_bytes(Seal::Without)); } @@ -987,7 +988,7 @@ impl BlockChainClient for Client { }) } - fn block_status(&self, id: BlockID) -> BlockStatus { + fn block_status(&self, id: BlockId) -> BlockStatus { let chain = self.chain.read(); match Self::block_hash(&chain, id) { Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain, @@ -996,38 +997,38 @@ impl BlockChainClient for Client { } } - fn block_total_difficulty(&self, id: BlockID) -> Option { - if let BlockID::Pending = id { + fn block_total_difficulty(&self, id: BlockId) -> Option { + if let BlockId::Pending = id { if let Some(block) = self.miner.pending_block() { - return Some(*block.header.difficulty() + self.block_total_difficulty(BlockID::Latest).expect("blocks in chain have details; qed")); + return Some(*block.header.difficulty() + self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed")); } } let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) } - fn nonce(&self, address: &Address, id: BlockID) -> Option { + fn nonce(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).map(|s| s.nonce(address)) } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id) } - fn code(&self, address: &Address, id: BlockID) -> Option> { + fn code(&self, address: &Address, id: BlockId) -> Option> { self.state_at(id).map(|s| s.code(address).map(|c| (*c).clone())) } - fn balance(&self, address: &Address, id: BlockID) -> Option { + fn balance(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).map(|s| s.balance(address)) } - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option { + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { self.state_at(id).map(|s| s.storage_at(address, position)) } - fn list_accounts(&self, id: BlockID) -> Option> { + fn list_accounts(&self, id: BlockId) -> Option> { if !self.factories.trie.is_fat() { trace!(target: "fatdb", "list_accounts: Not a fat DB"); return None; @@ -1059,16 +1060,16 @@ impl BlockChainClient for Client { Some(accounts) } - fn transaction(&self, id: TransactionID) -> Option { + fn transaction(&self, id: TransactionId) -> Option { self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) } - fn uncle(&self, id: UncleID) -> Option { + fn uncle(&self, id: UncleId) -> Option { let index = id.position; self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) } - fn transaction_receipt(&self, id: TransactionID) -> Option { + fn transaction_receipt(&self, id: TransactionId) -> Option { let chain = self.chain.read(); self.transaction_address(id) .and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| { @@ -1152,7 +1153,7 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&unverified.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockID::Hash(unverified.parent_hash())) == BlockStatus::Unknown { + if self.block_status(BlockId::Hash(unverified.parent_hash())) == BlockStatus::Unknown { return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash()))); } } @@ -1166,7 +1167,7 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&header.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown { + if self.block_status(BlockId::Hash(header.parent_hash())) == BlockStatus::Unknown { return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); } } @@ -1189,7 +1190,7 @@ impl BlockChainClient for Client { self.engine.additional_params().into_iter().collect() } - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option> { + fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option> { match (self.block_number(from_block), self.block_number(to_block)) { (Some(from), Some(to)) => Some(self.chain.read().blocks_with_bloom(bloom, from, to)), _ => None @@ -1231,20 +1232,20 @@ impl BlockChainClient for Client { let trace_address = trace.address; self.transaction_address(trace.transaction) .and_then(|tx_address| { - self.block_number(BlockID::Hash(tx_address.block_hash)) + self.block_number(BlockId::Hash(tx_address.block_hash)) .and_then(|number| self.tracedb.read().trace(number, tx_address.index, trace_address)) }) } - fn transaction_traces(&self, transaction: TransactionID) -> Option> { + fn transaction_traces(&self, transaction: TransactionId) -> Option> { self.transaction_address(transaction) .and_then(|tx_address| { - self.block_number(BlockID::Hash(tx_address.block_hash)) + self.block_number(BlockId::Hash(tx_address.block_hash)) .and_then(|number| self.tracedb.read().transaction_traces(number, tx_address.index)) }) } - fn block_traces(&self, block: BlockID) -> Option> { + fn block_traces(&self, block: BlockId) -> Option> { self.block_number(block) .and_then(|number| self.tracedb.read().block_traces(number)) } @@ -1279,13 +1280,13 @@ impl BlockChainClient for Client { self.engine.signing_network_id(&self.latest_env_info()) } - fn block_extra_info(&self, id: BlockID) -> Option> { + fn block_extra_info(&self, id: BlockId) -> Option> { self.block_header(id) .map(|block| decode(&block)) .map(|header| self.engine.extra_info(&header)) } - fn uncle_extra_info(&self, id: UncleID) -> Option> { + fn uncle_extra_info(&self, id: UncleId) -> Option> { self.uncle(id) .map(|header| self.engine.extra_info(&decode(&header))) } diff --git a/ethcore/src/client/operations.rs b/ethcore/src/client/operations.rs index 3141f1c250e..e0408cdb06e 100644 --- a/ethcore/src/client/operations.rs +++ b/ethcore/src/client/operations.rs @@ -14,26 +14,14 @@ pub struct Operations { impl Operations { pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { Operations { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"owners\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"findRelease\",\"outputs\":[{\"name\":\"o_forkBlock\",\"type\":\"uint32\"},{\"name\":\"o_track\",\"type\":\"uint8\"},{\"name\":\"o_semver\",\"type\":\"uint24\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"findBuild\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"forks\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"}],\"name\":\"findChecksum\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"}],\"name\":\"addRelease\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"clients\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"type\":\"function\"},{\"inputs\":[],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"Received\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"gas\",\"type\":\"uint256\"}],\"name\":\"TransactionProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"}],\"name\":\"TransactionRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"txid\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"TransactionRelayed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"spec\",\"type\":\"bytes32\"}],\"name\":\"ForkProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkAcceptedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"number\",\"type\":\"uint32\"}],\"name\":\"ForkRejectedBy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRejected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"forkNumber\",\"type\":\"uint32\"}],\"name\":\"ForkRatified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"forkBlock\",\"type\":\"uint32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"track\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"semver\",\"type\":\"uint24\"}],\"name\":\"ReleaseAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"release\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"platform\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"checksum\",\"type\":\"bytes32\"}],\"name\":\"ChecksumAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ClientAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"}],\"name\":\"ClientRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"ClientOwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"client\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"bool\"}],\"name\":\"ClientRequiredChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"now\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_hard\",\"type\":\"bool\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"release\",\"outputs\":[{\"name\":\"o_forkBlock\",\"type\":\"uint32\"},{\"name\":\"o_track\",\"type\":\"uint8\"},{\"name\":\"o_semver\",\"type\":\"uint24\"},{\"name\":\"o_critical\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"build\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"client\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"fork\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"hard\",\"type\":\"bool\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"clientOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"grandOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"},{\"name\":\"_critical\",\"type\":\"bool\"}],\"name\":\"addRelease\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"}],\"name\":\"checksum\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), address: address, do_call: Box::new(do_call), } } fn as_string(e: T) -> String { format!("{:?}", e) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"owners","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` - #[allow(dead_code)] - pub fn owners(&self, _1: &util::Address) -> Result { - let call = self.contract.function("owners".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![ethabi::Token::Address(_1.clone().0)] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) - } - - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn reset_client_owner(&self, _client: &str, _new_owner: &util::Address) -> Result<(), String> { let call = self.contract.function("resetClientOwner".into()).map_err(Self::as_string)?; @@ -45,7 +33,7 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn is_latest(&self, _client: &str, _release: &util::H256) -> Result { let call = self.contract.function("isLatest".into()).map_err(Self::as_string)?; @@ -57,20 +45,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"findRelease","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"}],"type":"function"}` - #[allow(dead_code)] - pub fn find_release(&self, _client: &str, _release: &util::H256) -> Result<(u32, u8, u32), String> { - let call = self.contract.function("findRelease".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] - ).map_err(Self::as_string)?; - let output = (self.do_call)(self.address.clone(), data)?; - let returned = call.decode_output(output).map_err(Self::as_string)?; - let mut result = returned.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u8 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) - } - - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn reject_transaction(&self, _txid: &util::H256) -> Result<(), String> { let call = self.contract.function("rejectTransaction".into()).map_err(Self::as_string)?; @@ -82,7 +57,7 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn set_owner(&self, _new_owner: &util::Address) -> Result<(), String> { let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?; @@ -94,34 +69,46 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_hard","type":"bool"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn remove_client(&self, _client: &str) -> Result<(), String> { - let call = self.contract.function("removeClient".into()).map_err(Self::as_string)?; + pub fn propose_fork(&self, _number: u32, _name: &util::H256, _hard: bool, _spec: &util::H256) -> Result<(), String> { + let call = self.contract.function("proposeFork".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned())] + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_number as u64).to_big_endian(&mut r); r }), ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::Bool(_hard), ethabi::Token::FixedBytes(_spec.as_ref().to_owned())] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reject_fork(&self) -> Result<(), String> { - let call = self.contract.function("rejectFork".into()).map_err(Self::as_string)?; + pub fn remove_client(&self, _client: &str) -> Result<(), String> { + let call = self.contract.function("removeClient".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned())] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"findBuild","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"release","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"},{"name":"o_critical","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn release(&self, _client: &str, _release: &util::H256) -> Result<(u32, u8, u32, bool), String> { + let call = self.contract.function("release".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u8 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"build","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn find_build(&self, _client: &str, _checksum: &util::H256) -> Result<(util::H256, util::H256), String> { - let call = self.contract.function("findBuild".into()).map_err(Self::as_string)?; + pub fn build(&self, _client: &str, _checksum: &util::H256) -> Result<(util::H256, util::H256), String> { + let call = self.contract.function("build".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_checksum.as_ref().to_owned())] ).map_err(Self::as_string)?; @@ -130,19 +117,31 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn propose_fork(&self, _number: u32, _name: &util::H256, _spec: &util::H256) -> Result<(), String> { - let call = self.contract.function("proposeFork".into()).map_err(Self::as_string)?; + pub fn reject_fork(&self) -> Result<(), String> { + let call = self.contract.function("rejectFork".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_number as u64).to_big_endian(&mut r); r }), ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::FixedBytes(_spec.as_ref().to_owned())] + vec![] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"client","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn client(&self, _1: &util::H256) -> Result<(util::Address, bool), String> { + let call = self.contract.function("client".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_1.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn set_client_owner(&self, _new_owner: &util::Address) -> Result<(), String> { let call = self.contract.function("setClientOwner".into()).map_err(Self::as_string)?; @@ -154,7 +153,19 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"fork","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"hard","type":"bool"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn fork(&self, _1: u32) -> Result<(util::H256, util::H256, bool, bool, util::U256), String> { + let call = self.contract.function("fork".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_1 as u64).to_big_endian(&mut r); r })] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn add_checksum(&self, _release: &util::H256, _platform: &str, _checksum: &util::H256) -> Result<(), String> { let call = self.contract.function("addChecksum".into()).map_err(Self::as_string)?; @@ -166,7 +177,7 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn confirm_transaction(&self, _txid: &util::H256) -> Result { let call = self.contract.function("confirmTransaction".into()).map_err(Self::as_string)?; @@ -178,7 +189,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn proxy(&self, _1: &util::H256) -> Result<(util::U256, util::Address, Vec, util::U256, util::U256), String> { let call = self.contract.function("proxy".into()).map_err(Self::as_string)?; @@ -190,7 +201,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bytes().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn add_client(&self, _client: &str, _owner: &util::Address) -> Result<(), String> { let call = self.contract.function("addClient".into()).map_err(Self::as_string)?; @@ -202,67 +213,67 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"forks","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"clientOwner","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn forks(&self, _1: u32) -> Result<(util::H256, util::H256, bool, util::U256), String> { - let call = self.contract.function("forks".into()).map_err(Self::as_string)?; + pub fn client_owner(&self, _1: &util::Address) -> Result { + let call = self.contract.function("clientOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_1 as u64).to_big_endian(&mut r); r })] + vec![ethabi::Token::Address(_1.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) } - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn owner(&self) -> Result { - let call = self.contract.function("owner".into()).map_err(Self::as_string)?; + pub fn propose_transaction(&self, _txid: &util::H256, _to: &util::Address, _data: &[u8], _value: util::U256, _gas: util::U256) -> Result { + let call = self.contract.function("proposeTransaction".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![] + vec![ethabi::Token::FixedBytes(_txid.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0), ethabi::Token::Bytes(_data.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; _gas.to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"grandOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn propose_transaction(&self, _txid: &util::H256, _to: &util::Address, _data: &[u8], _value: util::U256, _gas: util::U256) -> Result { - let call = self.contract.function("proposeTransaction".into()).map_err(Self::as_string)?; + pub fn grand_owner(&self) -> Result { + let call = self.contract.function("grandOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_txid.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0), ethabi::Token::Bytes(_data.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; _gas.to_big_endian(&mut r); r })] + vec![] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) } - /// Auto-generated from: `{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"},{"name":"_critical","type":"bool"}],"name":"addRelease","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn accept_fork(&self) -> Result<(), String> { - let call = self.contract.function("acceptFork".into()).map_err(Self::as_string)?; + pub fn add_release(&self, _release: &util::H256, _fork_block: u32, _track: u8, _semver: u32, _critical: bool) -> Result<(), String> { + let call = self.contract.function("addRelease".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![] + vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_fork_block as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_track as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_semver as u64).to_big_endian(&mut r); r }), ethabi::Token::Bool(_critical)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"findChecksum","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn find_checksum(&self, _client: &str, _release: &util::H256, _platform: &str) -> Result { - let call = self.contract.function("findChecksum".into()).map_err(Self::as_string)?; + pub fn accept_fork(&self) -> Result<(), String> { + let call = self.contract.function("acceptFork".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_bytes().to_owned())] + vec![] ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) } - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn clients_required(&self) -> Result { let call = self.contract.function("clientsRequired".into()).map_err(Self::as_string)?; @@ -274,7 +285,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn track(&self, _client: &str, _release: &util::H256) -> Result { let call = self.contract.function("track".into()).map_err(Self::as_string)?; @@ -286,7 +297,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u8 })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn set_client_required(&self, _client: &str, _r: bool) -> Result<(), String> { let call = self.contract.function("setClientRequired".into()).map_err(Self::as_string)?; @@ -298,19 +309,7 @@ impl Operations { Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"}],"name":"addRelease","outputs":[],"type":"function"}` - #[allow(dead_code)] - pub fn add_release(&self, _release: &util::H256, _fork_block: u32, _track: u8, _semver: u32) -> Result<(), String> { - let call = self.contract.function("addRelease".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_fork_block as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_track as u64).to_big_endian(&mut r); r }), ethabi::Token::Uint({ let mut r = [0u8; 32]; util::U256::from(_semver as u64).to_big_endian(&mut r); r })] - ).map_err(Self::as_string)?; - call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - - Ok(()) - } - - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn latest_fork(&self) -> Result { let call = self.contract.function("latestFork".into()).map_err(Self::as_string)?; @@ -322,7 +321,7 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()).as_u64() as u32 })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn latest_in_track(&self, _client: &str, _track: u8) -> Result { let call = self.contract.function("latestInTrack".into()).map_err(Self::as_string)?; @@ -334,19 +333,19 @@ impl Operations { Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"clients","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"checksum","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn clients(&self, _1: &util::H256) -> Result<(util::Address, bool), String> { - let call = self.contract.function("clients".into()).map_err(Self::as_string)?; + pub fn checksum(&self, _client: &str, _release: &util::H256, _platform: &str) -> Result { + let call = self.contract.function("checksum".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_1.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_client.as_bytes().to_owned()), ethabi::Token::FixedBytes(_release.as_ref().to_owned()), ethabi::Token::FixedBytes(_platform.as_bytes().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) }, { let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) } - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"}` #[allow(dead_code)] pub fn proposed_fork(&self) -> Result { let call = self.contract.function("proposedFork".into()).map_err(Self::as_string)?; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 84ed25b371a..f7f57291459 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -24,8 +24,8 @@ use devtools::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; use client::{ - BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, - TransactionID, UncleID, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, + BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, + TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, }; use db::{NUM_COLUMNS, COL_STATE}; use header::{Header as BlockHeader, BlockNumber}; @@ -72,7 +72,7 @@ pub struct TestBlockChainClient { /// Execution result. pub execution_result: RwLock>>, /// Transaction receipts. - pub receipts: RwLock>, + pub receipts: RwLock>, /// Logs pub logs: RwLock>, /// Block queue size. @@ -157,7 +157,7 @@ impl TestBlockChainClient { } /// Set the transaction receipt result - pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { + pub fn set_transaction_receipt(&self, id: TransactionId, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); } @@ -255,8 +255,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid extra data. pub fn corrupt_block(&mut self, n: BlockNumber) { - let hash = self.block_hash(BlockID::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); + let hash = self.block_hash(BlockId::Number(n)).unwrap(); + let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -267,8 +267,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid parent hash. pub fn corrupt_block_parent(&mut self, n: BlockNumber) { - let hash = self.block_hash(BlockID::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); + let hash = self.block_hash(BlockId::Number(n)).unwrap(); + let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); header.set_parent_hash(H256::from(42)); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -284,12 +284,12 @@ impl TestBlockChainClient { blocks_read[&index].clone() } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { match id { - BlockID::Hash(hash) => Some(hash), - BlockID::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), - BlockID::Earliest => self.numbers.read().get(&0).cloned(), - BlockID::Latest | BlockID::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() + BlockId::Hash(hash) => Some(hash), + BlockId::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), + BlockId::Earliest => self.numbers.read().get(&0).cloned(), + BlockId::Latest | BlockId::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() } } @@ -362,42 +362,42 @@ impl MiningBlockChainClient for TestBlockChainClient { } impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction, _block: BlockID, _analytics: CallAnalytics) -> Result { + fn call(&self, _t: &SignedTransaction, _block: BlockId, _analytics: CallAnalytics) -> Result { self.execution_result.read().clone().unwrap() } - fn replay(&self, _id: TransactionID, _analytics: CallAnalytics) -> Result { + fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result { self.execution_result.read().clone().unwrap() } - fn block_total_difficulty(&self, _id: BlockID) -> Option { + fn block_total_difficulty(&self, _id: BlockId) -> Option { Some(U256::zero()) } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { Self::block_hash(self, id) } - fn nonce(&self, address: &Address, id: BlockID) -> Option { + fn nonce(&self, address: &Address, id: BlockId) -> Option { match id { - BlockID::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), + BlockId::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), _ => None, } } fn latest_nonce(&self, address: &Address) -> U256 { - self.nonce(address, BlockID::Latest).unwrap() + self.nonce(address, BlockId::Latest).unwrap() } - fn code(&self, address: &Address, id: BlockID) -> Option> { + fn code(&self, address: &Address, id: BlockId) -> Option> { match id { - BlockID::Latest => Some(self.code.read().get(address).cloned()), + BlockId::Latest => Some(self.code.read().get(address).cloned()), _ => None, } } - fn balance(&self, address: &Address, id: BlockID) -> Option { - if let BlockID::Latest = id { + fn balance(&self, address: &Address, id: BlockId) -> Option { + if let BlockId::Latest = id { Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)) } else { None @@ -405,38 +405,38 @@ impl BlockChainClient for TestBlockChainClient { } fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockID::Latest).unwrap() + self.balance(address, BlockId::Latest).unwrap() } - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option { - if let BlockID::Latest = id { + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { + if let BlockId::Latest = id { Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)) } else { None } } - fn list_accounts(&self, _id: BlockID) -> Option> { + fn list_accounts(&self, _id: BlockId) -> Option> { None } - fn transaction(&self, _id: TransactionID) -> Option { + fn transaction(&self, _id: TransactionId) -> Option { None // Simple default. } - fn uncle(&self, _id: UncleID) -> Option { + fn uncle(&self, _id: UncleId) -> Option { None // Simple default. } - fn uncle_extra_info(&self, _id: UncleID) -> Option> { + fn uncle_extra_info(&self, _id: UncleId) -> Option> { None } - fn transaction_receipt(&self, id: TransactionID) -> Option { + fn transaction_receipt(&self, id: TransactionId) -> Option { self.receipts.read().get(&id).cloned() } - fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockID, _to_block: BlockID) -> Option> { + fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockId, _to_block: BlockId) -> Option> { unimplemented!(); } @@ -454,14 +454,14 @@ impl BlockChainClient for TestBlockChainClient { } fn best_block_header(&self) -> Bytes { - self.block_header(BlockID::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") + self.block_header(BlockId::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") } - fn block_header(&self, id: BlockID) -> Option { + fn block_header(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) } - fn block_body(&self, id: BlockID) -> Option { + fn block_body(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| { let mut stream = RlpStream::new_list(2); stream.append_raw(Rlp::new(r).at(1).as_raw(), 1); @@ -470,21 +470,21 @@ impl BlockChainClient for TestBlockChainClient { })) } - fn block(&self, id: BlockID) -> Option { + fn block(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) } - fn block_extra_info(&self, id: BlockID) -> Option> { + fn block_extra_info(&self, id: BlockId) -> Option> { self.block(id) .map(|block| BlockView::new(&block).header()) .map(|header| self.spec.engine.extra_info(&header)) } - fn block_status(&self, id: BlockID) -> BlockStatus { + fn block_status(&self, id: BlockId) -> BlockStatus { match id { - BlockID::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, - BlockID::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, + BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, + BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, _ => BlockStatus::Unknown } } @@ -637,11 +637,11 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn transaction_traces(&self, _trace: TransactionID) -> Option> { + fn transaction_traces(&self, _trace: TransactionId) -> Option> { unimplemented!(); } - fn block_traces(&self, _trace: BlockID) -> Option> { + fn block_traces(&self, _trace: BlockId) -> Option> { unimplemented!(); } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 67092e98688..a6605c43488 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -49,81 +49,81 @@ pub trait BlockChainClient : Sync + Send { fn keep_alive(&self) {} /// Get raw block header data by block id. - fn block_header(&self, id: BlockID) -> Option; + fn block_header(&self, id: BlockId) -> Option; /// Get raw block body data by block id. /// Block body is an RLP list of two items: uncles and transactions. - fn block_body(&self, id: BlockID) -> Option; + fn block_body(&self, id: BlockId) -> Option; /// Get raw block data by block header hash. - fn block(&self, id: BlockID) -> Option; + fn block(&self, id: BlockId) -> Option; /// Get block status by block header hash. - fn block_status(&self, id: BlockID) -> BlockStatus; + fn block_status(&self, id: BlockId) -> BlockStatus; /// Get block total difficulty. - fn block_total_difficulty(&self, id: BlockID) -> Option; + fn block_total_difficulty(&self, id: BlockId) -> Option; /// Attempt to get address nonce at given block. - /// May not fail on BlockID::Latest. - fn nonce(&self, address: &Address, id: BlockID) -> Option; + /// May not fail on BlockId::Latest. + fn nonce(&self, address: &Address, id: BlockId) -> Option; /// Get address nonce at the latest block's state. fn latest_nonce(&self, address: &Address) -> U256 { - self.nonce(address, BlockID::Latest) - .expect("nonce will return Some when given BlockID::Latest. nonce was given BlockID::Latest. \ + self.nonce(address, BlockId::Latest) + .expect("nonce will return Some when given BlockId::Latest. nonce was given BlockId::Latest. \ Therefore nonce has returned Some; qed") } /// Get block hash. - fn block_hash(&self, id: BlockID) -> Option; + fn block_hash(&self, id: BlockId) -> Option; /// Get address code at given block's state. - fn code(&self, address: &Address, id: BlockID) -> Option>; + fn code(&self, address: &Address, id: BlockId) -> Option>; /// Get address code at the latest block's state. fn latest_code(&self, address: &Address) -> Option { - self.code(address, BlockID::Latest) - .expect("code will return Some if given BlockID::Latest; qed") + self.code(address, BlockId::Latest) + .expect("code will return Some if given BlockId::Latest; qed") } /// Get address balance at the given block's state. /// - /// May not return None if given BlockID::Latest. + /// May not return None if given BlockId::Latest. /// Returns None if and only if the block's root hash has been pruned from the DB. - fn balance(&self, address: &Address, id: BlockID) -> Option; + fn balance(&self, address: &Address, id: BlockId) -> Option; /// Get address balance at the latest block's state. fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockID::Latest) - .expect("balance will return Some if given BlockID::Latest. balance was given BlockID::Latest \ + self.balance(address, BlockId::Latest) + .expect("balance will return Some if given BlockId::Latest. balance was given BlockId::Latest \ Therefore balance has returned Some; qed") } /// Get value of the storage at given position at the given block's state. /// - /// May not return None if given BlockID::Latest. + /// May not return None if given BlockId::Latest. /// Returns None if and only if the block's root hash has been pruned from the DB. - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option; + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option; /// Get value of the storage at given position at the latest block's state. fn latest_storage_at(&self, address: &Address, position: &H256) -> H256 { - self.storage_at(address, position, BlockID::Latest) - .expect("storage_at will return Some if given BlockID::Latest. storage_at was given BlockID::Latest. \ + self.storage_at(address, position, BlockId::Latest) + .expect("storage_at will return Some if given BlockId::Latest. storage_at was given BlockId::Latest. \ Therefore storage_at has returned Some; qed") } /// Get a list of all accounts in the block `id`, if fat DB is in operation, otherwise `None`. - fn list_accounts(&self, id: BlockID) -> Option>; + fn list_accounts(&self, id: BlockId) -> Option>; /// Get transaction with given hash. - fn transaction(&self, id: TransactionID) -> Option; + fn transaction(&self, id: TransactionId) -> Option; /// Get uncle with given id. - fn uncle(&self, id: UncleID) -> Option; + fn uncle(&self, id: UncleId) -> Option; /// Get transaction receipt with given hash. - fn transaction_receipt(&self, id: TransactionID) -> Option; + fn transaction_receipt(&self, id: TransactionId) -> Option; /// Get a tree route between `from` and `to`. /// See `BlockChain::tree_route`. @@ -160,16 +160,16 @@ pub trait BlockChainClient : Sync + Send { fn best_block_header(&self) -> Bytes; /// Returns numbers of blocks containing given bloom. - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option>; + fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option>; /// Returns logs matching given filter. fn logs(&self, filter: Filter) -> Vec; /// Makes a non-persistent transaction call. - fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result; + fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result; /// Replays a given transaction for inspection. - fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result; + fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result; /// Returns traces matching given filter. fn filter_traces(&self, filter: TraceFilter) -> Option>; @@ -178,10 +178,10 @@ pub trait BlockChainClient : Sync + Send { fn trace(&self, trace: TraceId) -> Option; /// Returns traces created by transaction. - fn transaction_traces(&self, trace: TransactionID) -> Option>; + fn transaction_traces(&self, trace: TransactionId) -> Option>; /// Returns traces created by transaction from block. - fn block_traces(&self, trace: BlockID) -> Option>; + fn block_traces(&self, trace: BlockId) -> Option>; /// Get last hashes starting from best block. fn last_hashes(&self) -> LastHashes; @@ -198,7 +198,7 @@ pub trait BlockChainClient : Sync + Send { let mut corpus = Vec::new(); while corpus.is_empty() { for _ in 0..sample_size { - let block_bytes = self.block(BlockID::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); + let block_bytes = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); let block = BlockView::new(&block_bytes); let header = block.header_view(); if header.number() == 0 { @@ -236,11 +236,11 @@ pub trait BlockChainClient : Sync + Send { /// Set the mode. fn set_mode(&self, mode: Mode); - /// Returns engine-related extra info for `BlockID`. - fn block_extra_info(&self, id: BlockID) -> Option>; + /// Returns engine-related extra info for `BlockId`. + fn block_extra_info(&self, id: BlockId) -> Option>; - /// Returns engine-related extra info for `UncleID`. - fn uncle_extra_info(&self, id: UncleID) -> Option>; + /// Returns engine-related extra info for `UncleId`. + fn uncle_extra_info(&self, id: UncleId) -> Option>; } /// Extended client interface used for mining diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 4fb210ca635..211fdeb1fb3 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -15,45 +15,81 @@ // along with Parity. If not, see . use std::sync::Weak; -use util::misc::code_hash; -use util::{Address, H160}; +use util::misc::{VersionInfo, ReleaseTrack, platform}; +use util::{Address, H160, H256, FixedHash}; use client::operations::Operations; use client::client::Client; +use client::BlockId; + +pub struct ReleaseInfo { + fork_supported: usize, + latest_known_fork: usize, + + latest: VersionInfo, + latest_fork: usize, + latest_binary: Option, +} pub struct Updater { + client: Weak, operations: Operations, -} -fn platform() -> &'static str { - "linux_x64" + pub this: VersionInfo, + pub release_info: Option, + } impl Updater { pub fn new(client: Weak, operations: Address) -> Self { - Updater { + let mut u = Updater { + client: client.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), - } + this: VersionInfo::this(), + release_info: None, + }; + u.release_info = u.get_release_info().ok(); + if u.this.track == ReleaseTrack::Unknown { + u.this.track = ReleaseTrack::Nightly; + } + u } - pub fn tick(&mut self) { - (|| -> Result<(), String> { - let code_hash = H160::from("0x080ec8043f41e25ee8aa4ee6112906ac6d82ea74").into();//code_hash().into(); - let client = "parity"; - - let (fork, track, semver) = self.operations.find_release(client, &code_hash)?; - let track_name = match track { 1 => "stable", 2 => "beta", 3 => "nightly", _ => "unknown" }; - info!(target: "updater", "Current release ({}) is {}.{}.{}-{} and latest fork it supports is at block #{}", H160::from(code_hash), semver >> 16, (semver >> 8) & 0xff, semver & 0xff, track_name, fork); - - let latest_fork = self.operations.latest_fork()?; - info!(target: "updater", "Latest fork is at block #{}", latest_fork); + fn get_release_info(&mut self) -> Result { + //601e0fb0fd7e9e1cec18f8872e8713117cab4e84 + if self.this.track == ReleaseTrack::Unknown { + return Err(format!("Current executable ({}) is unreleased.", H160::from(self.this.hash))); + } - let latest = self.operations.latest_in_track(client, track)?; - let (fork, _, semver) = self.operations.find_release(client, &latest)?; - info!(target: "updater", "Latest release in our track is {}.{}.{}-{} ({:?}); supports fork at block #{}", semver >> 16, (semver >> 8) & 0xff, semver & 0xff, track_name, H160::from(latest), fork); + let client_id = "parity"; + let latest_known_fork = self.operations.latest_fork()?; + let our_fork = self.operations.release(client_id, &self.this.hash.into())?.0; + let latest_release = self.operations.latest_in_track(client_id, self.this.track.into())?; + let (fork, track, semver, _critical) = self.operations.release(client_id, &latest_release)?; + let maybe_latest_binary = self.operations.checksum(client_id, &latest_release, &platform())?; + Ok(ReleaseInfo { + fork_supported: our_fork as usize, + latest_known_fork: latest_known_fork as usize, + latest: VersionInfo::from_raw(semver, track, latest_release.into()), + latest_fork: fork as usize, + latest_binary: if maybe_latest_binary.is_zero() { None } else { Some(maybe_latest_binary) }, + }) + } - let exe_hash = self.operations.find_checksum(client, &latest, platform())?; - info!(target: "updater", "Latest release's binary on {} is {}", platform(), exe_hash); - Ok(()) - })().unwrap_or_else(|e| warn!("{}", e)); + pub fn tick(&mut self) { + self.release_info = self.get_release_info().ok(); + let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); + info!(target: "updater", "Current release is {}", self.this); + if let Some(ref relinfo) = self.release_info { + info!(target: "updater", "Latest release in our track is {} ({} binary is {})", + relinfo.latest, + platform(), + if let Some(ref b) = relinfo.latest_binary { + format!("{}", b) + } else { + "unreleased".into() + } + ); + info!(target: "updater", "Fork: this/current/latest/latest-known: #{}/#{}/#{}/#{}", relinfo.fork_supported, current_number, relinfo.latest_fork, relinfo.latest_known_fork); + } } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7ad18ebfc05..29f28265fe7 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,7 +23,7 @@ use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics}; use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; @@ -693,7 +693,7 @@ impl MinerService for Miner { Ok(ret) }, None => { - chain.call(t, BlockID::Latest, analytics) + chain.call(t, BlockId::Latest, analytics) } } } @@ -1073,7 +1073,7 @@ impl MinerService for Miner { fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec { let block = chain - .block(BlockID::Hash(*hash)) + .block(BlockId::Hash(*hash)) // Client should send message after commit to db and inserting to chain. .expect("Expected in-chain blocks."); let block = BlockView::new(&block); diff --git a/ethcore/src/snapshot/error.rs b/ethcore/src/snapshot/error.rs index d417695f054..cc84d8e489e 100644 --- a/ethcore/src/snapshot/error.rs +++ b/ethcore/src/snapshot/error.rs @@ -18,7 +18,7 @@ use std::fmt; -use ids::BlockID; +use ids::BlockId; use util::H256; use util::trie::TrieError; @@ -28,7 +28,7 @@ use rlp::DecoderError; #[derive(Debug)] pub enum Error { /// Invalid starting block for snapshot. - InvalidStartingBlock(BlockID), + InvalidStartingBlock(BlockId), /// Block not found. BlockNotFound(H256), /// Incomplete chain. diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 3f63ac208ae..76695fbf221 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -27,7 +27,7 @@ use account_db::{AccountDB, AccountDBMut}; use blockchain::{BlockChain, BlockProvider}; use engines::Engine; use header::Header; -use ids::BlockID; +use ids::BlockId; use views::BlockView; use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint}; @@ -129,7 +129,7 @@ pub fn take_snapshot( p: &Progress ) -> Result<(), Error> { let start_header = try!(chain.block_header(&block_at) - .ok_or(Error::InvalidStartingBlock(BlockID::Hash(block_at)))); + .ok_or(Error::InvalidStartingBlock(BlockId::Hash(block_at)))); let state_root = start_header.state_root(); let number = start_header.number(); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index c0d34a6a9db..21725bc0657 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -30,7 +30,7 @@ use blockchain::BlockChain; use client::{BlockChainClient, Client}; use engines::Engine; use error::Error; -use ids::BlockID; +use ids::BlockId; use service::ClientIoMessage; use io::IoChannel; @@ -353,7 +353,7 @@ impl Service { let writer = try!(LooseWriter::new(temp_dir.clone())); let guard = Guard::new(temp_dir.clone()); - let res = client.take_snapshot(writer, BlockID::Number(num), &self.progress); + let res = client.take_snapshot(writer, BlockId::Number(num), &self.progress); self.taking_snapshot.store(false, Ordering::SeqCst); if let Err(e) = res { diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index e136985c6b5..efdb12323d8 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use client::{BlockChainClient, Client}; -use ids::BlockID; +use ids::BlockId; use snapshot::service::{Service, ServiceParams}; use snapshot::{self, ManifestData, SnapshotService}; use spec::Spec; @@ -96,8 +96,8 @@ fn restored_is_equivalent() { assert_eq!(service.status(), ::snapshot::RestorationStatus::Inactive); for x in 0..NUM_BLOCKS { - let block1 = client.block(BlockID::Number(x as u64)).unwrap(); - let block2 = client2.block(BlockID::Number(x as u64)).unwrap(); + let block1 = client.block(BlockId::Number(x as u64)).unwrap(); + let block2 = client2.block(BlockId::Number(x as u64)).unwrap(); assert_eq!(block1, block2); } diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 43439e437cf..ab4dde13433 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -18,7 +18,7 @@ use util::Mutex; use client::{BlockChainClient, Client, ChainNotify}; -use ids::BlockID; +use ids::BlockId; use service::ClientIoMessage; use views::HeaderView; @@ -43,7 +43,7 @@ impl Oracle for StandardOracle where F: Send + Sync + Fn() -> bool { fn to_number(&self, hash: H256) -> Option { - self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number()) + self.client.block_header(BlockId::Hash(hash)).map(|h| HeaderView::new(&h).number()) } fn is_major_importing(&self) -> bool { diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 99b251d6647..b96de22df1d 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use io::IoChannel; -use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockID}; +use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId}; use state::CleanupMode; use ethereum; use block::IsBlock; @@ -99,7 +99,7 @@ fn imports_good_block() { client.flush_queue(); client.import_verified_blocks(); - let block = client.block_header(BlockID::Number(1)).unwrap(); + let block = client.block_header(BlockId::Number(1)).unwrap(); assert!(!block.is_empty()); } @@ -117,7 +117,7 @@ fn query_none_block() { IoChannel::disconnected(), &db_config ).unwrap(); - let non_existant = client.block_header(BlockID::Number(188)); + let non_existant = client.block_header(BlockId::Number(188)); assert!(non_existant.is_none()); } @@ -125,7 +125,7 @@ fn query_none_block() { fn query_bad_block() { let client_result = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); let client = client_result.reference(); - let bad_block:Option = client.block_header(BlockID::Number(1)); + let bad_block:Option = client.block_header(BlockId::Number(1)); assert!(bad_block.is_none()); } @@ -146,8 +146,8 @@ fn returns_logs() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let logs = client.logs(Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![], limit: None, @@ -161,8 +161,8 @@ fn returns_logs_with_limit() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let logs = client.logs(Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![], limit: Some(2), @@ -176,7 +176,7 @@ fn returns_block_body() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let block = BlockView::new(&dummy_block); - let body = client.block_body(BlockID::Hash(block.header().hash())).unwrap(); + let body = client.block_body(BlockId::Hash(block.header().hash())).unwrap(); let body = Rlp::new(&body); assert_eq!(body.item_count(), 2); assert_eq!(body.at(0).as_raw()[..], block.rlp().at(1).as_raw()[..]); @@ -187,7 +187,7 @@ fn returns_block_body() { fn imports_block_sequence() { let client_result = generate_dummy_client(6); let client = client_result.reference(); - let block = client.block_header(BlockID::Number(5)).unwrap(); + let block = client.block_header(BlockId::Number(5)).unwrap(); assert!(!block.is_empty()); } diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs index b021e750d24..2da94b3d0bb 100644 --- a/ethcore/src/tests/rpc.rs +++ b/ethcore/src/tests/rpc.rs @@ -19,7 +19,7 @@ use nanoipc; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; -use client::{Client, BlockChainClient, ClientConfig, BlockID}; +use client::{Client, BlockChainClient, ClientConfig, BlockId}; use client::remote::RemoteClient; use tests::helpers::*; use devtools::*; @@ -71,7 +71,7 @@ fn can_query_block() { run_test_worker(scope, stop_guard.share(), socket_path); let remote_client = nanoipc::generic_client::>(socket_path).unwrap(); - let non_existant_block = remote_client.block_header(BlockID::Number(999)); + let non_existant_block = remote_client.block_header(BlockId::Number(999)); assert!(non_existant_block.is_none()); }) diff --git a/ethcore/src/types/filter.rs b/ethcore/src/types/filter.rs index e3487e5f60f..ecc997f7155 100644 --- a/ethcore/src/types/filter.rs +++ b/ethcore/src/types/filter.rs @@ -18,17 +18,17 @@ use util::{Address, H256, Hashable, H2048}; use util::bloom::Bloomable; -use client::BlockID; +use client::BlockId; use log_entry::LogEntry; /// Blockchain Filter. #[derive(Binary, Debug, PartialEq)] pub struct Filter { /// Blockchain will be searched from this block. - pub from_block: BlockID, + pub from_block: BlockId, /// Till this block. - pub to_block: BlockID, + pub to_block: BlockId, /// Search addresses. /// @@ -114,14 +114,14 @@ impl Filter { mod tests { use util::FixedHash; use filter::Filter; - use client::BlockID; + use client::BlockId; use log_entry::LogEntry; #[test] fn test_bloom_possibilities_none() { let none_filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![None, None, None, None], limit: None, @@ -136,8 +136,8 @@ mod tests { #[test] fn test_bloom_possibilities_single_address_and_topic() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), @@ -155,8 +155,8 @@ mod tests { #[test] fn test_bloom_possibilities_single_address_and_many_topics() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), @@ -174,8 +174,8 @@ mod tests { #[test] fn test_bloom_possibilites_multiple_addresses_and_topics() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec![ "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), @@ -204,8 +204,8 @@ mod tests { #[test] fn test_filter_matches() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), diff --git a/ethcore/src/types/ids.rs b/ethcore/src/types/ids.rs index 1fe81f39202..2c3c777b4da 100644 --- a/ethcore/src/types/ids.rs +++ b/ethcore/src/types/ids.rs @@ -21,7 +21,7 @@ use header::BlockNumber; /// Uniquely identifies block. #[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Binary)] -pub enum BlockID { +pub enum BlockId { /// Block's sha3. /// Querying by hash is always faster. Hash(H256), @@ -37,28 +37,28 @@ pub enum BlockID { /// Uniquely identifies transaction. #[derive(Debug, PartialEq, Clone, Hash, Eq, Binary)] -pub enum TransactionID { +pub enum TransactionId { /// Transaction's sha3. Hash(H256), /// Block id and transaction index within this block. /// Querying by block position is always faster. - Location(BlockID, usize) + Location(BlockId, usize) } /// Uniquely identifies Trace. #[derive(Binary)] pub struct TraceId { /// Transaction - pub transaction: TransactionID, + pub transaction: TransactionId, /// Trace address within transaction. pub address: Vec, } /// Uniquely identifies Uncle. #[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] -pub struct UncleID { +pub struct UncleId { /// Block id. - pub block: BlockID, + pub block: BlockId, /// Position in block. pub position: usize } diff --git a/ethcore/src/types/trace_filter.rs b/ethcore/src/types/trace_filter.rs index c17cc9e8543..af9d7c3eefd 100644 --- a/ethcore/src/types/trace_filter.rs +++ b/ethcore/src/types/trace_filter.rs @@ -18,13 +18,13 @@ use std::ops::Range; use util::{Address}; -use types::ids::BlockID; +use types::ids::BlockId; /// Easy to use trace filter. #[derive(Binary)] pub struct Filter { /// Range of filtering. - pub range: Range, + pub range: Range, /// From address. pub from_address: Vec
, /// To address. diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 56b2c1ccb85..c86123d1a48 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; use time; use ethkey::Address; use {json, SafeAccount, Error}; -use json::UUID; +use json::Uuid; use super::KeyDirectory; const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json"]; @@ -113,7 +113,7 @@ impl KeyDirectory for DiskDirectory { // build file path let filename = account.filename.as_ref().cloned().unwrap_or_else(|| { let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid."); - format!("UTC--{}Z--{}", timestamp, UUID::from(account.id)) + format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id)) }); // update account filename diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4991c471477..3747431fbfc 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -24,7 +24,7 @@ use dir::KeyDirectory; use account::SafeAccount; use {Error, SecretStore}; use json; -use json::UUID; +use json::Uuid; use parking_lot::RwLock; use presale::PresaleWallet; use import; @@ -154,7 +154,7 @@ impl SecretStore for EthStore { account.public(password) } - fn uuid(&self, address: &Address) -> Result { + fn uuid(&self, address: &Address) -> Result { let account = try!(self.get(address)); Ok(account.id.into()) } diff --git a/ethstore/src/json/error.rs b/ethstore/src/json/error.rs index 5e077cfad87..a3ea4d326fa 100644 --- a/ethstore/src/json/error.rs +++ b/ethstore/src/json/error.rs @@ -21,7 +21,7 @@ pub enum Error { UnsupportedCipher, InvalidCipherParams, UnsupportedKdf, - InvalidUUID, + InvalidUuid, UnsupportedVersion, InvalidCiphertext, InvalidH256, @@ -31,7 +31,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Error::InvalidUUID => write!(f, "Invalid UUID"), + Error::InvalidUuid => write!(f, "Invalid Uuid"), Error::UnsupportedVersion => write!(f, "Unsupported version"), Error::UnsupportedKdf => write!(f, "Unsupported kdf"), Error::InvalidCiphertext => write!(f, "Invalid ciphertext"), diff --git a/ethstore/src/json/id.rs b/ethstore/src/json/id.rs index ff282a9f87f..664716d95a2 100644 --- a/ethstore/src/json/id.rs +++ b/ethstore/src/json/id.rs @@ -23,15 +23,15 @@ use super::Error; /// Universaly unique identifier. #[derive(Debug, PartialEq)] -pub struct UUID([u8; 16]); +pub struct Uuid([u8; 16]); -impl From<[u8; 16]> for UUID { +impl From<[u8; 16]> for Uuid { fn from(uuid: [u8; 16]) -> Self { - UUID(uuid) + Uuid(uuid) } } -impl<'a> Into for &'a UUID { +impl<'a> Into for &'a Uuid { fn into(self) -> String { let d1 = &self.0[0..4]; let d2 = &self.0[4..6]; @@ -42,44 +42,44 @@ impl<'a> Into for &'a UUID { } } -impl Into for UUID { +impl Into for Uuid { fn into(self) -> String { Into::into(&self) } } -impl Into<[u8; 16]> for UUID { +impl Into<[u8; 16]> for Uuid { fn into(self) -> [u8; 16] { self.0 } } -impl fmt::Display for UUID { +impl fmt::Display for Uuid { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let s: String = (self as &UUID).into(); + let s: String = (self as &Uuid).into(); write!(f, "{}", s) } } fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> { - let from = try!(from.from_hex().map_err(|_| Error::InvalidUUID)); + let from = try!(from.from_hex().map_err(|_| Error::InvalidUuid)); if from.len() != into.len() { - return Err(Error::InvalidUUID); + return Err(Error::InvalidUuid); } into.copy_from_slice(&from); Ok(()) } -impl str::FromStr for UUID { +impl str::FromStr for Uuid { type Err = Error; fn from_str(s: &str) -> Result { let parts: Vec<&str> = s.split("-").collect(); if parts.len() != 5 { - return Err(Error::InvalidUUID); + return Err(Error::InvalidUuid); } let mut uuid = [0u8; 16]; @@ -90,17 +90,17 @@ impl str::FromStr for UUID { try!(copy_into(parts[3], &mut uuid[8..10])); try!(copy_into(parts[4], &mut uuid[10..16])); - Ok(UUID(uuid)) + Ok(Uuid(uuid)) } } -impl From<&'static str> for UUID { +impl From<&'static str> for Uuid { fn from(s: &'static str) -> Self { s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s)) } } -impl Serialize for UUID { +impl Serialize for Uuid { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { let s: String = self.into(); @@ -108,17 +108,17 @@ impl Serialize for UUID { } } -impl Deserialize for UUID { +impl Deserialize for Uuid { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { - deserializer.deserialize(UUIDVisitor) + deserializer.deserialize(UuidVisitor) } } -struct UUIDVisitor; +struct UuidVisitor; -impl Visitor for UUIDVisitor { - type Value = UUID; +impl Visitor for UuidVisitor { + type Value = Uuid; fn visit_str(&mut self, value: &str) -> Result where E: SerdeError { value.parse().map_err(SerdeError::custom) @@ -131,18 +131,18 @@ impl Visitor for UUIDVisitor { #[cfg(test)] mod tests { - use super::UUID; + use super::Uuid; #[test] fn uuid_from_str() { - let uuid: UUID = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); - assert_eq!(uuid, UUID::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); + let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); + assert_eq!(uuid, Uuid::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); } #[test] fn uuid_from_and_to_str() { let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6"; - let uuid: UUID = from.into(); + let uuid: Uuid = from.into(); let to: String = uuid.into(); assert_eq!(from, &to); } diff --git a/ethstore/src/json/key_file.rs b/ethstore/src/json/key_file.rs index 6e37c7c8970..5bc5a4e1c4e 100644 --- a/ethstore/src/json/key_file.rs +++ b/ethstore/src/json/key_file.rs @@ -18,11 +18,11 @@ use std::io::{Read, Write}; use serde::{Deserialize, Deserializer, Error}; use serde::de::{Visitor, MapVisitor}; use serde_json; -use super::{UUID, Version, Crypto, H160}; +use super::{Uuid, Version, Crypto, H160}; #[derive(Debug, PartialEq, Serialize)] pub struct KeyFile { - pub id: UUID, + pub id: Uuid, pub version: Version, pub crypto: Crypto, pub address: H160, @@ -153,7 +153,7 @@ impl KeyFile { mod tests { use std::str::FromStr; use serde_json; - use json::{KeyFile, UUID, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; + use json::{KeyFile, Uuid, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; #[test] fn basic_keyfile() { @@ -183,7 +183,7 @@ mod tests { }"#; let expected = KeyFile { - id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), + id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), version: Version::V3, address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), crypto: Crypto { diff --git a/ethstore/src/json/mod.rs.in b/ethstore/src/json/mod.rs.in index 133d9821eeb..8ad48b9941e 100644 --- a/ethstore/src/json/mod.rs.in +++ b/ethstore/src/json/mod.rs.in @@ -14,7 +14,7 @@ pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr}; pub use self::crypto::{Crypto, CipherText}; pub use self::error::Error; pub use self::hash::{H128, H160, H256}; -pub use self::id::UUID; +pub use self::id::Uuid; pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; pub use self::key_file::KeyFile; pub use self::presale::{PresaleWallet, Encseed}; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 06f38922b3d..bf0a4ec4465 100644 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -16,7 +16,7 @@ use ethkey::{Address, Message, Signature, Secret, Public}; use Error; -use json::UUID; +use json::Uuid; pub trait SecretStore: Send + Sync { fn insert_account(&self, secret: Secret, password: &str) -> Result; @@ -30,7 +30,7 @@ pub trait SecretStore: Send + Sync { fn public(&self, account: &Address, password: &str) -> Result; fn accounts(&self) -> Result, Error>; - fn uuid(&self, account: &Address) -> Result; + fn uuid(&self, account: &Address) -> Result; fn name(&self, account: &Address) -> Result; fn meta(&self, account: &Address) -> Result; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 0baeb135451..7696d1b3870 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -25,7 +25,7 @@ use io::{PanicHandler, ForwardPanic}; use util::{ToPretty, Uint}; use rlp::PayloadInfo; use ethcore::service::ClientService; -use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockID}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockId}; use ethcore::error::ImportError; use ethcore::miner::Miner; use cache::CacheConfig; @@ -98,8 +98,8 @@ pub struct ExportBlockchain { pub wal: bool, pub fat_db: Switch, pub tracing: Switch, - pub from_block: BlockID, - pub to_block: BlockID, + pub from_block: BlockId, + pub to_block: BlockId, pub check_seal: bool, } @@ -329,7 +329,7 @@ fn execute_export(cmd: ExportBlockchain) -> Result { let to = try!(client.block_number(cmd.to_block).ok_or("To block could not be found")); for i in from..(to + 1) { - let b = try!(client.block(BlockID::Number(i)).ok_or("Error exporting incomplete chain")); + let b = try!(client.block(BlockId::Number(i)).ok_or("Error exporting incomplete chain")); match format { DataFormat::Binary => { out.write(&b).expect("Couldn't write to stream."); } DataFormat::Hex => { out.write_fmt(format_args!("{}", b.pretty())).expect("Couldn't write to stream."); } diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 89603d311e4..807de7977fd 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -24,6 +24,14 @@ Operating Options: wakes regularly to resync. dark - Parity syncs only when the RPC is active. offline - Parity doesn't sync. (default: {flag_mode}). + --updates POLICY Set the client updating policy. POLICY specifies + which updates Parity will auto-install: + track - All updates in the current release track. + patch - All updates of the current minor version. + critical - Only consensus/security updates. + none - No updates. Not recommended. + --no-consensus Force the binary to run even if there are known + issues regarding consensus. Not recommended. --mode-timeout SECS Specify the number of seconds before inactivity timeout occurs when mode is dark or passive (default: {flag_mode_timeout}). diff --git a/parity/configuration.rs b/parity/configuration.rs index 61063aa18d8..e3f11685e33 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -684,7 +684,7 @@ mod tests { use super::*; use cli::Args; use ethcore_rpc::NetworkSettings; - use ethcore::client::{VMType, BlockID}; + use ethcore::client::{VMType, BlockId}; use ethcore::miner::{MinerOptions, PrioritizationStrategy}; use helpers::{replace_home, default_network_config}; use run::RunCmd; @@ -792,8 +792,8 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - from_block: BlockID::Number(1), - to_block: BlockID::Latest, + from_block: BlockId::Number(1), + to_block: BlockId::Latest, check_seal: true, }))); } @@ -814,8 +814,8 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - from_block: BlockID::Number(1), - to_block: BlockID::Latest, + from_block: BlockId::Number(1), + to_block: BlockId::Latest, check_seal: true, }))); } diff --git a/parity/dapps.rs b/parity/dapps.rs index 16ae4dd98f5..ec6fd884634 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -106,7 +106,7 @@ mod server { use util::{Bytes, Address, U256}; use ethcore::transaction::{Transaction, Action}; - use ethcore::client::{Client, BlockChainClient, BlockID}; + use ethcore::client::{Client, BlockChainClient, BlockId}; use rpc_apis; use ethcore_rpc::is_major_importing; @@ -182,7 +182,7 @@ mod server { data: data, }.fake_sign(from); - self.client.call(&transaction, BlockID::Latest, Default::default()) + self.client.call(&transaction, BlockId::Latest, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output diff --git a/parity/helpers.rs b/parity/helpers.rs index ab0dafdddff..fe5303ec87a 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -20,7 +20,7 @@ use std::time::Duration; use std::fs::File; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; -use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; +use ethcore::client::{Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; use ethcore::miner::{PendingSet, GasLimit, PrioritizationStrategy}; use cache::CacheConfig; use dir::DatabaseDirectories; @@ -62,13 +62,13 @@ pub fn to_mode(s: &str, timeout: u64, alarm: u64) -> Result { } } -pub fn to_block_id(s: &str) -> Result { +pub fn to_block_id(s: &str) -> Result { if s == "latest" { - Ok(BlockID::Latest) + Ok(BlockId::Latest) } else if let Ok(num) = s.parse() { - Ok(BlockID::Number(num)) + Ok(BlockId::Number(num)) } else if let Ok(hash) = s.parse() { - Ok(BlockID::Hash(hash)) + Ok(BlockId::Hash(hash)) } else { Err("Invalid block.".into()) } @@ -327,7 +327,7 @@ mod tests { use std::io::Write; use devtools::RandomTempPath; use util::{U256}; - use ethcore::client::{Mode, BlockID}; + use ethcore::client::{Mode, BlockId}; use ethcore::miner::PendingSet; use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes, password_from_file}; @@ -361,13 +361,13 @@ mod tests { #[test] fn test_to_block_id() { - assert_eq!(to_block_id("latest").unwrap(), BlockID::Latest); - assert_eq!(to_block_id("0").unwrap(), BlockID::Number(0)); - assert_eq!(to_block_id("2").unwrap(), BlockID::Number(2)); - assert_eq!(to_block_id("15").unwrap(), BlockID::Number(15)); + assert_eq!(to_block_id("latest").unwrap(), BlockId::Latest); + assert_eq!(to_block_id("0").unwrap(), BlockId::Number(0)); + assert_eq!(to_block_id("2").unwrap(), BlockId::Number(2)); + assert_eq!(to_block_id("15").unwrap(), BlockId::Number(15)); assert_eq!( to_block_id("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e").unwrap(), - BlockID::Hash("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e".parse().unwrap()) + BlockId::Hash("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e".parse().unwrap()) ); } diff --git a/parity/informant.rs b/parity/informant.rs index d3e3c8a2015..1caeb1b7c77 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -184,12 +184,12 @@ impl ChainNotify for Informant { let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing; let txs_imported = imported.iter() .take(imported.len() - if ripe {1} else {0}) - .filter_map(|h| self.client.block(BlockID::Hash(*h))) + .filter_map(|h| self.client.block(BlockId::Hash(*h))) .map(|b| BlockView::new(&b).transactions_count()) .sum(); if ripe { - if let Some(block) = imported.last().and_then(|h| self.client.block(BlockID::Hash(*h))) { + if let Some(block) = imported.last().and_then(|h| self.client.block(BlockId::Hash(*h))) { let view = BlockView::new(&block); let header = view.header(); let tx_count = view.transactions_count(); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index d8323084d33..804047596a0 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -26,7 +26,7 @@ use ethcore::snapshot::service::Service as SnapshotService; use ethcore::service::ClientService; use ethcore::client::{Mode, DatabaseCompactionProfile, VMType}; use ethcore::miner::Miner; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use cache::CacheConfig; use params::{SpecType, Pruning, Switch, tracing_switch_to_bool, fatdb_switch_to_bool}; @@ -60,7 +60,7 @@ pub struct SnapshotCommand { pub file_path: Option, pub wal: bool, pub kind: Kind, - pub block_at: BlockID, + pub block_at: BlockId, } // helper for reading chunks from arbitrary reader and feeding them into the diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 5f1449e079c..36cdc3f993b 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -33,7 +33,7 @@ use util::sha3::*; use util::{FromHex, Mutex}; use rlp::{self, UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID}; +use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId}; use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; use ethcore::block::IsBlock; use ethcore::views::*; @@ -120,7 +120,7 @@ impl EthClient where } } - fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { + fn block(&self, id: BlockId, include_txs: bool) -> Result, Error> { let client = take_weak!(self.client); match (client.block(id.clone()), client.block_total_difficulty(id)) { (Some(bytes), Some(total_difficulty)) => { @@ -160,20 +160,20 @@ impl EthClient where } } - fn transaction(&self, id: TransactionID) -> Result, Error> { + fn transaction(&self, id: TransactionId) -> Result, Error> { match take_weak!(self.client).transaction(id) { Some(t) => Ok(Some(Transaction::from(t))), None => Ok(None), } } - fn uncle(&self, id: UncleID) -> Result, Error> { + fn uncle(&self, id: UncleId) -> Result, Error> { let client = take_weak!(self.client); let uncle: BlockHeader = match client.uncle(id) { Some(rlp) => rlp::decode(&rlp), None => { return Ok(None); } }; - let parent_difficulty = match client.block_total_difficulty(BlockID::Hash(uncle.parent_hash().clone())) { + let parent_difficulty = match client.block_total_difficulty(BlockId::Hash(uncle.parent_hash().clone())) { Some(difficulty) => difficulty, None => { return Ok(None); } }; @@ -394,7 +394,7 @@ impl Eth for EthClient where fn block_transaction_count_by_hash(&self, hash: RpcH256) -> Result, Error> { try!(self.active()); Ok( - take_weak!(self.client).block(BlockID::Hash(hash.into())) + take_weak!(self.client).block(BlockId::Hash(hash.into())) .map(|bytes| BlockView::new(&bytes).transactions_count().into()) ) } @@ -417,7 +417,7 @@ impl Eth for EthClient where try!(self.active()); Ok( - take_weak!(self.client).block(BlockID::Hash(hash.into())) + take_weak!(self.client).block(BlockId::Hash(hash.into())) .map(|bytes| BlockView::new(&bytes).uncles_count().into()) ) } @@ -450,7 +450,7 @@ impl Eth for EthClient where fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { try!(self.active()); - self.block(BlockID::Hash(hash.into()), include_txs) + self.block(BlockId::Hash(hash.into()), include_txs) } fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { @@ -464,19 +464,19 @@ impl Eth for EthClient where let hash: H256 = hash.into(); let miner = take_weak!(self.miner); let client = take_weak!(self.client); - Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) + Ok(try!(self.transaction(TransactionId::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) } fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); - self.transaction(TransactionID::Location(BlockID::Hash(hash.into()), index.value())) + self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value())) } fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); - self.transaction(TransactionID::Location(num.into(), index.value())) + self.transaction(TransactionId::Location(num.into(), index.value())) } fn transaction_receipt(&self, hash: RpcH256) -> Result, Error> { @@ -489,7 +489,7 @@ impl Eth for EthClient where (Some(receipt), true) => Ok(Some(receipt.into())), _ => { let client = take_weak!(self.client); - let receipt = client.transaction_receipt(TransactionID::Hash(hash)); + let receipt = client.transaction_receipt(TransactionId::Hash(hash)); Ok(receipt.map(Into::into)) } } @@ -498,13 +498,13 @@ impl Eth for EthClient where fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); - self.uncle(UncleID { block: BlockID::Hash(hash.into()), position: index.value() }) + self.uncle(UncleId { block: BlockId::Hash(hash.into()), position: index.value() }) } fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); - self.uncle(UncleID { block: num.into(), position: index.value() }) + self.uncle(UncleId { block: num.into(), position: index.value() }) } fn compilers(&self) -> Result, Error> { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index dd1c937ac43..695ff525159 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -21,7 +21,7 @@ use std::collections::HashSet; use jsonrpc_core::*; use ethcore::miner::MinerService; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::client::{BlockChainClient, BlockID}; +use ethcore::client::{BlockChainClient, BlockId}; use util::Mutex; use v1::traits::EthFilter; use v1::types::{BlockNumber, Index, Filter, FilterChanges, Log, H256 as RpcH256, U256 as RpcU256}; @@ -98,7 +98,7 @@ impl EthFilter for EthFilterClient // + 1, cause we want to return hashes including current block hash. let current_number = client.chain_info().best_block_number + 1; let hashes = (*block_number..current_number).into_iter() - .map(BlockID::Number) + .map(BlockId::Number) .filter_map(|id| client.block_hash(id)) .map(Into::into) .collect::>(); @@ -140,10 +140,10 @@ impl EthFilter for EthFilterClient // build appropriate filter let mut filter: EthcoreFilter = filter.clone().into(); - filter.from_block = BlockID::Number(*block_number); - filter.to_block = BlockID::Latest; + filter.from_block = BlockId::Number(*block_number); + filter.to_block = BlockId::Latest; - // retrieve logs in range from_block..min(BlockID::Latest..to_block) + // retrieve logs in range from_block..min(BlockId::Latest..to_block) let mut logs = client.logs(filter.clone()) .into_iter() .map(From::from) diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 1fdcbdef867..4a274627a43 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -28,7 +28,7 @@ use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; use ethcore::miner::MinerService; use ethcore::client::{MiningBlockChainClient}; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::mode::Mode; use ethcore::account_provider::AccountProvider; @@ -238,7 +238,7 @@ impl Parity for ParityClient where try!(self.active()); Ok(take_weak!(self.client) - .list_accounts(BlockID::Latest) + .list_accounts(BlockId::Latest) .map(|a| a.into_iter().map(Into::into).collect())) } diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index e85d961a117..0b287ce2994 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -19,7 +19,7 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use rlp::{UntrustedRlp, View}; -use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; +use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; @@ -100,7 +100,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: from_params::<(H256,)>(params) .and_then(|(transaction_hash,)| { let client = take_weak!(self.client); - let traces = client.transaction_traces(TransactionID::Hash(transaction_hash.into())); + let traces = client.transaction_traces(TransactionId::Hash(transaction_hash.into())); let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); Ok(to_value(&traces)) }) @@ -112,7 +112,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(transaction_hash, address)| { let client = take_weak!(self.client); let id = TraceId { - transaction: TransactionID::Hash(transaction_hash.into()), + transaction: TransactionId::Hash(transaction_hash.into()), address: address.into_iter().map(|i| i.value()).collect() }; let trace = client.trace(id); @@ -153,7 +153,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: try!(self.active()); from_params::<(H256, _)>(params) .and_then(|(transaction_hash, flags)| { - match take_weak!(self.client).replay(TransactionID::Hash(transaction_hash.into()), to_call_analytics(flags)) { + match take_weak!(self.client).replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags)) { Ok(e) => Ok(to_value(&TraceResults::from(e))), _ => Ok(Value::Null), } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 2f5131f3262..9a298dde49d 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use std::time::Duration; use ethcore::client::{BlockChainClient, Client, ClientConfig}; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; @@ -424,7 +424,7 @@ fn verify_transaction_counts(name: String, chain: BlockChain) { assert_eq!(tester.handler.handle_request_sync(&req), Some(res)); // uncles can share block numbers, so skip them. - if tester.client.block_hash(BlockID::Number(number)) == Some(hash) { + if tester.client.block_hash(BlockId::Number(number)) == Some(hash) { let (req, res) = by_number(number, count, &mut id); assert_eq!(tester.handler.handle_request_sync(&req), Some(res)); } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 861bb523464..f0b6eeac1e9 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -24,7 +24,7 @@ use rlp; use util::{Uint, U256, Address, H256, FixedHash, Mutex}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID}; +use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId}; use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; @@ -950,7 +950,7 @@ fn rpc_eth_transaction_receipt() { let hash = H256::from_str("b903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").unwrap(); let tester = EthTester::default(); - tester.client.set_transaction_receipt(TransactionID::Hash(hash), receipt); + tester.client.set_transaction_receipt(TransactionId::Hash(hash), receipt); let request = r#"{ "jsonrpc": "2.0", diff --git a/rpc/src/v1/types/block_number.rs b/rpc/src/v1/types/block_number.rs index 01625f8edcb..0a2b2f30572 100644 --- a/rpc/src/v1/types/block_number.rs +++ b/rpc/src/v1/types/block_number.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Deserializer, Error}; use serde::de::Visitor; -use ethcore::client::BlockID; +use ethcore::client::BlockId; /// Represents rpc api block number param. #[derive(Debug, PartialEq, Clone)] @@ -64,20 +64,20 @@ impl Visitor for BlockNumberVisitor { } } -impl Into for BlockNumber { - fn into(self) -> BlockID { +impl Into for BlockNumber { + fn into(self) -> BlockId { match self { - BlockNumber::Num(n) => BlockID::Number(n), - BlockNumber::Earliest => BlockID::Earliest, - BlockNumber::Latest => BlockID::Latest, - BlockNumber::Pending => BlockID::Pending, + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => BlockId::Pending, } } } #[cfg(test)] mod tests { - use ethcore::client::BlockID; + use ethcore::client::BlockId; use super::*; use serde_json; @@ -90,10 +90,10 @@ mod tests { #[test] fn block_number_into() { - assert_eq!(BlockID::Number(100), BlockNumber::Num(100).into()); - assert_eq!(BlockID::Earliest, BlockNumber::Earliest.into()); - assert_eq!(BlockID::Latest, BlockNumber::Latest.into()); - assert_eq!(BlockID::Pending, BlockNumber::Pending.into()); + assert_eq!(BlockId::Number(100), BlockNumber::Num(100).into()); + assert_eq!(BlockId::Earliest, BlockNumber::Earliest.into()); + assert_eq!(BlockId::Latest, BlockNumber::Latest.into()); + assert_eq!(BlockId::Pending, BlockNumber::Pending.into()); } } diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs index fc163c54be8..bc07254e2aa 100644 --- a/rpc/src/v1/types/filter.rs +++ b/rpc/src/v1/types/filter.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, Error}; use serde_json::value; use jsonrpc_core::Value; use ethcore::filter::Filter as EthFilter; -use ethcore::client::BlockID; +use ethcore::client::BlockId; use v1::types::{BlockNumber, H160, H256, Log}; /// Variadic value @@ -73,8 +73,8 @@ pub struct Filter { impl Into for Filter { fn into(self) -> EthFilter { EthFilter { - from_block: self.from_block.map_or_else(|| BlockID::Latest, Into::into), - to_block: self.to_block.map_or_else(|| BlockID::Latest, Into::into), + from_block: self.from_block.map_or_else(|| BlockId::Latest, Into::into), + to_block: self.to_block.map_or_else(|| BlockId::Latest, Into::into), address: self.address.and_then(|address| match address { VariadicValue::Null => None, VariadicValue::Single(a) => Some(vec![a.into()]), @@ -128,7 +128,7 @@ mod tests { use super::*; use v1::types::BlockNumber; use ethcore::filter::Filter as EthFilter; - use ethcore::client::BlockID; + use ethcore::client::BlockId; #[test] fn topic_deserialization() { @@ -173,8 +173,8 @@ mod tests { let eth_filter: EthFilter = filter.into(); assert_eq!(eth_filter, EthFilter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec![]), topics: vec![ None, diff --git a/rpc/src/v1/types/trace_filter.rs b/rpc/src/v1/types/trace_filter.rs index 21e50e17580..83ec6b06e68 100644 --- a/rpc/src/v1/types/trace_filter.rs +++ b/rpc/src/v1/types/trace_filter.rs @@ -16,7 +16,7 @@ //! Trace filter deserialization. -use ethcore::client::BlockID; +use ethcore::client::BlockId; use ethcore::client; use v1::types::{BlockNumber, H160}; @@ -39,8 +39,8 @@ pub struct TraceFilter { impl Into for TraceFilter { fn into(self) -> client::TraceFilter { - let start = self.from_block.map_or(BlockID::Latest, Into::into); - let end = self.to_block.map_or(BlockID::Latest, Into::into); + let start = self.from_block.map_or(BlockId::Latest, Into::into); + let end = self.to_block.map_or(BlockId::Latest, Into::into); client::TraceFilter { range: start..end, from_address: self.from_address.map_or_else(Vec::new, |x| x.into_iter().map(Into::into).collect()), diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index ff541114023..0a5d8550be7 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -22,7 +22,7 @@ use util::*; use rlp::*; use ethcore::views::{BlockView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; -use ethcore::client::{BlockStatus, BlockID, BlockImportError}; +use ethcore::client::{BlockStatus, BlockId, BlockImportError}; use ethcore::block::Block; use ethcore::error::{ImportError, BlockError}; use sync_io::SyncIo; @@ -225,7 +225,7 @@ impl BlockDownloader { trace!(target: "sync", "Error decoding block header RLP: {:?}", e); BlockDownloaderImportError::Invalid })); - match io.chain().block_status(BlockID::Hash(hash.clone())) { + match io.chain().block_status(BlockId::Hash(hash.clone())) { BlockStatus::InChain | BlockStatus::Queued => { match self.state { State::Blocks => trace!(target: "sync", "Header already in chain {} ({})", number, hash), @@ -347,7 +347,7 @@ impl BlockDownloader { debug!(target: "sync", "Could not revert to previous ancient block, last: {} ({})", self.last_imported_block, self.last_imported_hash); self.reset(); } else { - match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { + match io.chain().block_hash(BlockId::Number(self.last_imported_block - 1)) { Some(h) => { self.last_imported_block -= 1; self.last_imported_hash = h; diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index bf0c4b2447e..bcb9973dca8 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -475,7 +475,7 @@ impl BlockCollection { #[cfg(test)] mod test { use super::BlockCollection; - use ethcore::client::{TestBlockChainClient, EachBlockWith, BlockID, BlockChainClient}; + use ethcore::client::{TestBlockChainClient, EachBlockWith, BlockId, BlockChainClient}; use ethcore::views::HeaderView; use ethcore::header::BlockNumber; use util::*; @@ -497,7 +497,7 @@ mod test { assert!(is_empty(&bc)); let client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Nothing); - let hashes = (0 .. 100).map(|i| (&client as &BlockChainClient).block_hash(BlockID::Number(i)).unwrap()).collect(); + let hashes = (0 .. 100).map(|i| (&client as &BlockChainClient).block_hash(BlockId::Number(i)).unwrap()).collect(); bc.reset_to(hashes); assert!(!is_empty(&bc)); bc.clear(); @@ -511,7 +511,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); @@ -564,7 +564,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); @@ -586,7 +586,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d98b142cb97..fd3e82767dd 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -94,7 +94,7 @@ use rlp::*; use network::*; use ethcore::views::{HeaderView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; -use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError}; +use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo, BlockImportError}; use ethcore::error::*; use ethcore::snapshot::{ManifestData, RestorationStatus}; use sync_io::SyncIo; @@ -929,7 +929,7 @@ impl ChainSync { io.disable_peer(peer_id); continue; } - match io.chain().block_status(BlockID::Hash(hash.clone())) { + match io.chain().block_status(BlockId::Hash(hash.clone())) { BlockStatus::InChain => { trace!(target: "sync", "New block hash already in chain {:?}", hash); }, @@ -1150,7 +1150,7 @@ impl ChainSync { return; } - let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown; + let have_latest = io.chain().block_status(BlockId::Hash(peer_latest)) != BlockStatus::Unknown; if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { // check if got new blocks to download trace!(target: "sync", "Syncing with {}, force={}, td={:?}, our td={}, state={:?}", peer_id, force, peer_difficulty, syncing_difficulty, self.state); @@ -1442,11 +1442,11 @@ impl ChainSync { // id is a hash let hash: H256 = try!(r.val_at(0)); trace!(target: "sync", "{} -> GetBlockHeaders (hash: {}, max: {}, skip: {}, reverse:{})", peer_id, hash, max_headers, skip, reverse); - match io.chain().block_header(BlockID::Hash(hash)) { + match io.chain().block_header(BlockId::Hash(hash)) { Some(hdr) => { let number = From::from(HeaderView::new(&hdr).number()); debug_assert_eq!(HeaderView::new(&hdr).sha3(), hash); - if max_headers == 1 || io.chain().block_hash(BlockID::Number(number)) != Some(hash) { + if max_headers == 1 || io.chain().block_hash(BlockId::Number(number)) != Some(hash) { // Non canonical header or single header requested // TODO: handle single-step reverse hashchains of non-canon hashes trace!(target:"sync", "Returning single header: {:?}", hash); @@ -1479,7 +1479,7 @@ impl ChainSync { trace!(target: "sync", "{}: Returning cached fork header", peer_id); data.extend_from_slice(hdr); count += 1; - } else if let Some(mut hdr) = io.chain().block_header(BlockID::Number(number)) { + } else if let Some(mut hdr) = io.chain().block_header(BlockId::Number(number)) { data.append(&mut hdr); count += 1; } else { @@ -1513,7 +1513,7 @@ impl ChainSync { let mut added = 0usize; let mut data = Bytes::new(); for i in 0..count { - if let Some(mut hdr) = io.chain().block_body(BlockID::Hash(try!(r.val_at::(i)))) { + if let Some(mut hdr) = io.chain().block_body(BlockId::Hash(try!(r.val_at::(i)))) { data.append(&mut hdr); added += 1; } @@ -1770,7 +1770,7 @@ impl ChainSync { let mut rlp_stream = RlpStream::new_list(blocks.len()); for block_hash in blocks { let mut hash_rlp = RlpStream::new_list(2); - let number = HeaderView::new(&chain.block_header(BlockID::Hash(block_hash.clone())) + let number = HeaderView::new(&chain.block_header(BlockId::Hash(block_hash.clone())) .expect("chain.tree_route and chain.find_uncles only return hahses of blocks that are in the blockchain. qed.")).number(); hash_rlp.append(&block_hash); hash_rlp.append(&number); @@ -1787,7 +1787,7 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); + rlp_stream.append_raw(&chain.block(BlockId::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); rlp_stream.append(&chain.chain_info().total_difficulty); rlp_stream.out() } @@ -1795,8 +1795,8 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_new_block_rlp(chain: &BlockChainClient, hash: &H256) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); - rlp_stream.append(&chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.")); + rlp_stream.append_raw(&chain.block(BlockId::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); + rlp_stream.append(&chain.block_total_difficulty(BlockId::Hash(hash.clone())).expect("Block has just been sealed; qed.")); rlp_stream.out() } @@ -1804,7 +1804,7 @@ impl ChainSync { fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { let latest_hash = chain_info.best_block_hash; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| - match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { + match io.chain().block_status(BlockId::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { if peer_info.latest_hash != latest_hash { Some(id) @@ -1855,7 +1855,7 @@ impl ChainSync { fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewHashes to {:?}", peers); let mut sent = 0; - let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())) + let last_parent = HeaderView::new(&io.chain().block_header(BlockId::Hash(chain_info.best_block_hash.clone())) .expect("Best block always exists")).parent_hash(); for peer_id in peers { sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { @@ -2113,7 +2113,7 @@ mod tests { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. 100).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. 100).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); @@ -2286,7 +2286,7 @@ mod tests { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); let mut queue = VecDeque::new(); - let hash = client.block_hash(BlockID::Number(99)).unwrap(); + let hash = client.block_hash(BlockId::Number(99)).unwrap(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); @@ -2544,7 +2544,7 @@ mod tests { // Add some balance to clients and reset nonces for h in &[good_blocks[0], retracted_blocks[0]] { - let block = client.block(BlockID::Hash(*h)).unwrap(); + let block = client.block(BlockId::Hash(*h)).unwrap(); let view = BlockView::new(&block); client.set_balance(view.transactions()[0].sender().unwrap(), U256::from(1_000_000_000)); client.set_nonce(view.transactions()[0].sender().unwrap(), U256::from(0)); @@ -2563,7 +2563,7 @@ mod tests { } // We need to update nonce status (because we say that the block has been imported) for h in &[good_blocks[0]] { - let block = client.block(BlockID::Hash(*h)).unwrap(); + let block = client.block(BlockId::Hash(*h)).unwrap(); let view = BlockView::new(&block); client.set_nonce(view.transactions()[0].sender().unwrap(), U256::from(1)); } diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 5fe34428ebd..30ae131aa89 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use util::*; -use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockID, EachBlockWith}; +use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockId, EachBlockWith}; use chain::{SyncState}; use super::helpers::*; use SyncConfig; @@ -27,7 +27,7 @@ fn two_peers() { net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + assert!(net.peer(0).chain.block(BlockId::Number(1000)).is_some()); assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); } @@ -37,7 +37,7 @@ fn long_chain() { let mut net = TestNet::new(2); net.peer_mut(1).chain.add_blocks(50000, EachBlockWith::Nothing); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(50000)).is_some()); + assert!(net.peer(0).chain.block(BlockId::Number(50000)).is_some()); assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); } @@ -71,7 +71,7 @@ fn empty_blocks() { net.peer_mut(2).chain.add_blocks(5, with); } net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + assert!(net.peer(0).chain.block(BlockId::Number(1000)).is_some()); assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); } @@ -123,13 +123,13 @@ fn net_hard_fork() { let ref_client = TestBlockChainClient::new(); ref_client.add_blocks(50, EachBlockWith::Uncle); { - let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap()))); net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 100); } { - let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap()))); net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); diff --git a/util/Cargo.toml b/util/Cargo.toml index cc342eecaae..2f8f5c6fb40 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -37,6 +37,7 @@ tiny-keccak= "1.0" ethcore-bloom-journal = { path = "bloom" } regex = "0.1" lru-cache = "0.1.0" +semver = "0.5" [features] default = [] diff --git a/util/src/lib.rs b/util/src/lib.rs index 2b4ac0fed0e..e62a47e4155 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -106,6 +106,7 @@ extern crate tiny_keccak; extern crate rlp; extern crate regex; extern crate lru_cache; +extern crate semver; #[macro_use] extern crate heapsize; diff --git a/util/src/misc.rs b/util/src/misc.rs index d9eab1af08a..62231da9a86 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -17,14 +17,15 @@ //! Diff misc. use common::*; +use semver::{Identifier, Version}; use rlp::{Stream, RlpStream}; use target_info::Target; include!(concat!(env!("OUT_DIR"), "/version.rs")); include!(concat!(env!("OUT_DIR"), "/rustc_version.rs")); -#[derive(PartialEq, Eq, Clone, Copy, Debug)] /// Boolean type for clean/dirty status. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Filth { /// Data has not been changed. Clean, @@ -32,9 +33,112 @@ pub enum Filth { Dirty, } -/// Get the (SHA1?) 160-bit hash of this build's code base. -pub fn code_hash() -> H160 { - sha().into() +/// A release's track. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ReleaseTrack { + /// Stable track. + Stable, + /// Beta track. + Beta, + /// Nightly track. + Nightly, + /// No known track. + Unknown, +} + +impl fmt::Display for ReleaseTrack { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", match *self { + ReleaseTrack::Stable => "stable", + ReleaseTrack::Beta => "beta", + ReleaseTrack::Nightly => "nightly", + ReleaseTrack::Unknown => "unknown", + }) + } +} + +impl<'a> From<&'a str> for ReleaseTrack { + fn from(s: &'a str) -> Self { + match s { + "stable" => ReleaseTrack::Stable, + "beta" => ReleaseTrack::Beta, + "nightly" => ReleaseTrack::Nightly, + _ => ReleaseTrack::Unknown, + } + } +} + +impl From for ReleaseTrack { + fn from(i: u8) -> Self { + match i { + 1 => ReleaseTrack::Stable, + 2 => ReleaseTrack::Beta, + 3 => ReleaseTrack::Nightly, + _ => ReleaseTrack::Unknown, + } + } +} + +impl Into for ReleaseTrack { + fn into(self) -> u8 { + match self { + ReleaseTrack::Stable => 1, + ReleaseTrack::Beta => 2, + ReleaseTrack::Nightly => 3, + ReleaseTrack::Unknown => 0, + } + } +} + +/// Version information of a particular release. +#[derive(Debug, PartialEq)] +pub struct VersionInfo { + /// The track on which it was released. + pub track: ReleaseTrack, + /// The version. + pub version: Version, + /// The (SHA1?) 160-bit hash of this build's code base. + pub hash: H160, +} + +impl fmt::Display for VersionInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}-{}", self.version, self.hash) + } +} + +impl VersionInfo { + /// Get information for this (currently running) binary. + pub fn this() -> Self { + VersionInfo { + track: env!["CARGO_PKG_VERSION_PRE"].into(), + version: Version::parse(env!["CARGO_PKG_VERSION"]).expect("Environment variables are known to be valid; qed"), + hash: sha().into(), + } + } + + /// Compose the information from the provided raw fields. + pub fn from_raw(semver: u32, track: u8, hash: H160) -> Self { + let t = track.into(); + VersionInfo { + version: Version { + major: (semver >> 16) as u64, + minor: ((semver >> 8) & 0xff) as u64, + patch: (semver & 0xff) as u64, + build: vec![], + pre: vec![Identifier::AlphaNumeric(format!("{}", t))] + }, + track: t, + hash: hash, + } + } +} + +/// Get the platform identifier. +pub fn platform() -> String { + let env = Target::env(); + let env_dash = if env.is_empty() { "" } else { "-" }; + format!("{}-{}{}{}", Target::arch(), Target::os(), env_dash, env) } /// Get the standard version string for this software. @@ -43,18 +147,16 @@ pub fn version() -> String { let sha3_dash = if sha3.is_empty() { "" } else { "-" }; let commit_date = commit_date().replace("-", ""); let date_dash = if commit_date.is_empty() { "" } else { "-" }; - let env = Target::env(); - let env_dash = if env.is_empty() { "" } else { "-" }; - format!("Parity/v{}-unstable{}{}{}{}/{}-{}{}{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, Target::arch(), Target::os(), env_dash, env, rustc_version()) + format!("Parity/v{}-unstable{}{}{}{}/{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, platform(), rustc_version()) } /// Get the standard version data for this software. pub fn version_data() -> Bytes { let mut s = RlpStream::new_list(4); let v = - (u32::from_str(env!("CARGO_PKG_VERSION_MAJOR")).unwrap() << 16) + - (u32::from_str(env!("CARGO_PKG_VERSION_MINOR")).unwrap() << 8) + - u32::from_str(env!("CARGO_PKG_VERSION_PATCH")).unwrap(); + (u32::from_str(env!("CARGO_PKG_VERSION_MAJOR")).expect("Environment variables are known to be valid; qed") << 16) + + (u32::from_str(env!("CARGO_PKG_VERSION_MINOR")).expect("Environment variables are known to be valid; qed") << 8) + + u32::from_str(env!("CARGO_PKG_VERSION_PATCH")).expect("Environment variables are known to be valid; qed"); s.append(&v); s.append(&"Parity"); s.append(&rustc_version()); From 340d37793058c47d5095d5e1ff871ed1fb7c4163 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 16:05:27 +0000 Subject: [PATCH 080/382] Revert "dont keep account provider in miner" This reverts commit 11ccacd6d0a9989d07b941a77f7a4e9f5a6f7bb3. --- ethcore/res/tendermint.json | 18 ++--- ethcore/src/engines/authority_round.rs | 6 +- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/mod.rs | 6 +- ethcore/src/engines/tendermint/mod.rs | 103 +++++++++++++++++-------- parity/run.rs | 11 ++- 6 files changed, 92 insertions(+), 54 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 2f40d707b27..ae265aa0bd3 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -12,16 +12,16 @@ } }, "params": { - "accountStartNonce": "0x0100000", + "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "networkID" : "0x69" + "networkID" : "0x2323" }, "genesis": { "seal": { "generic": { - "fields": 1, - "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + "fields": 3, + "rlp": "0x40010" } }, "difficulty": "0x20000", @@ -32,10 +32,10 @@ "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" } + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ab2b41aec16..e90bc73f2e9 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -68,9 +68,9 @@ pub struct AuthorityRound { builtins: BTreeMap, transition_service: IoService, message_channel: Mutex>>, - account_provider: Mutex>>, step: AtomicUsize, proposed: AtomicBool, + account_provider: Mutex>>, } fn header_step(header: &Header) -> Result { @@ -102,9 +102,9 @@ impl AuthorityRound { builtins: builtins, transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), - account_provider: Mutex::new(None), step: AtomicUsize::new(initial_step), - proposed: AtomicBool::new(false) + proposed: AtomicBool::new(false), + account_provider: Mutex::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.transition_service.register_handler(Arc::new(handler))); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index c43495967ae..7e0402dabaa 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,7 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, - account_provider: Mutex>> + account_provider: Mutex>>, } impl BasicAuthority { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 5aae9ee787a..ab50ee9c921 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -174,11 +174,11 @@ pub trait Engine : Sync + Send { /// Add a channel for communication with Client which can be used for sealing. fn register_message_channel(&self, _message_channel: IoChannel) {} + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} + /// Check if new block should be chosen as the one in chain. fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) } - - /// Add an account provider useful for Engines that sign stuff. - fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 48e3b359992..b110a602d27 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -202,26 +202,22 @@ impl Tendermint { } } - fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { - let ref p = self.our_params; - p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed") - } - - fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { - self.nonce_proposer(proposer_nonce) == address - } - fn is_authority(&self, address: &Address) -> bool { self.our_params.authorities.contains(address) } - fn threshold(&self) -> usize { - self.our_params.authority_n * 2/3 + fn is_above_threshold(&self, n: usize) -> bool { + n > self.our_params.authority_n * 2/3 } /// Round proposer switching. fn is_proposer(&self, address: &Address) -> bool { - self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + let ref p = self.our_params; + let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); + let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); + println!("{:?}", &proposer); + println!("{:?}", &address); + proposer == address } fn is_height(&self, message: &ConsensusMessage) -> bool { @@ -251,16 +247,22 @@ impl Tendermint { fn has_enough_any_votes(&self) -> bool { - self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() + let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()); + self.is_above_threshold(step_votes) } fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool { - message.round > self.round.load(AtomicOrdering::SeqCst) - && self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + if message.round > self.round.load(AtomicOrdering::SeqCst) { + let step_votes = self.votes.count_step_votes(message.height, message.round, message.step); + self.is_above_threshold(step_votes) + } else { + false + } } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.aligned_votes(&message).len() > self.threshold() + let aligned_votes = self.votes.aligned_votes(&message).len(); + self.is_above_threshold(aligned_votes) } } @@ -476,6 +478,13 @@ mod tests { use super::params::TendermintParams; use super::message::*; + fn setup() -> (Spec, Arc) { + let tap = Arc::new(AccountProvider::transient_provider()); + let spec = Spec::new_test_tendermint(); + spec.engine.register_account_provider(tap.clone()); + (spec, tap) + } + fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { let mut header = Header::default(); let last_hashes = Arc::new(vec![]); @@ -489,9 +498,7 @@ mod tests { engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } - fn proposal_seal(header: &Header, round: Round) -> Vec { - let tap = AccountProvider::transient_provider(); - + fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); @@ -502,6 +509,12 @@ mod tests { ] } + fn insert_and_unlock(tap: &Arc, acc: &str) -> Address { + let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + tap.unlock_account_permanently(addr, acc.into()).unwrap(); + addr + } + fn default_seal() -> Vec { vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] } @@ -543,22 +556,45 @@ mod tests { } } + #[test] + fn allows_correct_proposer() { + ::env_logger::init().unwrap(); + let (spec, tap) = setup(); + let engine = spec.engine; + + let mut header = Header::default(); + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Good proposer. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + + let mut header = Header::default(); + let random = insert_and_unlock(&tap, "101"); + header.set_author(random); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Bad proposer. + assert!(engine.verify_block_unordered(&header, None).is_err()); + } + #[test] fn verification_fails_on_wrong_signatures() { - let engine = Spec::new_test_tendermint().engine; + let (spec, tap) = setup(); + let engine = spec.engine; let mut header = Header::default(); - let tap = AccountProvider::transient_provider(); - let mut seal = Vec::new(); let v1 = tap.insert_account("0".sha3(), "0").unwrap(); - let sig1 = tap.sign(v1, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); + + header.set_author(v1); + let mut seal = proposal_seal(&tap, &header, 0); header.set_seal(seal.clone()); // Not enough signatures. - assert!(engine.verify_block_basic(&header, None).is_err()); + assert!(engine.verify_block_unordered(&header, None).is_err()); let v2 = tap.insert_account("101".sha3(), "101").unwrap(); let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); @@ -567,7 +603,7 @@ mod tests { header.set_seal(seal); // Enough signatures. - assert!(engine.verify_block_basic(&header, None).is_ok()); + assert!(engine.verify_block_unordered(&header, None).is_ok()); let verify_result = engine.verify_block_unordered(&header, None); @@ -581,10 +617,11 @@ mod tests { #[test] fn seal_with_enough_signatures_is_ok() { - let engine = Spec::new_test_tendermint().engine; + let (spec, tap) = setup(); + let engine = spec.engine; let mut header = Header::default(); - let seal = proposal_seal(&header, 0); + let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Enough signatures. @@ -596,20 +633,18 @@ mod tests { #[test] fn can_generate_seal() { - let spec = Spec::new_test_tendermint(); - let ref engine = *spec.engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()).unwrap(); + let (spec, _) = setup(); 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, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = tender.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + let seal = spec.engine.generate_seal(b.block()).unwrap(); + assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); } #[test] diff --git a/parity/run.rs b/parity/run.rs index aae4db74852..0f00bba9756 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -203,8 +203,14 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { sync_config.fork_block = spec.fork_block(); sync_config.warp_sync = cmd.warp_sync; + // prepare account provider + let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); + + // let the Engine access the accounts + spec.engine.register_account_provider(account_provider); + // create miner - let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec); + let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); miner.set_author(cmd.miner_extras.author); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); @@ -238,9 +244,6 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create supervisor let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path()); - // prepare account provider - let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); - // create client service. let service = try!(ClientService::start( client_config, From 8f6a464c5181abc14c60d13dbcdaffca80b2d4b0 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 17:15:22 +0000 Subject: [PATCH 081/382] new error types --- ethcore/src/error.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 9206447ebe7..5ba4aa0c2b7 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -170,6 +170,10 @@ pub enum BlockError { UnknownUncleParent(H256), /// The same author issued different votes at the same step. DoubleVote(H160), + /// The received block is from an incorrect proposer. + NotProposer(H160), + /// Signature does not belong to an authority. + NotAuthority(H160) } impl fmt::Display for BlockError { @@ -204,6 +208,8 @@ impl fmt::Display for BlockError { UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), + NotProposer(ref address) => format!("Author {} is not a current proposer.", address), + NotAuthority(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) From d5b15d4560a5d7ed4dc5c3e7e8c3e13212480576 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 17:15:42 +0000 Subject: [PATCH 082/382] change authorities for testing --- ethcore/res/tendermint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index ae265aa0bd3..d244b5e69c7 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -5,8 +5,8 @@ "params": { "gasLimitBoundDivisor": "0x0400", "authorities" : [ - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + "0xff7b8b40a1ec83e2955a0a8a008c73acae282ae7", + "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" ] } } From 9d3b2352cca5386de0da28b9a2433e32d448843e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 23 Nov 2016 16:29:15 +0100 Subject: [PATCH 083/382] More work. --- ethcore/src/client/client.rs | 3 ++- ethcore/src/client/config.rs | 29 +++++++++++++++++++++++++++++ ethcore/src/client/mod.rs | 2 +- ethcore/src/client/updater.rs | 2 ++ parity/cli/mod.rs | 2 ++ parity/cli/usage.txt | 19 +++++++++++-------- parity/configuration.rs | 6 +++++- 7 files changed, 52 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a4fa5aa57c5..102c4aab631 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -54,7 +54,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, + ChainNotify, UpdatePolicy, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -129,6 +129,7 @@ impl SleepState { /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. pub struct Client { mode: Mutex, + update_policy: UpdatePolicy, chain: RwLock>, tracedb: RwLock>, engine: Arc, diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index 045b8ee05ca..bf06df46254 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -66,6 +66,35 @@ impl FromStr for DatabaseCompactionProfile { } } +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum UpdateFilter { + All, + Patch, + Critical, + None, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct UpdatePolicy { + download_only: bool, + track: Track, +} + +/// Operating mode for the client. +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum UpdatePolicy { + /// Always on. + Active, + /// Goes offline after RLP is inactive for some (given) time, but + /// comes back online after a while of inactivity. + Passive(Duration, Duration), + /// Goes offline after RLP is inactive for some (given) time and + /// stays inactive. + Dark(Duration), + /// Always off. + Off, +} + /// Operating mode for the client. #[derive(Debug, Eq, PartialEq, Clone)] pub enum Mode { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index b49301bbd17..adeaaf64ac8 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -26,7 +26,7 @@ mod client; mod updater; pub use self::client::*; -pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; +pub use self::config::{Mode, ClientConfig, UpdatePolicy, Automation, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::error::Error; pub use types::ids::*; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 211fdeb1fb3..dc45a55ff83 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -34,6 +34,8 @@ pub struct Updater { client: Weak, operations: Operations, + + pub this: VersionInfo, pub release_info: Option, diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 3ad6259b806..33a48b3b0ef 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -77,6 +77,7 @@ usage! { flag_mode: String = "last", or |c: &Config| otry!(c.parity).mode.clone(), flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), + flag_auto_update: String = "consensus", or |c: &Config| otry!(c.parity).auto_update.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), flag_db_path: String = "$HOME/.parity", or |c: &Config| otry!(c.parity).db_path.clone(), flag_keys_path: String = "$HOME/.parity/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), @@ -501,6 +502,7 @@ mod tests { flag_mode: "last".into(), flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, + flag_auto_update: "consensus".into(), flag_chain: "xyz".into(), flag_db_path: "$HOME/.parity".into(), flag_keys_path: "$HOME/.parity/keys".into(), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index e12a2566e7c..0ac59da0ecc 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -24,20 +24,23 @@ Operating Options: wakes regularly to resync. dark - Parity syncs only when the RPC is active. offline - Parity doesn't sync. (default: {flag_mode}). - --updates POLICY Set the client updating policy. POLICY specifies - which updates Parity will auto-install: - track - All updates in the current release track. - patch - All updates of the current minor version. - critical - Only consensus/security updates. - none - No updates. Not recommended. - --no-consensus Force the binary to run even if there are known - issues regarding consensus. Not recommended. --mode-timeout SECS Specify the number of seconds before inactivity timeout occurs when mode is dark or passive (default: {flag_mode_timeout}). --mode-alarm SECS Specify the number of seconds before auto sleep reawake timeout occurs when mode is passive (default: {flag_mode_alarm}). + --auto-update TRACK Set a release track to automatically update and + install. + all - All updates in the current release track. + patch - All updates of the current minor version. + critical - Only consensus/security updates. + none - No updates will be auto-installed. + (default: {flag_auto_update}). + --no-download Normally new releases will be downloaded ready for + updating. This disables it. Not recommended. + --no-consensus Force the binary to run even if there are known + issues regarding consensus. Not recommended. --chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file or olympic, frontier, homestead, mainnet, morden, ropsten, classic, expanse, diff --git a/parity/configuration.rs b/parity/configuration.rs index a51d1227340..e08c853bd01 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -23,7 +23,7 @@ use cli::{Args, ArgsError}; use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address}; use util::log::Colour; use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; -use ethcore::client::VMType; +use ethcore::client::{VMType, UpdatePolicy}; use ethcore::miner::{MinerOptions, Banning}; use rpc::{IpcConfiguration, HttpConfiguration}; @@ -585,6 +585,10 @@ impl Configuration { } } + fn update_policy(&self) -> UpdatePolicy { + + } + fn directories(&self) -> Directories { use util::path; From 03ef95ba5027f899d1aecd29de9b81f06023f997 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 23 Nov 2016 20:35:21 +0100 Subject: [PATCH 084/382] Compiles. --- db/src/database.rs | 3 +-- ethcore/src/client/client.rs | 5 +++-- ethcore/src/client/config.rs | 38 +++++++++++++++++++++-------------- ethcore/src/client/mod.rs | 2 +- ethcore/src/client/updater.rs | 7 ++----- parity/blockchain.rs | 4 ++-- parity/cli/mod.rs | 7 +++++++ parity/cli/usage.txt | 4 +++- parity/configuration.rs | 37 +++++++++++++++++++++++++++++++--- parity/helpers.rs | 4 +++- parity/run.rs | 8 +++++++- parity/snapshot.rs | 2 +- 12 files changed, 87 insertions(+), 34 deletions(-) diff --git a/db/src/database.rs b/db/src/database.rs index e1774159b3c..6504900e68d 100644 --- a/db/src/database.rs +++ b/db/src/database.rs @@ -270,8 +270,7 @@ impl DatabaseService for Database { Ok(next_iterator) } - fn iter_next(&self, handle: IteratorHandle) -> Option - { + fn iter_next(&self, handle: IteratorHandle) -> Option { let mut iterators = self.iterators.write(); let mut iterator = match iterators.get_mut(&handle) { Some(some_iterator) => some_iterator, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 102c4aab631..004f181d976 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -129,7 +129,6 @@ impl SleepState { /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. pub struct Client { mode: Mutex, - update_policy: UpdatePolicy, chain: RwLock>, tracedb: RwLock>, engine: Arc, @@ -227,6 +226,8 @@ impl Client { accountdb: Default::default(), }; + + let client = Arc::new(Client { sleep_state: Mutex::new(SleepState::new(awake)), liveness: AtomicBool::new(awake), @@ -262,7 +263,7 @@ impl Client { if let Ok(ops_addr) = registrar.get_address(&(&b"operations"[..]).sha3(), "A") { if !ops_addr.is_zero() { trace!(target: "client", "Found operations at {}", ops_addr); - *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), ops_addr)); + *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), ops_addr, client.config.update_policy.clone())); } } *client.registrar.lock() = Some(registrar); diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index bf06df46254..effa0b1dc7b 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -66,33 +66,39 @@ impl FromStr for DatabaseCompactionProfile { } } +/// Filter for releases. #[derive(Debug, Eq, PartialEq, Clone)] pub enum UpdateFilter { + /// All releases following the same track. All, + /// Only those of the same minor version potentially changing tracks. Patch, + /// As with `All`, but only those which are known to be critical. Critical, + /// None. None, } +/// The policy for auto-updating. #[derive(Debug, Eq, PartialEq, Clone)] pub struct UpdatePolicy { - download_only: bool, - track: Track, + /// Download potential updates. + pub enable_downloading: bool, + /// Which of those downloaded should be automatically installed. + pub filter: UpdateFilter, } -/// Operating mode for the client. -#[derive(Debug, Eq, PartialEq, Clone)] -pub enum UpdatePolicy { - /// Always on. - Active, - /// Goes offline after RLP is inactive for some (given) time, but - /// comes back online after a while of inactivity. - Passive(Duration, Duration), - /// Goes offline after RLP is inactive for some (given) time and - /// stays inactive. - Dark(Duration), - /// Always off. - Off, +impl Default for UpdatePolicy { + fn default() -> Self { + UpdatePolicy { + enable_downloading: false, + filter: UpdateFilter::None, + } + } +} + +impl UpdatePolicy { + pub fn new() -> Self { Default::default() } } /// Operating mode for the client. @@ -130,6 +136,8 @@ impl Display for Mode { /// Client configuration. Includes configs for all sub-systems. #[derive(Debug, PartialEq, Default)] pub struct ClientConfig { + /// Updater policy. + pub update_policy: UpdatePolicy, /// Block queue configuration. pub queue: QueueConfig, /// Blockchain configuration. diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index adeaaf64ac8..7759d08ef45 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -26,7 +26,7 @@ mod client; mod updater; pub use self::client::*; -pub use self::config::{Mode, ClientConfig, UpdatePolicy, Automation, DatabaseCompactionProfile, BlockChainConfig, VMType}; +pub use self::config::{Mode, ClientConfig, UpdatePolicy, UpdateFilter, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::error::Error; pub use types::ids::*; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index dc45a55ff83..f4f40cfade1 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -18,8 +18,7 @@ use std::sync::Weak; use util::misc::{VersionInfo, ReleaseTrack, platform}; use util::{Address, H160, H256, FixedHash}; use client::operations::Operations; -use client::client::Client; -use client::BlockId; +use client::{Client, UpdatePolicy, BlockId}; pub struct ReleaseInfo { fork_supported: usize, @@ -34,15 +33,13 @@ pub struct Updater { client: Weak, operations: Operations, - - pub this: VersionInfo, pub release_info: Option, } impl Updater { - pub fn new(client: Weak, operations: Address) -> Self { + pub fn new(client: Weak, operations: Address, _update_policy: UpdatePolicy) -> Self { let mut u = Updater { client: client.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 7696d1b3870..70326de3bc3 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -153,7 +153,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result { try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&cmd.cache_config, Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm, cmd.pruning_history, cmd.check_seal); + let client_config = to_client_config(&cmd.cache_config, Default::default(), Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm, cmd.pruning_history, cmd.check_seal); // build client let service = try!(ClientService::start( @@ -304,7 +304,7 @@ fn execute_export(cmd: ExportBlockchain) -> Result { try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&cmd.cache_config, Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm, cmd.pruning_history, cmd.check_seal); + let client_config = to_client_config(&cmd.cache_config, Default::default(), Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm, cmd.pruning_history, cmd.check_seal); let service = try!(ClientService::start( client_config, diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 3948929147c..08474ea45cf 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -78,6 +78,8 @@ usage! { flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), flag_auto_update: String = "consensus", or |c: &Config| otry!(c.parity).auto_update.clone(), + flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), + flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), flag_db_path: String = "$HOME/.parity", or |c: &Config| otry!(c.parity).db_path.clone(), flag_keys_path: String = "$HOME/.parity/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), @@ -290,6 +292,9 @@ struct Operating { mode: Option, mode_timeout: Option, mode_alarm: Option, + auto_update: Option, + no_download: Option, + no_consensus: Option, chain: Option, db_path: Option, keys_path: Option, @@ -504,6 +509,8 @@ mod tests { flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, flag_auto_update: "consensus".into(), + flag_no_download: false, + flag_no_consensus: false, flag_chain: "xyz".into(), flag_db_path: "$HOME/.parity".into(), flag_keys_path: "$HOME/.parity/keys".into(), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index f905ec74a9a..1a6fe3b612e 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -38,9 +38,11 @@ Operating Options: none - No updates will be auto-installed. (default: {flag_auto_update}). --no-download Normally new releases will be downloaded ready for - updating. This disables it. Not recommended. + updating. This disables it. Not recommended. + (default: {flag_no_download}). --no-consensus Force the binary to run even if there are known issues regarding consensus. Not recommended. + (default: {flag_no_consensus}). --chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file or olympic, frontier, homestead, mainnet, morden, ropsten, classic, expanse, diff --git a/parity/configuration.rs b/parity/configuration.rs index 0f0c1981448..3cb19fe378e 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -23,7 +23,7 @@ use cli::{Args, ArgsError}; use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address}; use util::log::Colour; use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; -use ethcore::client::{VMType, UpdatePolicy}; +use ethcore::client::{VMType, UpdatePolicy, UpdateFilter}; use ethcore::miner::{MinerOptions, Banning}; use rpc::{IpcConfiguration, HttpConfiguration}; @@ -81,6 +81,7 @@ impl Configuration { let pruning_history = self.args.flag_pruning_history; let vm_type = try!(self.vm_type()); let mode = match self.args.flag_mode.as_ref() { "last" => None, mode => Some(try!(to_mode(&mode, self.args.flag_mode_timeout, self.args.flag_mode_alarm))), }; + let update_policy = try!(self.update_policy()); let miner_options = try!(self.miner_options()); let logger_config = self.logger_config(); let http_conf = try!(self.http_config()); @@ -233,6 +234,7 @@ impl Configuration { acc_conf: try!(self.accounts_config()), gas_pricer: try!(self.gas_pricer_config()), miner_extras: try!(self.miner_extras()), + update_policy: update_policy, mode: mode, tracing: tracing, fat_db: fat_db, @@ -251,6 +253,7 @@ impl Configuration { no_periodic_snapshot: self.args.flag_no_periodic_snapshot, check_seal: !self.args.flag_no_seal_check, download_old_blocks: !self.args.flag_no_ancient_blocks, + require_consensus: !self.args.flag_no_consensus, }; Cmd::Run(run_cmd) }; @@ -586,8 +589,17 @@ impl Configuration { } } - fn update_policy(&self) -> UpdatePolicy { - + fn update_policy(&self) -> Result { + Ok(UpdatePolicy { + enable_downloading: !self.args.flag_no_download, + filter: match self.args.flag_auto_update.as_ref() { + "none" => UpdateFilter::None, + "critical" => UpdateFilter::Critical, + "patch" => UpdateFilter::Patch, + "all" => UpdateFilter::All, + _ => return Err("Invalid value for `--auto-update`. See `--help` for more information.".into()), + }, + }) } fn directories(&self) -> Directories { @@ -877,6 +889,7 @@ mod tests { no_periodic_snapshot: false, check_seal: true, download_old_blocks: true, + require_consensus: true, })); } @@ -901,6 +914,24 @@ mod tests { assert_eq!(conf3.miner_options().unwrap(), mining_options); } + #[test] + fn should_parse_updater_options() { + // when + let conf0 = parse(&["parity"]); + let conf1 = parse(&["parity", "--auto-update", "all"]); + let conf2 = parse(&["parity", "--no-download", "--auto-update=patch"]); + let conf3 = parse(&["parity", "--auto-update=xxx"]); + + // then + assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, filter: UpdateFilter::Critical}); + mining_options.tx_queue_strategy = PrioritizationStrategy::GasFactorAndGasPrice; + assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, filter: UpdateFilter::All}); + mining_options.tx_queue_strategy = PrioritizationStrategy::GasPriceOnly; + assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, filter: UpdateFilter::Patch}); + mining_options.tx_queue_strategy = PrioritizationStrategy::GasAndGasPrice; + assert!(conf3.update_policy().is_err()); + } + #[test] fn should_parse_network_settings() { // given diff --git a/parity/helpers.rs b/parity/helpers.rs index fe5303ec87a..d748caa6ada 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -20,7 +20,7 @@ use std::time::Duration; use std::fs::File; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; -use ethcore::client::{Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; +use ethcore::client::{UpdatePolicy, Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; use ethcore::miner::{PendingSet, GasLimit, PrioritizationStrategy}; use cache::CacheConfig; use dir::DatabaseDirectories; @@ -209,6 +209,7 @@ pub fn default_network_config() -> ::ethsync::NetworkConfiguration { #[cfg_attr(feature = "dev", allow(too_many_arguments))] pub fn to_client_config( cache_config: &CacheConfig, + update_policy: UpdatePolicy, mode: Mode, tracing: bool, fat_db: bool, @@ -242,6 +243,7 @@ pub fn to_client_config( // in bytes client_config.jump_table_size = cache_config.jump_tables() as usize * mb; + client_config.update_policy = update_policy; client_config.mode = mode; client_config.tracing.enabled = tracing; client_config.fat_db = fat_db; diff --git a/parity/run.rs b/parity/run.rs index f56ba5b9268..daa6aae051c 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -23,7 +23,7 @@ use ethsync::NetworkConfiguration; use util::{Colour, version, RotatingLogger}; use io::{MayPanic, ForwardPanic, PanicHandler}; use ethcore_logger::{Config as LogConfig}; -use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, ChainNotify, BlockChainClient}; +use ethcore::client::{Mode, UpdatePolicy, DatabaseCompactionProfile, VMType, ChainNotify, BlockChainClient}; use ethcore::service::ClientService; use ethcore::account_provider::AccountProvider; use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions}; @@ -75,6 +75,7 @@ pub struct RunCmd { pub acc_conf: AccountsConfig, pub gas_pricer: GasPricerConfig, pub miner_extras: MinerExtras, + pub update_policy: UpdatePolicy, pub mode: Option, pub tracing: Switch, pub fat_db: Switch, @@ -92,6 +93,7 @@ pub struct RunCmd { pub no_periodic_snapshot: bool, pub check_seal: bool, pub download_old_blocks: bool, + pub require_consensus: bool, } pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> { @@ -158,6 +160,9 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { trace!(target: "mode", "mode is {:?}", mode); let network_enabled = match &mode { &Mode::Dark(_) | &Mode::Off => false, _ => true, }; + // get the update policy + let update_policy = cmd.update_policy; + // prepare client and snapshot paths. let client_path = db_dirs.client_path(algorithm); let snapshot_path = db_dirs.snapshot_path(); @@ -219,6 +224,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create client config let client_config = to_client_config( &cmd.cache_config, + update_policy, mode, tracing, fat_db, diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 804047596a0..d74adc1b479 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -170,7 +170,7 @@ impl SnapshotCommand { try!(execute_upgrades(&db_dirs, algorithm, self.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&self.cache_config, Mode::Active, tracing, fat_db, self.compaction, self.wal, VMType::default(), "".into(), algorithm, self.pruning_history, true); + let client_config = to_client_config(&self.cache_config, Default::default(), Mode::Active, tracing, fat_db, self.compaction, self.wal, VMType::default(), "".into(), algorithm, self.pruning_history, true); let service = try!(ClientService::start( client_config, From 207364929c9f82e9cc40bf108ffa28278a2d943b Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:55:16 +0000 Subject: [PATCH 085/382] improve error types --- ethcore/src/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 5ba4aa0c2b7..cadb4fb1f17 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -171,9 +171,9 @@ pub enum BlockError { /// The same author issued different votes at the same step. DoubleVote(H160), /// The received block is from an incorrect proposer. - NotProposer(H160), + NotProposer(Mismatch), /// Signature does not belong to an authority. - NotAuthority(H160) + NotAuthorized(H160) } impl fmt::Display for BlockError { @@ -208,8 +208,8 @@ impl fmt::Display for BlockError { UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), - NotProposer(ref address) => format!("Author {} is not a current proposer.", address), - NotAuthority(ref address) => format!("Signer {} is not authorized.", address), + NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), + NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) From a3730b30421ee0eb6be1a5c66d72e41a6cf47262 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:56:27 +0000 Subject: [PATCH 086/382] change proposer address --- ethcore/res/tendermint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index d244b5e69c7..94eaa626d85 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -5,7 +5,7 @@ "params": { "gasLimitBoundDivisor": "0x0400", "authorities" : [ - "0xff7b8b40a1ec83e2955a0a8a008c73acae282ae7", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" ] } From 38f25fc1952cc186be2b29ffa5c776ea226c0849 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:57:04 +0000 Subject: [PATCH 087/382] message tests and fixes --- ethcore/src/engines/tendermint/message.rs | 130 ++++++++++++++++++---- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index cdcadb5b5cd..a9a4462596c 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use error::Error; +use error::{Error, BlockError}; use header::Header; use rlp::*; use ethkey::{recover, public_to_address}; @@ -34,7 +34,8 @@ pub struct ConsensusMessage { fn consensus_round(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[0].as_slice()).as_val() + let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed"); + UntrustedRlp::new(round_rlp.as_slice()).as_val() } impl ConsensusMessage { @@ -80,7 +81,7 @@ impl PartialOrd for ConsensusMessage { } impl Step { - fn number(&self) -> i8 { + fn number(&self) -> u8 { match *self { Step::Propose => 0, Step::Prevote => 1, @@ -107,17 +108,17 @@ impl Ord for ConsensusMessage { impl Decodable for Step { fn decode(decoder: &D) -> Result where D: Decoder { match try!(decoder.as_rlp().as_val()) { - 0u8 => Ok(Step::Prevote), - 1 => Ok(Step::Precommit), + 0u8 => Ok(Step::Propose), + 1 => Ok(Step::Prevote), + 2 => Ok(Step::Precommit), _ => Err(DecoderError::Custom("Invalid step.")), } } } - impl Encodable for Step { fn rlp_append(&self, s: &mut RlpStream) { - s.append(&(self.number() as u8)); + s.append(&self.number()); } } @@ -125,9 +126,6 @@ impl Encodable for Step { impl Decodable for ConsensusMessage { fn decode(decoder: &D) -> Result where D: Decoder { let rlp = decoder.as_rlp(); - if decoder.as_raw().len() != try!(rlp.payload_info()).total() { - return Err(DecoderError::RlpIsTooBig); - } let m = try!(rlp.at(1)); let block_message: H256 = try!(m.val_at(3)); Ok(ConsensusMessage { @@ -141,23 +139,25 @@ impl Decodable for ConsensusMessage { } }) } -} - +} impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(2); - s.append(&self.signature); - s.begin_list(4); - s.append(&self.height); - s.append(&self.round); - s.append(&self.step); - s.append(&self.block_hash.unwrap_or(H256::zero())); + s.begin_list(2) + .append(&self.signature) + // TODO: figure out whats wrong with nested list encoding + .begin_list(5) + .append(&self.height) + .append(&self.round) + .append(&self.step) + .append(&self.block_hash.unwrap_or(H256::zero())); } } pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { - let mut s = RlpStream::new_list(4); + // TODO: figure out whats wrong with nested list encoding + let mut s = RlpStream::new_list(5); s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + println!("{:?}, {:?}, {:?}, {:?}", &height, &round, &step, &block_hash); s.out() } @@ -170,7 +170,95 @@ pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { let mut s = RlpStream::new_list(2); - s.append(signature).append(&vote_info); + s.append(signature).append_raw(&vote_info, 1); s.out() }) } + +#[cfg(test)] +mod tests { + use util::*; + use rlp::*; + use super::super::Step; + use super::*; + use account_provider::AccountProvider; + use header::Header; + + #[test] + fn encode_decode() { + let message = ConsensusMessage { + signature: H520::default(), + height: 10, + round: 123, + step: Step::Precommit, + block_hash: Some("1".sha3()) + }; + let raw_rlp = ::rlp::encode(&message).to_vec(); + let rlp = Rlp::new(&raw_rlp); + assert_eq!(message, rlp.as_val()); + + let message = ConsensusMessage { + signature: H520::default(), + height: 1314, + round: 0, + step: Step::Prevote, + block_hash: None + }; + let raw_rlp = ::rlp::encode(&message); + let rlp = Rlp::new(&raw_rlp); + assert_eq!(message, rlp.as_val()); + } + + #[test] + fn generate_and_verify() { + let tap = Arc::new(AccountProvider::transient_provider()); + let addr = tap.insert_account("0".sha3(), "0").unwrap(); + tap.unlock_account_permanently(addr, "0".into()).unwrap(); + + let raw_rlp = message_full_rlp( + |mh| tap.sign(addr, None, mh).ok().map(H520::from), + 123, + 2, + Step::Precommit, + Some(H256::default()) + ).unwrap(); + + let rlp = UntrustedRlp::new(&raw_rlp); + let message: ConsensusMessage = rlp.as_val().unwrap(); + match message.verify() { Ok(a) if a == addr => {}, _ => panic!(), }; + } + + #[test] + fn proposal_message() { + let mut header = Header::default(); + let seal = vec![ + ::rlp::encode(&0u8).to_vec(), + ::rlp::encode(&H520::default()).to_vec(), + Vec::new() + ]; + header.set_seal(seal); + let message = ConsensusMessage::new_proposal(&header).unwrap(); + assert_eq!( + message, + ConsensusMessage { + signature: Default::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: Some(header.bare_hash()) + } + ); + } + + #[test] + fn message_info_from_header() { + let mut header = Header::default(); + let seal = vec![ + ::rlp::encode(&0u8).to_vec(), + ::rlp::encode(&H520::default()).to_vec(), + Vec::new() + ]; + header.set_seal(seal); + assert_eq!(message_info_rlp_from_header(&header).unwrap().to_vec(), vec![228, 128, 128, 2, 160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]); + } +} From 8f37807d4ba6a2679cd0002623e0e6d3abd0f588 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:57:54 +0000 Subject: [PATCH 088/382] seal checks --- ethcore/src/engines/tendermint/mod.rs | 104 +++++++++++++------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b110a602d27..eb5975d9a8b 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -211,13 +211,15 @@ impl Tendermint { } /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> bool { + fn is_proposer(&self, address: &Address) -> Result<(), BlockError> { let ref p = self.our_params; let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); - println!("{:?}", &proposer); - println!("{:?}", &address); - proposer == address + if proposer == address { + Ok(()) + } else { + Err(BlockError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + } } fn is_height(&self, message: &ConsensusMessage) -> bool { @@ -310,7 +312,7 @@ impl Engine for Tendermint { /// Round proposer switching. fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_proposer(address)) + Some(self.is_proposer(address).is_ok()) } /// Attempt to seal the block internally using all available signatures. @@ -318,6 +320,7 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); + println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); @@ -341,7 +344,7 @@ impl Engine for Tendermint { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); // TODO: Do not admit old messages. if !self.is_authority(&sender) { - try!(Err(BlockError::InvalidSeal)); + try!(Err(BlockError::NotAuthorized(sender))); } // Check if the message is known. @@ -403,17 +406,22 @@ impl Engine for Tendermint { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = try!(ConsensusMessage::new_proposal(header)); let proposer = try!(proposal.verify()); - if !self.is_proposer(&proposer) { - try!(Err(BlockError::InvalidSeal)) - } + try!(self.is_proposer(&proposer)); self.votes.vote(proposal, proposer); let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + + let mut signature_count = 0; for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { - try!(Err(BlockError::InvalidSeal)) + try!(Err(BlockError::NotAuthorized(address))) } + + signature_count += 1; + } + if signature_count > self.our_params.authority_n { + try!(Err(BlockError::InvalidSealArity(Mismatch { expected: self.our_params.authority_n, found: signature_count }))) } Ok(()) } @@ -478,6 +486,7 @@ mod tests { use super::params::TendermintParams; use super::message::*; + /// Accounts inserted with "1" and "2" are authorities. First proposer is "0". fn setup() -> (Spec, Arc) { let tap = Arc::new(AccountProvider::transient_provider()); let spec = Spec::new_test_tendermint(); @@ -500,6 +509,7 @@ mod tests { fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); + println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); vec![ @@ -545,7 +555,7 @@ mod tests { #[test] fn verification_fails_on_short_seal() { let engine = Spec::new_test_tendermint().engine; - let header: Header = Header::default(); + let header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -558,16 +568,17 @@ mod tests { #[test] fn allows_correct_proposer() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine; let mut header = Header::default(); let validator = insert_and_unlock(&tap, "0"); + println!("validator: {:?}", &validator); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. + println!("{:?}", engine.verify_block_unordered(&header, None)); assert!(engine.verify_block_unordered(&header, None).is_ok()); let mut header = Header::default(); @@ -576,75 +587,64 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. - assert!(engine.verify_block_unordered(&header, None).is_err()); + match engine.verify_block_unordered(&header, None) { + Err(Error::Block(BlockError::NotProposer(_))) => {}, + _ => panic!(), + } } #[test] - fn verification_fails_on_wrong_signatures() { + fn seal_signatures_checking() { let (spec, tap) = setup(); let engine = spec.engine; - let mut header = Header::default(); - - let v1 = tap.insert_account("0".sha3(), "0").unwrap(); - - header.set_author(v1); + let mut header = Header::default(); + let proposer = insert_and_unlock(&tap, "0"); + header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - header.set_seal(seal.clone()); - - // Not enough signatures. - assert!(engine.verify_block_unordered(&header, None).is_err()); + let voter = insert_and_unlock(&tap, "1"); + let vote_info = message_info_rlp(0, 0, Step::Prevote, Some(header.bare_hash())); + let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); - let v2 = tap.insert_account("101".sha3(), "101").unwrap(); - let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig2 as &[u8])).to_vec()); + seal[2] = ::rlp::encode(&vec![H520::from(signature)]).to_vec(); - header.set_seal(seal); + header.set_seal(seal.clone()); - // Enough signatures. + // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); - let verify_result = engine.verify_block_unordered(&header, None); + let bad_voter = insert_and_unlock(&tap, "101"); + let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); + seal.push(::rlp::encode(&(&*bad_signature as &[u8])).to_vec()); - // But wrong signatures. - match verify_result { - Err(Error::Block(BlockError::InvalidSeal)) => (), - Err(_) => panic!("should be block seal-arity mismatch error (got {:?})", verify_result), - _ => panic!("Should be error, got Ok"), - } - } - - #[test] - fn seal_with_enough_signatures_is_ok() { - let (spec, tap) = setup(); - let engine = spec.engine; - let mut header = Header::default(); - - let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); - // Enough signatures. - assert!(engine.verify_block_basic(&header, None).is_ok()); - - // And they are ok. - assert!(engine.verify_block_unordered(&header, None).is_ok()); + // One good and one bad signature. + match engine.verify_block_unordered(&header, None) { + Err(Error::Block(BlockError::NotAuthorized(_))) => {}, + _ => panic!(), + } } #[test] fn can_generate_seal() { - let (spec, _) = setup(); + let (spec, tap) = setup(); 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(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); + let validator = insert_and_unlock(&tap, "0"); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, validator, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); let seal = spec.engine.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); + println!("{:?}", seal.clone()); + if let Err(e) = b.try_seal(spec.engine.as_ref(), seal) { + println!("{:?}", e.0); + } } #[test] From 735df6c30fd70dde26b331b64ecbd828d1b7a211 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Nov 2016 17:19:48 +0100 Subject: [PATCH 089/382] More work. --- ethcore/res/ethereum/ropsten.json | 3 +- ethcore/src/client/client.rs | 2 +- ethcore/src/client/config.rs | 4 --- ethcore/src/client/updater.rs | 59 +++++++++++++++++++++++++------ parity/cli/mod.rs | 4 +-- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/ethcore/res/ethereum/ropsten.json b/ethcore/res/ethereum/ropsten.json index 62282801ddf..7d2d53016c2 100644 --- a/ethcore/res/ethereum/ropsten.json +++ b/ethcore/res/ethereum/ropsten.json @@ -14,7 +14,8 @@ "eip155Transition": 10, "eip160Transition": 10, "eip161abcTransition": 10, - "eip161dTransition": 10 + "eip161dTransition": 10, + "maxCodeSize": 24576 } } }, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 004f181d976..200527222d0 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -54,7 +54,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, UpdatePolicy, + ChainNotify, }; use client::Error as ClientError; use env_info::EnvInfo; diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index effa0b1dc7b..1b153d888f9 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -97,10 +97,6 @@ impl Default for UpdatePolicy { } } -impl UpdatePolicy { - pub fn new() -> Self { Default::default() } -} - /// Operating mode for the client. #[derive(Debug, Eq, PartialEq, Clone)] pub enum Mode { diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index f4f40cfade1..0b83307410c 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -21,47 +21,84 @@ use client::operations::Operations; use client::{Client, UpdatePolicy, BlockId}; pub struct ReleaseInfo { - fork_supported: usize, - latest_known_fork: usize, + pub latest_known_fork: usize, - latest: VersionInfo, - latest_fork: usize, - latest_binary: Option, + pub latest: VersionInfo, + pub latest_fork: usize, + pub latest_binary: Option, } pub struct Updater { client: Weak, operations: Operations, + update_policy: UpdatePolicy, pub this: VersionInfo, + pub this_fork: Option, pub release_info: Option, - } impl Updater { - pub fn new(client: Weak, operations: Address, _update_policy: UpdatePolicy) -> Self { + pub fn new(client: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { let mut u = Updater { client: client.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), + update_policy: update_policy, this: VersionInfo::this(), + this_fork: None, release_info: None, }; + + let (fork, track, _, _) = self.operations.release(client_id, &v.hash.into())?; + u.this_fork = if track > 0 { Some(fork) } else { None }; + u.release_info = u.get_release_info().ok(); + + // TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! REMOVE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if u.this.track == ReleaseTrack::Unknown { u.this.track = ReleaseTrack::Nightly; - } + } + u } + /// Is the currently running client capable of supporting the current chain? + /// `Some` answer or `None` if information on the running client is not available. + pub fn is_capable(&self) -> Option { + self.release_info.and_then(|relinfo| { + relinfo.fork_supported.map(|fork_supported| { + let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); + fork_supported >= relinfo.latest_fork || current_number < relinfo.latest_fork + }) + }) + } + + /// The release which is ready to be upgraded to, if any. If this returns `Some`, then + /// `execute_upgrade` may be called. + pub fn upgrade_ready(&self) -> Option { + unimplemented!() + } + + /// Actually upgrades the client. Assumes that the binary has been downloaded. + /// @returns `true` on success. + pub fn execute_upgrade(&mut self) -> bool { + unimplemented!() + } + + /// Our version info. + pub fn version_info() -> &VersionInfo { &self.this } + + /// Information gathered concerning the release. + pub fn release_info() -> &Option { &self.release_info } + fn get_release_info(&mut self) -> Result { - //601e0fb0fd7e9e1cec18f8872e8713117cab4e84 if self.this.track == ReleaseTrack::Unknown { return Err(format!("Current executable ({}) is unreleased.", H160::from(self.this.hash))); } let client_id = "parity"; - let latest_known_fork = self.operations.latest_fork()?; - let our_fork = self.operations.release(client_id, &self.this.hash.into())?.0; + + let latest_release = self.operations.latest_in_track(client_id, self.this.track.into())?; let (fork, track, semver, _critical) = self.operations.release(client_id, &latest_release)?; let maybe_latest_binary = self.operations.checksum(client_id, &latest_release, &platform())?; diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 08474ea45cf..b9d4770be19 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -77,7 +77,7 @@ usage! { flag_mode: String = "last", or |c: &Config| otry!(c.parity).mode.clone(), flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), - flag_auto_update: String = "consensus", or |c: &Config| otry!(c.parity).auto_update.clone(), + flag_auto_update: String = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(), flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), @@ -508,7 +508,7 @@ mod tests { flag_mode: "last".into(), flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, - flag_auto_update: "consensus".into(), + flag_auto_update: "critical".into(), flag_no_download: false, flag_no_consensus: false, flag_chain: "xyz".into(), From 45017c599a28fa33113caf1c2b095e50afb60125 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Nov 2016 19:11:29 +0100 Subject: [PATCH 090/382] Update test, fix number. --- ethcore/res/ethereum/tests | 2 +- ethcore/src/json_tests/transaction.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index 43885212454..12e82bca2ab 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -34,7 +34,7 @@ fn do_json_test(json_data: &[u8]) -> Vec { Some(x) if x < 1_150_000 => &old_schedule, Some(_) => &new_schedule }; - let allow_network_id_of_one = number.map_or(false, |n| n >= 3_500_000); + let allow_network_id_of_one = number.map_or(false, |n| n >= 2_675_000); let rlp: Vec = test.rlp.into(); let res = UntrustedRlp::new(&rlp) From 04acdd6ca0afa7119653487d6673f7020573fb72 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:57:58 +0000 Subject: [PATCH 091/382] reuse rlp generation --- ethcore/src/engines/tendermint/message.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index a9a4462596c..df671f82bda 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use error::{Error, BlockError}; +use error::Error; use header::Header; use rlp::*; use ethkey::{recover, public_to_address}; @@ -49,7 +49,6 @@ impl ConsensusMessage { }) } - pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -140,16 +139,13 @@ impl Decodable for ConsensusMessage { }) } } + impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { + let info = message_info_rlp(self.height, self.round, self.step, self.block_hash); s.begin_list(2) .append(&self.signature) - // TODO: figure out whats wrong with nested list encoding - .begin_list(5) - .append(&self.height) - .append(&self.round) - .append(&self.step) - .append(&self.block_hash.unwrap_or(H256::zero())); + .append_raw(&info, 1); } } From f867372dfee89d4cf6a365f559a86c64f6538e82 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:58:29 +0000 Subject: [PATCH 092/382] increase default proposal time --- ethcore/src/engines/tendermint/transition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a5cb02763cc..a3f2e7b46f3 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -48,7 +48,7 @@ impl TendermintTimeouts { impl Default for TendermintTimeouts { fn default() -> Self { TendermintTimeouts { - propose: Duration::milliseconds(1000), + propose: Duration::milliseconds(2000), prevote: Duration::milliseconds(1000), precommit: Duration::milliseconds(1000), commit: Duration::milliseconds(1000) From da499b0a4ac0f07c8a176267cf6e8b627125db3e Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:59:08 +0000 Subject: [PATCH 093/382] self contained test proposal --- ethcore/res/tendermint.json | 2 +- ethcore/src/engines/tendermint/mod.rs | 109 +++++++++++++------------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 94eaa626d85..e411d54e2dd 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -6,7 +6,7 @@ "gasLimitBoundDivisor": "0x0400", "authorities" : [ "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", - "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" ] } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index eb5975d9a8b..d4e756779d2 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -320,7 +320,6 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); @@ -472,7 +471,8 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; - use rlp::{UntrustedRlp, RlpStream, Stream, View}; + use rlp::{UntrustedRlp, View}; + use io::{IoContext, IoHandler}; use block::*; use state_db::StateDB; use error::{Error, BlockError}; @@ -480,6 +480,8 @@ mod tests { use env_info::EnvInfo; use tests::helpers::*; use account_provider::AccountProvider; + use io::IoService; + use service::ClientIoMessage; use spec::Spec; use engines::{Engine, EngineError}; use super::*; @@ -494,12 +496,16 @@ mod tests { (spec, tap) } - fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { - let mut header = Header::default(); - let last_hashes = Arc::new(vec![]); - let b = OpenBlock::new(engine.as_ref(), Default::default(), false, db.boxed_clone(), &header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); + fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec) { + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); + let genesis_header = spec.genesis_header(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - engine.generate_seal(b.block()) + let seal = spec.engine.generate_seal(b.block()).unwrap(); + (b, seal) } fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { @@ -525,8 +531,17 @@ mod tests { addr } - fn default_seal() -> Vec { - vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] + struct TestIo; + + impl IoHandler for TestIo { + fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { + match *net_message { + ClientIoMessage::UpdateSealing => {}, + ClientIoMessage::SubmitSeal(ref block_hash, ref seal) => {}, + ClientIoMessage::BroadcastMessage(ref message) => {}, + _ => {} // ignore other messages + } + } } #[test] @@ -604,19 +619,21 @@ mod tests { let mut seal = proposal_seal(&tap, &header, 0); let voter = insert_and_unlock(&tap, "1"); - let vote_info = message_info_rlp(0, 0, Step::Prevote, Some(header.bare_hash())); + println!("voter: {:?}", &voter); + let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); - seal[2] = ::rlp::encode(&vec![H520::from(signature)]).to_vec(); + seal[2] = ::rlp::encode(&vec![H520::from(signature.clone())]).to_vec(); header.set_seal(seal.clone()); + println!("{:?}", engine.verify_block_unordered(&header, None)); // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); - seal.push(::rlp::encode(&(&*bad_signature as &[u8])).to_vec()); + seal[2] = ::rlp::encode(&vec![H520::from(signature), H520::from(bad_signature)]).to_vec(); header.set_seal(seal); @@ -630,59 +647,30 @@ mod tests { #[test] fn can_generate_seal() { let (spec, tap) = setup(); + + let proposer = insert_and_unlock(&tap, "0"); - 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 validator = insert_and_unlock(&tap, "0"); - let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, validator, (3141562.into(), 31415620.into()), vec![]).unwrap(); - let b = b.close_and_lock(); - - let seal = spec.engine.generate_seal(b.block()).unwrap(); - println!("{:?}", seal.clone()); - if let Err(e) = b.try_seal(spec.engine.as_ref(), seal) { - println!("{:?}", e.0); - } - } - - #[test] - fn propose_step() { - let spec = Spec::new_test_tendermint(); - let engine = spec.engine.clone(); - let tap = AccountProvider::transient_provider(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); - - let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, &db, not_authority_addr).is_none()); - - let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); - - let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_seal(), propose_default(&engine, &db, proposer_addr).unwrap()); - - assert!(propose_default(&engine, &db, proposer_addr).is_none()); - assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); + let (b, seal) = propose_default(&spec, proposer); + assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); } #[test] fn prevote_step() { - let spec = Spec::new_test_tendermint(); - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); + let (spec, tap) = setup(); + let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); + + let io_service = IoService::::start().unwrap(); + io_service.register_handler(Arc::new(TestIo)); + engine.register_message_channel(io_service.channel()); - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + let v0 = insert_and_unlock(&tap, "0"); + let v1 = insert_and_unlock(&tap, "1"); // Propose - assert!(propose_default(&engine, &db, v1.clone()).is_some()); + propose_default(&spec, v0.clone()); let h = 0; let r = 0; @@ -694,6 +682,19 @@ mod tests { vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); } + #[test] + fn precommit_step() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + + let not_authority_addr = insert_and_unlock(&tap, "101"); + let proposer_addr = insert_and_unlock(&tap, "0"); + propose_default(&spec, proposer_addr); + + let not_proposer_addr = insert_and_unlock(&tap, "1"); + + } + #[test] fn timeout_switching() { let tender = { From a143da2cb8f3790d9aeaa85f84f83dcc3bf0b7ed Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 11:36:25 +0000 Subject: [PATCH 094/382] fix complete build --- parity/run.rs | 2 +- sync/src/api.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parity/run.rs b/parity/run.rs index 5ae667bdc7e..f860e414d3b 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -209,7 +209,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); // let the Engine access the accounts - spec.engine.register_account_provider(account_provider); + spec.engine.register_account_provider(account_provider.clone()); // create miner let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); diff --git a/sync/src/api.rs b/sync/src/api.rs index c85c5ebb01c..8d7d08037f3 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -180,7 +180,7 @@ impl SyncProvider for EthSync { } fn transactions_stats(&self) -> BTreeMap { - let sync = self.handler.sync.read(); + let sync = self.eth_handler.sync.read(); sync.transactions_stats() .iter() .map(|(hash, stats)| (*hash, stats.into())) From 8cddf9976a6273022557594e6bd239b85ea7e6b4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Nov 2016 15:43:49 +0100 Subject: [PATCH 095/382] Better information. --- ethcore/src/client/updater.rs | 102 ++++++++++++++++++++++------------ util/src/misc.rs | 2 +- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 0b83307410c..c21f47b4a57 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -20,12 +20,20 @@ use util::{Address, H160, H256, FixedHash}; use client::operations::Operations; use client::{Client, UpdatePolicy, BlockId}; +#[derive(Debug, Clone, PartialEq)] pub struct ReleaseInfo { - pub latest_known_fork: usize, + pub version: VersionInfo, + pub is_critical: bool, + pub fork: u64, + pub binary: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct OperationsInfo { + pub fork: u64, - pub latest: VersionInfo, - pub latest_fork: usize, - pub latest_binary: Option, + pub track: ReleaseInfo, + pub minor: Option, } pub struct Updater { @@ -33,11 +41,16 @@ pub struct Updater { operations: Operations, update_policy: UpdatePolicy, + // These don't change pub this: VersionInfo, - pub this_fork: Option, - pub release_info: Option, + pub this_fork: Option, + + // This does change + pub latest: Option, } +const CLIENT_ID: &'static str = "parity"; + impl Updater { pub fn new(client: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { let mut u = Updater { @@ -46,29 +59,29 @@ impl Updater { update_policy: update_policy, this: VersionInfo::this(), this_fork: None, - release_info: None, + latest: None, }; - let (fork, track, _, _) = self.operations.release(client_id, &v.hash.into())?; - u.this_fork = if track > 0 { Some(fork) } else { None }; - - u.release_info = u.get_release_info().ok(); + u.this_fork = u.operations.release(CLIENT_ID, &u.this.hash.into()).ok() + .and_then(|(fork, track, _, _)| if track > 0 {Some(fork as u64)} else {None}); // TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! REMOVE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if u.this.track == ReleaseTrack::Unknown { u.this.track = ReleaseTrack::Nightly; } + u.latest = u.collect_latest().ok(); + u } /// Is the currently running client capable of supporting the current chain? /// `Some` answer or `None` if information on the running client is not available. pub fn is_capable(&self) -> Option { - self.release_info.and_then(|relinfo| { - relinfo.fork_supported.map(|fork_supported| { + self.latest.as_ref().and_then(|latest| { + self.this_fork.map(|this_fork| { let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); - fork_supported >= relinfo.latest_fork || current_number < relinfo.latest_fork + this_fork >= latest.fork || current_number < latest.fork }) }) } @@ -86,46 +99,65 @@ impl Updater { } /// Our version info. - pub fn version_info() -> &VersionInfo { &self.this } + pub fn version_info(&self) -> &VersionInfo { &self.this } /// Information gathered concerning the release. - pub fn release_info() -> &Option { &self.release_info } + pub fn info(&self) -> &Option { &self.latest } - fn get_release_info(&mut self) -> Result { + fn collect_release_info(&self, release_id: &H256) -> Result { + let (fork, track, semver, is_critical) = self.operations.release(CLIENT_ID, release_id)?; + let latest_binary = self.operations.checksum(CLIENT_ID, release_id, &platform())?; + Ok(ReleaseInfo { + version: VersionInfo::from_raw(semver, track, release_id.clone().into()), + is_critical: is_critical, + fork: fork as u64, + binary: if latest_binary.is_zero() { None } else { Some(latest_binary) }, + }) + } + + fn collect_latest(&self) -> Result { if self.this.track == ReleaseTrack::Unknown { return Err(format!("Current executable ({}) is unreleased.", H160::from(self.this.hash))); } - let client_id = "parity"; - + let latest_in_track = self.operations.latest_in_track(CLIENT_ID, self.this.track.into())?; + let in_track = self.collect_release_info(&latest_in_track)?; + let mut in_minor = Some(in_track.clone()); + const PROOF: &'static str = "in_minor initialised and assigned with Some; loop breaks if None assigned; qed"; + while in_minor.as_ref().expect(PROOF).version.track != self.this.track { + let track = match in_minor.as_ref().expect(PROOF).version.track { + ReleaseTrack::Beta => ReleaseTrack::Stable, + ReleaseTrack::Nightly => ReleaseTrack::Beta, + _ => { in_minor = None; break; } + }; + in_minor = Some(self.collect_release_info(&self.operations.latest_in_track(CLIENT_ID, track.into())?)?); + } - let latest_release = self.operations.latest_in_track(client_id, self.this.track.into())?; - let (fork, track, semver, _critical) = self.operations.release(client_id, &latest_release)?; - let maybe_latest_binary = self.operations.checksum(client_id, &latest_release, &platform())?; - Ok(ReleaseInfo { - fork_supported: our_fork as usize, - latest_known_fork: latest_known_fork as usize, - latest: VersionInfo::from_raw(semver, track, latest_release.into()), - latest_fork: fork as usize, - latest_binary: if maybe_latest_binary.is_zero() { None } else { Some(maybe_latest_binary) }, + Ok(OperationsInfo { + fork: self.operations.latest_fork()? as u64, + track: in_track, + minor: in_minor, }) } pub fn tick(&mut self) { - self.release_info = self.get_release_info().ok(); - let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); info!(target: "updater", "Current release is {}", self.this); - if let Some(ref relinfo) = self.release_info { - info!(target: "updater", "Latest release in our track is {} ({} binary is {})", - relinfo.latest, + + self.latest = self.collect_latest().ok(); + let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); + + if let Some(ref latest) = self.latest { + info!(target: "updater", "Latest release in our track is v{} it is {}critical ({} binary is {})", + latest.track.version, + if latest.track.is_critical {""} else {"non-"}, platform(), - if let Some(ref b) = relinfo.latest_binary { + if let Some(ref b) = latest.track.binary { format!("{}", b) } else { "unreleased".into() } ); - info!(target: "updater", "Fork: this/current/latest/latest-known: #{}/#{}/#{}/#{}", relinfo.fork_supported, current_number, relinfo.latest_fork, relinfo.latest_known_fork); + info!(target: "updater", "Fork: this/current/latest/latest-known: {}/#{}/#{}/#{}", match self.this_fork { Some(f) => format!("#{}", f), None => "unknown".into(), }, current_number, latest.track.fork, latest.fork); } } } diff --git a/util/src/misc.rs b/util/src/misc.rs index 62231da9a86..f779b92e99e 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -91,7 +91,7 @@ impl Into for ReleaseTrack { } /// Version information of a particular release. -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct VersionInfo { /// The track on which it was released. pub track: ReleaseTrack, From d2099d9f13f742408bea20c95a4e9df144d97c3e Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:43:26 +0000 Subject: [PATCH 096/382] derive Eq for tests --- ethcore/src/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 1962bec5f5f..20a5587e0af 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -32,7 +32,7 @@ use nanoipc; use client::BlockChainClient; /// Message type for external and internal events -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub enum ClientIoMessage { /// Best Block Hash in chain has been changed NewChainHead, From f59746b2da3b354e228524a0249aecf456e4bc10 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:44:18 +0000 Subject: [PATCH 097/382] order messages by signature --- ethcore/src/engines/tendermint/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index df671f82bda..c8af7c9d8c8 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -99,7 +99,7 @@ impl Ord for ConsensusMessage { } else if self.step != m.step { self.step.number().cmp(&m.step.number()) } else { - self.block_hash.cmp(&m.block_hash) + self.signature.cmp(&m.signature) } } } From 8f72017bccf76398669ad4d55a197c99d0c4ccf8 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:44:57 +0000 Subject: [PATCH 098/382] add transition tracing --- ethcore/src/engines/tendermint/transition.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a3f2e7b46f3..ace5661b653 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -76,19 +76,23 @@ impl IoHandler for TransitionHandler { if let Some(engine) = self.engine.upgrade() { let next_step = match *engine.step.read() { Step::Propose => { + trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); Some(Step::Prevote) }, Step::Prevote if engine.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); Some(Step::Precommit) }, Step::Precommit if engine.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); Some(Step::Propose) }, Step::Commit => { + trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); Some(Step::Propose) From a7afbf4d25bf76e1a5cfa0dadfee10de0efd3c27 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:45:32 +0000 Subject: [PATCH 099/382] tracing and vote test --- ethcore/src/engines/tendermint/mod.rs | 46 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index d4e756779d2..f57c56093b3 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -140,8 +140,6 @@ impl Tendermint { Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), } } - } else { - warn!(target: "poa", "broadcast_message: Message could not be generated."); } } @@ -155,6 +153,7 @@ impl Tendermint { block_hash ) } else { + warn!(target: "poa", "generate_message: No AccountProvider available."); None } } @@ -163,10 +162,12 @@ impl Tendermint { *self.step.write() = step; match step { Step::Propose => { + trace!(target: "poa", "to_step: Transitioning to Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { + trace!(target: "poa", "to_step: Transitioning to Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -175,6 +176,7 @@ impl Tendermint { self.broadcast_message(block_hash) }, Step::Precommit => { + trace!(target: "poa", "to_step: Transitioning to Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -185,6 +187,7 @@ impl Tendermint { self.broadcast_message(block_hash); }, Step::Commit => { + trace!(target: "poa", "to_step: Transitioning to Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { @@ -348,6 +351,7 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { + trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, @@ -355,6 +359,7 @@ impl Engine for Tendermint { if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { + trace!(target: "poa", "handle_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -409,6 +414,7 @@ impl Engine for Tendermint { self.votes.vote(proposal, proposer); let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + // TODO: use addresses recovered during precommit vote let mut signature_count = 0; for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); @@ -508,8 +514,8 @@ mod tests { (b, seal) } - fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { - let m = message_full_rlp(signer, height, round, step, Some(Default::default())).unwrap(); + fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Option { + let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } @@ -531,15 +537,13 @@ mod tests { addr } - struct TestIo; + struct TestIo(ClientIoMessage); impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - match *net_message { - ClientIoMessage::UpdateSealing => {}, - ClientIoMessage::SubmitSeal(ref block_hash, ref seal) => {}, - ClientIoMessage::BroadcastMessage(ref message) => {}, - _ => {} // ignore other messages + let TestIo(ref expected) = *self; + if net_message == expected { + panic!() } } } @@ -655,31 +659,35 @@ mod tests { } #[test] + #[should_panic] fn prevote_step() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); - let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo)); - engine.register_message_channel(io_service.channel()); - let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); // Propose - propose_default(&spec, v0.clone()); + let (b, seal) = propose_default(&spec, v0.clone()); + let proposal = Some(b.header().bare_hash()); + + // Register IoHandler that panics on correct message. + let io_service = IoService::::start().unwrap(); + io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(Default::default(), seal)))).unwrap(); + engine.register_message_channel(io_service.channel()); - let h = 0; + let h = 1; let r = 0; // Prevote. - vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote_default(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote); - vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); } #[test] From de8dd47ff9262af4fa93bcef6e67536f7d872763 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Nov 2016 19:29:13 +0100 Subject: [PATCH 100/382] Fetch binaries. --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/hash-fetch/src/lib.rs | 2 +- ethcore/src/client/operations.rs | 4 +-- ethcore/src/client/updater.rs | 52 ++++++++++++++++++++++++++++++-- ethcore/src/lib.rs | 1 + 6 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f738d28b8d4..c8920f52283 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,7 @@ dependencies = [ "ethash 1.4.0", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", + "ethcore-hash-fetch 1.5.0", "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 7c65766726b..dd1c796a276 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -34,6 +34,7 @@ ethash = { path = "../ethash" } ethcore-util = { path = "../util" } ethcore-io = { path = "../util/io" } ethcore-devtools = { path = "../devtools" } +ethcore-hash-fetch = { path = "./hash-fetch" } ethjson = { path = "../json" } ethcore-ipc = { path = "../ipc/rpc" } ethstore = { path = "../ethstore" } diff --git a/ethcore/hash-fetch/src/lib.rs b/ethcore/hash-fetch/src/lib.rs index ffb74b260e8..b566121f54e 100644 --- a/ethcore/hash-fetch/src/lib.rs +++ b/ethcore/hash-fetch/src/lib.rs @@ -30,4 +30,4 @@ mod client; pub mod urlhint; -pub use client::{HashFetch, Client}; +pub use client::{HashFetch, Client, Error}; diff --git a/ethcore/src/client/operations.rs b/ethcore/src/client/operations.rs index e0408cdb06e..2124884c7d8 100644 --- a/ethcore/src/client/operations.rs +++ b/ethcore/src/client/operations.rs @@ -9,10 +9,10 @@ use util::{FixedHash, Uint}; pub struct Operations { contract: ethabi::Contract, address: util::Address, - do_call: Box) -> Result, String> + Send + 'static>, + do_call: Box) -> Result, String> + Send + Sync + 'static>, } impl Operations { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { Operations { contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"resetClientOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"isLatest\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"rejectTransaction\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_number\",\"type\":\"uint32\"},{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_hard\",\"type\":\"bool\"},{\"name\":\"_spec\",\"type\":\"bytes32\"}],\"name\":\"proposeFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"}],\"name\":\"removeClient\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"release\",\"outputs\":[{\"name\":\"o_forkBlock\",\"type\":\"uint32\"},{\"name\":\"o_track\",\"type\":\"uint8\"},{\"name\":\"o_semver\",\"type\":\"uint24\"},{\"name\":\"o_critical\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"build\",\"outputs\":[{\"name\":\"o_release\",\"type\":\"bytes32\"},{\"name\":\"o_platform\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"rejectFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"client\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"required\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setClientOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"fork\",\"outputs\":[{\"name\":\"name\",\"type\":\"bytes32\"},{\"name\":\"spec\",\"type\":\"bytes32\"},{\"name\":\"hard\",\"type\":\"bool\"},{\"name\":\"ratified\",\"type\":\"bool\"},{\"name\":\"requiredCount\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"},{\"name\":\"_checksum\",\"type\":\"bytes32\"}],\"name\":\"addChecksum\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"}],\"name\":\"confirmTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"proxy\",\"outputs\":[{\"name\":\"requiredCount\",\"type\":\"uint256\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"gas\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"addClient\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"clientOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_txid\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gas\",\"type\":\"uint256\"}],\"name\":\"proposeTransaction\",\"outputs\":[{\"name\":\"txSuccess\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"grandOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_forkBlock\",\"type\":\"uint32\"},{\"name\":\"_track\",\"type\":\"uint8\"},{\"name\":\"_semver\",\"type\":\"uint24\"},{\"name\":\"_critical\",\"type\":\"bool\"}],\"name\":\"addRelease\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"acceptFork\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"clientsRequired\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"}],\"name\":\"track\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_r\",\"type\":\"bool\"}],\"name\":\"setClientRequired\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"latestFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_track\",\"type\":\"uint8\"}],\"name\":\"latestInTrack\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_client\",\"type\":\"bytes32\"},{\"name\":\"_release\",\"type\":\"bytes32\"},{\"name\":\"_platform\",\"type\":\"bytes32\"}],\"name\":\"checksum\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposedFork\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), address: address, diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index c21f47b4a57..11bdc0b5c92 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -14,11 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::Weak; +use std::sync::{Mutex, Weak, Arc}; +use std::path::PathBuf; use util::misc::{VersionInfo, ReleaseTrack, platform}; -use util::{Address, H160, H256, FixedHash}; +use std::str::FromStr; +use util::{Bytes, Address, H160, H256, FixedHash}; use client::operations::Operations; -use client::{Client, UpdatePolicy, BlockId}; +use client::{Client, BlockChainClient, UpdatePolicy, BlockId}; +use fetch::HashFetch; +use fetch; #[derive(Debug, Clone, PartialEq)] pub struct ReleaseInfo { @@ -40,6 +44,7 @@ pub struct Updater { client: Weak, operations: Operations, update_policy: UpdatePolicy, + fetch_handler: Mutex>, // These don't change pub this: VersionInfo, @@ -51,12 +56,38 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; +struct FetchHandler { + client: Weak, +} + +impl fetch::urlhint::ContractClient for FetchHandler { + fn registrar(&self) -> Result { + self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? + .additional_params() + .get("registrar") + .and_then(|s| Address::from_str(s).ok()) + .ok_or_else(|| "Registrar not available".into()) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? + .call_contract(address, data) + } +} + +fn start_fetch(client: Weak, hash: H256, on_done: Box) + Send>) -> Result { + let f = fetch::Client::new(Arc::new(FetchHandler { client: client, })); + let r = f.fetch(hash, on_done); + r.map(|_| f) +} + impl Updater { pub fn new(client: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { let mut u = Updater { client: client.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), update_policy: update_policy, + fetch_handler: Mutex::new(None), this: VersionInfo::this(), this_fork: None, latest: None, @@ -140,6 +171,12 @@ impl Updater { }) } + fn fetch_done(&self, _r: Result) { + if let Ok(mut x) = self.fetch_handler.lock() { + *x = None; + } + } + pub fn tick(&mut self) { info!(target: "updater", "Current release is {}", self.this); @@ -157,6 +194,15 @@ impl Updater { "unreleased".into() } ); + if let Some(b) = latest.track.binary { + if let Ok(mut fetch_handler) = self.fetch_handler.lock() { + if fetch_handler.is_none() { + let c = self.client.clone(); + let f = move |r: Result| if let Some(c) = c.upgrade() { c.updater().as_ref().expect("updater exists; updater only owned by client; qed").fetch_done(r); }; + *fetch_handler = start_fetch(self.client.clone(), b, Box::new(f)).ok(); + } + } + } info!(target: "updater", "Fork: this/current/latest/latest-known: {}/#{}/#{}/#{}", match self.this_fork { Some(f) => format!("#{}", f), None => "unknown".into(), }, current_number, latest.track.fork, latest.fork); } } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 26db147442b..c0ad612d29b 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -103,6 +103,7 @@ extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; extern crate transient_hashmap; extern crate linked_hash_map; +extern crate ethcore_hash_fetch as fetch; #[macro_use] extern crate log; From 89f0bd714d16533105f180a51962193c5098fc36 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 09:42:50 +0000 Subject: [PATCH 101/382] test whole transitioning --- ethcore/src/engines/tendermint/mod.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index f57c56093b3..2777093eb72 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -98,7 +98,7 @@ impl Tendermint { builtins: builtins, step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), - height: AtomicUsize::new(0), + height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), @@ -386,6 +386,7 @@ impl Engine for Tendermint { }; if let Some(step) = next_step { + trace!(target: "poa", "handle_message: Transition triggered."); if let Err(io_err) = self.step_service.send_message(step) { warn!(target: "poa", "Could not proceed to next step {}.", io_err) } @@ -531,6 +532,14 @@ mod tests { ] } + fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option) -> Bytes { + let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash); + ::rlp::encode(&vec![ + tap.sign("0".sha3(), Some("0"), vote_info.sha3()).unwrap(), + tap.sign("1".sha3(), Some("1"), vote_info.sha3()).unwrap() + ]).to_vec() + } + fn insert_and_unlock(tap: &Arc, acc: &str) -> Address { let addr = tap.insert_account(acc.sha3(), acc).unwrap(); tap.unlock_account_permanently(addr, acc.into()).unwrap(); @@ -660,7 +669,7 @@ mod tests { #[test] #[should_panic] - fn prevote_step() { + fn step_transitioning() { ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); @@ -671,23 +680,25 @@ mod tests { let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); + let h = 1; + let r = 0; + // Propose - let (b, seal) = propose_default(&spec, v0.clone()); + let (b, mut seal) = propose_default(&spec, v0.clone()); let proposal = Some(b.header().bare_hash()); // Register IoHandler that panics on correct message. + seal[2] = precommit_signatures(&tap, h, r, b.header().bare_hash()); let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(Default::default(), seal)))).unwrap(); + io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(proposal, seal)))).unwrap(); engine.register_message_channel(io_service.channel()); - let h = 1; - let r = 0; - // Prevote. vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); } #[test] From 09c28806d6b4c5539ed2e372d139faf82b72df0e Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 11:47:33 +0000 Subject: [PATCH 102/382] proper test IoHandler --- ethcore/src/engines/tendermint/mod.rs | 29 +++++++++++++++++---------- ethcore/src/service.rs | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2777093eb72..04519aac04e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -532,11 +532,11 @@ mod tests { ] } - fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option) -> Bytes { + fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option, v1: H160, v2: H160) -> Bytes { let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash); ::rlp::encode(&vec![ - tap.sign("0".sha3(), Some("0"), vote_info.sha3()).unwrap(), - tap.sign("1".sha3(), Some("1"), vote_info.sha3()).unwrap() + H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()), + H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap()) ]).to_vec() } @@ -546,14 +546,17 @@ mod tests { addr } - struct TestIo(ClientIoMessage); + struct TestIo { + received: Mutex> + } + + impl TestIo { + fn new() -> Arc { Arc::new(TestIo { received: Mutex::new(None) }) } + } impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - let TestIo(ref expected) = *self; - if net_message == expected { - panic!() - } + *self.received.lock() = Some(net_message.clone()); } } @@ -687,10 +690,11 @@ mod tests { let (b, mut seal) = propose_default(&spec, v0.clone()); let proposal = Some(b.header().bare_hash()); - // Register IoHandler that panics on correct message. - seal[2] = precommit_signatures(&tap, h, r, b.header().bare_hash()); + // Register IoHandler remembers messages. + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(proposal, seal)))).unwrap(); + let test_io = TestIo::new(); + io_service.register_handler(test_io.clone()).unwrap(); engine.register_message_channel(io_service.channel()); // Prevote. @@ -699,6 +703,9 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + + ::std::thread::sleep(::std::time::Duration::from_millis(40)); + assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } #[test] diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 20a5587e0af..b3e1e0d51cd 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -32,7 +32,7 @@ use nanoipc; use client::BlockChainClient; /// Message type for external and internal events -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum ClientIoMessage { /// Best Block Hash in chain has been changed NewChainHead, From ef4ecce7bfc766abd2f6c9f407d2e7f06258e0ed Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 14:08:38 +0000 Subject: [PATCH 103/382] nicer vote counting + test --- ethcore/src/engines/tendermint/message.rs | 8 +- ethcore/src/engines/tendermint/mod.rs | 6 +- .../src/engines/tendermint/vote_collector.rs | 92 +++++++++++++++---- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index c8af7c9d8c8..7f6e6075414 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -61,8 +61,12 @@ impl ConsensusMessage { self.height == height && self.round == round && self.step == step } - pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { - self.height == height && self.round == round && self.block_hash == block_hash + pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option) -> bool { + self.height == h && self.round == r && self.step == s && self.block_hash == block_hash + } + + pub fn is_aligned(&self, m: &ConsensusMessage) -> bool { + self.is_block_hash(m.height, m.round, m.step, m.block_hash) } pub fn verify(&self) -> Result { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 04519aac04e..005e76011c3 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -266,8 +266,8 @@ impl Tendermint { } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - let aligned_votes = self.votes.aligned_votes(&message).len(); - self.is_above_threshold(aligned_votes) + let aligned_count = self.votes.count_aligned_votes(&message); + self.is_above_threshold(aligned_count) } } @@ -709,6 +709,7 @@ mod tests { } #[test] + #[ignore] fn precommit_step() { let (spec, tap) = setup(); let engine = spec.engine.clone(); @@ -722,6 +723,7 @@ mod tests { } #[test] + #[ignore] fn timeout_switching() { let tender = { let engine = Spec::new_test_tendermint().engine; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 0f4553502ce..4158398d104 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -42,34 +42,92 @@ impl VoteCollector { pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { let guard = self.votes.read(); - // Get only Propose and Precommits. - let mut correct_signatures = guard.keys() - .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature.clone()); - correct_signatures.next().map(|proposal| SealSignatures { - proposal: proposal, - votes: correct_signatures.collect() + let mut current_signatures = guard.keys() + .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, block_hash)); + current_signatures.next().map(|proposal| SealSignatures { + proposal: proposal.signature, + votes: current_signatures + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) + .take_while(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .map(|m| m.signature.clone()) + .collect() }) } - pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec { + pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize { let guard = self.votes.read(); guard.keys() - // Get only Propose and Precommits. - .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) - .cloned() - .collect() - } - - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { - self.seal_signatures(message.height, message.round, message.block_hash).map_or(Vec::new(), |s| s.votes) + .skip_while(|m| !m.is_aligned(message)) + // sorted by signature so might not be continuous + .filter(|m| m.is_aligned(message)) + .count() } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { self.votes .read() .keys() - .filter(|m| m.is_step(height, round, step)) + .skip_while(|m| !m.is_step(height, round, step)) + .take_while(|m| m.is_step(height, round, step)) .count() } } + +#[cfg(test)] +mod tests { + use util::*; + use super::*; + use super::super::{Height, Round, BlockHash, Step}; + use super::super::message::ConsensusMessage; + + fn simple_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { + collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, H160::default()) + } + + #[test] + fn seal_retrieval() { + let collector = VoteCollector::new(); + let bh = Some("1".sha3()); + let h = 1; + let r = 2; + let proposal = H520::random(); + simple_vote(&collector, proposal.clone(), h, r, Step::Propose, Some("0".sha3())); + let seal = SealSignatures { + proposal: proposal, + votes: Vec::new() + }; + collector.seal_signatures(h, r, bh); + } + + #[test] + fn count_votes() { + let collector = VoteCollector::new(); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + // good precommit + simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + // good precommit + simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + + assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4); + assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2); + + let message = ConsensusMessage { + signature: H520::default(), + height: 3, + round: 2, + step: Step::Prevote, + block_hash: Some("1".sha3()) + }; + assert_eq!(collector.count_aligned_votes(&message), 2); + } +} From 7d97ba5ee01582c1b79e720b744630d06d9fb026 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:23:39 +0000 Subject: [PATCH 104/382] seal sigs test --- .../src/engines/tendermint/vote_collector.rs | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 4158398d104..afc5427b834 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -26,6 +26,7 @@ pub struct VoteCollector { votes: RwLock> } +#[derive(Debug, PartialEq, Eq)] pub struct SealSignatures { pub proposal: H520, pub votes: Vec @@ -48,7 +49,7 @@ impl VoteCollector { proposal: proposal.signature, votes: current_signatures .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) - .take_while(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) .map(|m| m.signature.clone()) .collect() }) @@ -90,13 +91,41 @@ mod tests { let bh = Some("1".sha3()); let h = 1; let r = 2; - let proposal = H520::random(); - simple_vote(&collector, proposal.clone(), h, r, Step::Propose, Some("0".sha3())); + let mut signatures = Vec::new(); + for _ in 0..5 { + signatures.push(H520::random()); + } + // Wrong height proposal. + simple_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); + // Good proposal. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); + // Wrong block proposal. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); + // Wrong block precommit. + simple_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); + // Wrong round proposal. + simple_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); + // Prevote. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); + // Relevant precommit. + simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Replcated vote. + simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit. + simple_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong height precommit. + simple_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); + // Relevant precommit. + simple_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit, same signature. + simple_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong round precommit. + simple_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); let seal = SealSignatures { - proposal: proposal, - votes: Vec::new() + proposal: signatures[0], + votes: signatures[1..3].to_vec() }; - collector.seal_signatures(h, r, bh); + assert_eq!(seal, collector.seal_signatures(h, r, bh).unwrap()); } #[test] @@ -111,7 +140,9 @@ mod tests { // good prevote simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + let same_sig = H520::random(); + simple_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); // good precommit simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); // good prevote From 1326c6cf5abc0df932d4eeb0f60dcce6e2b1bd98 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:24:22 +0000 Subject: [PATCH 105/382] rebroadcast unseen messages --- ethcore/src/engines/tendermint/mod.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 005e76011c3..5cadf0aefc9 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -132,13 +132,11 @@ impl Tendermint { } } - fn broadcast_message(&self, block_hash: Option) { - if let Some(message) = self.generate_message(block_hash) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), - } + fn broadcast_message(&self, message: Bytes) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), } } } @@ -158,6 +156,12 @@ impl Tendermint { } } + fn generate_and_broadcast_message(&self, block_hash: Option) { + if let Some(message) = self.generate_message(block_hash) { + self.broadcast_message(message); + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { @@ -173,7 +177,7 @@ impl Tendermint { Some(ref m) => m.block_hash, None => None, }; - self.broadcast_message(block_hash) + self.generate_and_broadcast_message(block_hash) }, Step::Precommit => { trace!(target: "poa", "to_step: Transitioning to Precommit."); @@ -184,7 +188,7 @@ impl Tendermint { }, _ => None, }; - self.broadcast_message(block_hash); + self.generate_and_broadcast_message(block_hash); }, Step::Commit => { trace!(target: "poa", "to_step: Transitioning to Commit."); @@ -352,6 +356,7 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); + self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, @@ -704,7 +709,7 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(40)); + ::std::thread::sleep(::std::time::Duration::from_millis(5)); assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } From b454f7e3070d1f3498ee8fdb6dcb09f22ef35bf4 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:42:36 +0000 Subject: [PATCH 106/382] use Io queue for messages --- ethcore/src/client/client.rs | 12 ++++++++++-- ethcore/src/service.rs | 7 ++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e89bc89219c..b6976a933d4 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -562,6 +562,13 @@ impl Client { results.len() } + /// Handle messages from the IO queue + pub fn handle_queued_message(&self, message: &Bytes) { + if let Err(e) = self.engine.handle_message(UntrustedRlp::new(message)) { + trace!(target: "poa", "Invalid message received: {}", e); + } + } + /// Used by PoA to try sealing on period change. pub fn update_sealing(&self) { self.miner.update_sealing(self) @@ -1229,9 +1236,10 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } - // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - self.engine.handle_message(UntrustedRlp::new(&message)); + if let Err(e) = self.io_channel.lock().send(ClientIoMessage::NewMessage(message)) { + debug!("Ignoring the message, error queueing: {}", e); + } } fn signing_network_id(&self) -> Option { diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b3e1e0d51cd..c7dccaa896b 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -53,7 +53,9 @@ pub enum ClientIoMessage { /// Submit seal (useful for internal sealing). SubmitSeal(H256, Vec), /// Broadcast a message to the network. - BroadcastMessage(Bytes) + BroadcastMessage(Bytes), + /// New consensus message received. + NewMessage(Bytes) } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -234,6 +236,9 @@ impl IoHandler for ClientIoHandler { trace!(target: "poa", "message: BroadcastMessage"); self.client.broadcast_message(message.clone()); }, + ClientIoMessage::NewMessage(ref message) => { + self.client.handle_queued_message(message); + }, _ => {} // ignore other messages } } From e4ff614966df08a2a6d7800bb42103fb80fb0399 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:50:55 +0000 Subject: [PATCH 107/382] remove unused tracing --- ethcore/src/engines/tendermint/message.rs | 1 - ethcore/src/engines/tendermint/mod.rs | 19 +++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 7f6e6075414..3366510d26f 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -157,7 +157,6 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op // TODO: figure out whats wrong with nested list encoding let mut s = RlpStream::new_list(5); s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); - println!("{:?}, {:?}, {:?}, {:?}", &height, &round, &step, &block_hash); s.out() } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 5cadf0aefc9..f62bafb572f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -117,8 +117,8 @@ impl Tendermint { fn update_sealing(&self) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "update_sealing: UpdateSealing message sent."), + Err(err) => warn!(target: "poa", "update_sealing: Could not send a sealing message {}.", err), } } } @@ -126,8 +126,8 @@ impl Tendermint { fn submit_seal(&self, block_hash: H256, seal: Vec) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) { - Ok(_) => trace!(target: "poa", "timeout: SubmitSeal message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "submit_seal: SubmitSeal message sent."), + Err(err) => warn!(target: "poa", "submit_seal: Could not send a sealing message {}.", err), } } } @@ -135,8 +135,8 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), } } } @@ -527,7 +527,6 @@ mod tests { fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); - println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); vec![ @@ -609,12 +608,10 @@ mod tests { let mut header = Header::default(); let validator = insert_and_unlock(&tap, "0"); - println!("validator: {:?}", &validator); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - println!("{:?}", engine.verify_block_unordered(&header, None)); assert!(engine.verify_block_unordered(&header, None).is_ok()); let mut header = Header::default(); @@ -640,7 +637,6 @@ mod tests { let mut seal = proposal_seal(&tap, &header, 0); let voter = insert_and_unlock(&tap, "1"); - println!("voter: {:?}", &voter); let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); @@ -648,7 +644,6 @@ mod tests { header.set_seal(seal.clone()); - println!("{:?}", engine.verify_block_unordered(&header, None)); // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); @@ -709,7 +704,7 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(5)); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } From 0f1eefc00d24467f5e1b4e15c940b37f9e6f2a6e Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 18:37:00 +0000 Subject: [PATCH 108/382] disallow None seal sigs --- ethcore/src/engines/tendermint/vote_collector.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index afc5427b834..19cc0d1abca 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -41,15 +41,16 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { let guard = self.votes.read(); + let bh = Some(block_hash); let mut current_signatures = guard.keys() - .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, block_hash)); + .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); current_signatures.next().map(|proposal| SealSignatures { proposal: proposal.signature, votes: current_signatures - .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) - .filter(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) .map(|m| m.signature.clone()) .collect() }) @@ -125,7 +126,7 @@ mod tests { proposal: signatures[0], votes: signatures[1..3].to_vec() }; - assert_eq!(seal, collector.seal_signatures(h, r, bh).unwrap()); + assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap()); } #[test] From 61cf8b8b7e590aa7c24ae14305a18a17cc8f5e7b Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 18:58:15 +0000 Subject: [PATCH 109/382] vote propose --- ethcore/src/engines/tendermint/mod.rs | 37 +++++++++++++++------------ 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index f62bafb572f..c5009a7c6f8 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -194,14 +194,16 @@ impl Tendermint { trace!(target: "poa", "to_step: Transitioning to Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { - let seal = vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(&seal.proposal).to_vec(), - ::rlp::encode(&seal.votes).to_vec() - ]; - if let Some(block_hash) = *self.proposal.read() { + if let Some(block_hash) = *self.proposal.read() { + if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, block_hash) { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() + ]; self.submit_seal(block_hash, seal); + } else { + warn!(target: "poa", "Proposal was not found!"); } } *self.lock_change.write() = None; @@ -327,12 +329,16 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); - if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { + let height = header.number() as Height; + let round = self.round.load(AtomicOrdering::SeqCst); + let bh = Some(header.bare_hash()); + let vote_info = message_info_rlp(height, round, Step::Propose, bh); + if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()).map(H520::from) { + self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&H520::from(signature)).to_vec(), + ::rlp::encode(&signature).to_vec(), Vec::new() ]) } else { @@ -551,16 +557,16 @@ mod tests { } struct TestIo { - received: Mutex> + received: RwLock> } impl TestIo { - fn new() -> Arc { Arc::new(TestIo { received: Mutex::new(None) }) } + fn new() -> Arc { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) } } impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - *self.received.lock() = Some(net_message.clone()); + self.received.write().push(net_message.clone()); } } @@ -671,7 +677,6 @@ mod tests { } #[test] - #[should_panic] fn step_transitioning() { ::env_logger::init().unwrap(); let (spec, tap) = setup(); @@ -704,8 +709,8 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(50)); - assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + ::std::thread::sleep(::std::time::Duration::from_millis(500)); + assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); } #[test] From d0eab4a0d8be724f347a92cd17f4d48921cbd6c5 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 10:55:24 +0000 Subject: [PATCH 110/382] old message removal, avoid too many recoveries --- ethcore/src/engines/tendermint/mod.rs | 16 +++--- .../src/engines/tendermint/vote_collector.rs | 55 ++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5009a7c6f8..70e04e9291c 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -353,14 +353,14 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); - let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); - // TODO: Do not admit old messages. - if !self.is_authority(&sender) { - try!(Err(BlockError::NotAuthorized(sender))); - } - // Check if the message is known. - if self.votes.vote(message.clone(), sender).is_none() { + if self.votes.is_known(&message) { + let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); + if !self.is_authority(&sender) { + try!(Err(BlockError::NotAuthorized(sender))); + } + self.votes.vote(message.clone(), sender); + trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { @@ -381,6 +381,8 @@ impl Engine for Tendermint { self.increment_round(1); Some(Step::Propose) } else { + // Remove old messages. + self.votes.throw_out_old(&message); Some(Step::Commit) } }, diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 19cc0d1abca..758d6bcf52c 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -34,11 +34,40 @@ pub struct SealSignatures { impl VoteCollector { pub fn new() -> VoteCollector { - VoteCollector { votes: RwLock::new(BTreeMap::new()) } + let mut collector = BTreeMap::new(); + // Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted". + collector.insert(ConsensusMessage { + signature: H520::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: None + }, + Address::default()); + VoteCollector { votes: RwLock::new(collector) } } + /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - self.votes.write().insert(message, voter) + if { + let guard = self.votes.read(); + guard.keys().next().map_or(true, |oldest| &message > oldest) + } { + self.votes.write().insert(message, voter) + } else { + None + } + } + + pub fn is_known(&self, message: &ConsensusMessage) -> bool { + self.votes.read().contains_key(message) + } + + /// Throws out messages older than message, leaves message as marker for the oldest. + pub fn throw_out_old(&self, message: &ConsensusMessage) { + let mut guard = self.votes.write(); + let new_collector = guard.split_off(message); + *guard = new_collector; } pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { @@ -162,4 +191,26 @@ mod tests { }; assert_eq!(collector.count_aligned_votes(&message), 2); } + + #[test] + fn remove_old() { + let collector = VoteCollector::new(); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + + let message = ConsensusMessage { + signature: H520::default(), + height: 3, + round: 2, + step: Step::Precommit, + block_hash: Some("1".sha3()) + }; + collector.throw_out_old(&message); + assert_eq!(collector.votes.read().len(), 1); + } } From 49b953a9f488865a774933c59ceb659bacbbb8d7 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 11:18:40 +0000 Subject: [PATCH 111/382] order invariant seal equality --- ethcore/src/engines/tendermint/vote_collector.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 758d6bcf52c..51e51d15559 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -26,12 +26,21 @@ pub struct VoteCollector { votes: RwLock> } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub struct SealSignatures { pub proposal: H520, pub votes: Vec } +impl PartialEq for SealSignatures { + fn eq(&self, other: &SealSignatures) -> bool { + self.proposal == other.proposal + && self.votes.iter().collect::>() == other.votes.iter().collect::>() + } +} + +impl Eq for SealSignatures {} + impl VoteCollector { pub fn new() -> VoteCollector { let mut collector = BTreeMap::new(); From e784fa906e6e68637c9c3ef68d6c7c5037c983f6 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 12:20:38 +0000 Subject: [PATCH 112/382] warn on double vote --- .../src/engines/tendermint/vote_collector.rs | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 51e51d15559..923b3c9a778 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -104,12 +104,20 @@ impl VoteCollector { } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { - self.votes - .read() - .keys() - .skip_while(|m| !m.is_step(height, round, step)) - .take_while(|m| m.is_step(height, round, step)) - .count() + let guard = self.votes.read(); + let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step)); + let mut origins = HashSet::new(); + let mut n = 0; + for (message, origin) in current { + if message.is_step(height, round, step) { + if origins.insert(origin) { + n += 1; + } else { + warn!("count_step_votes: authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) + } + } + } + n } } @@ -120,8 +128,12 @@ mod tests { use super::super::{Height, Round, BlockHash, Step}; use super::super::message::ConsensusMessage; - fn simple_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { - collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, H160::default()) + fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { + full_vote(collector, signature, h, r, step, block_hash, H160::random()) + } + + fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option, address: Address) -> Option { + collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address) } #[test] @@ -135,31 +147,31 @@ mod tests { signatures.push(H520::random()); } // Wrong height proposal. - simple_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); + random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); // Good proposal. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); // Wrong block proposal. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); // Wrong block precommit. - simple_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); + random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); // Wrong round proposal. - simple_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); // Prevote. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); // Relevant precommit. - simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); // Replcated vote. - simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); // Wrong round precommit. - simple_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); // Wrong height precommit. - simple_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); // Relevant precommit. - simple_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); // Wrong round precommit, same signature. - simple_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); // Wrong round precommit. - simple_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); let seal = SealSignatures { proposal: signatures[0], votes: signatures[1..3].to_vec() @@ -171,22 +183,22 @@ mod tests { fn count_votes() { let collector = VoteCollector::new(); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); // good precommit - simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); // good prevote let same_sig = H520::random(); - simple_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); // good precommit - simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4); assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2); @@ -204,13 +216,13 @@ mod tests { #[test] fn remove_old() { let collector = VoteCollector::new(); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); let message = ConsensusMessage { signature: H520::default(), @@ -222,4 +234,12 @@ mod tests { collector.throw_out_old(&message); assert_eq!(collector.votes.read().len(), 1); } + + #[test] + fn malicious_authority() { + let collector = VoteCollector::new(); + full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default()); + full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default()); + assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1); + } } From 294e89e5c0269b98d56d5f4847e15815a70c1a55 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 12:51:27 +0000 Subject: [PATCH 113/382] use EngineError instead of BlockError --- ethcore/src/engines/authority_round.rs | 4 +-- ethcore/src/engines/mod.rs | 30 +++++++++++++------- ethcore/src/engines/tendermint/mod.rs | 38 ++++---------------------- ethcore/src/error.rs | 12 +------- 4 files changed, 29 insertions(+), 55 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index e55f1e5f90f..9987ffd1010 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::Engine; +use engines::{Engine, EngineError}; use header::Header; use error::{Error, BlockError}; use evm::Schedule; @@ -283,7 +283,7 @@ impl Engine for AuthorityRound { // Check if parent is from a previous step. if step == try!(header_step(parent)) { trace!(target: "poa", "Multiple blocks proposed for step {}.", step); - try!(Err(BlockError::DoubleVote(header.author().clone()))); + try!(Err(EngineError::DoubleVote(header.author().clone()))); } // Check difficulty is correct given the two timestamps. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ab50ee9c921..91557f8c36d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -48,18 +48,28 @@ use views::HeaderView; /// Voting errors. #[derive(Debug)] pub enum EngineError { - /// Voter is not in the voters set. - UnauthorisedVoter, - /// Message pertaining incorrect consensus step. - WrongStep, - /// Message pertaining unknown consensus step. - UnknownStep, + /// Signature does not belong to an authority. + NotAuthorized(H160), + /// The same author issued different votes at the same step. + DoubleVote(H160), + /// The received block is from an incorrect proposer. + NotProposer(Mismatch), /// Message was not expected. UnexpectedMessage, - /// Received a vote for a different proposal. - WrongVote, - /// Received message is from a different consensus round. - WrongRound +} + +impl fmt::Display for EngineError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::EngineError::*; + let msg = match *self { + DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), + NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), + NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), + UnexpectedMessage => "This Engine should not be fed messages.".into(), + }; + + f.write_fmt(format_args!("Engine error ({})", msg)) + } } /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 70e04e9291c..1168872c136 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -220,14 +220,14 @@ impl Tendermint { } /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> Result<(), BlockError> { + fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) } else { - Err(BlockError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) } } @@ -357,7 +357,7 @@ impl Engine for Tendermint { if self.votes.is_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { - try!(Err(BlockError::NotAuthorized(sender))); + try!(Err(EngineError::NotAuthorized(sender))); } self.votes.vote(message.clone(), sender); @@ -434,7 +434,7 @@ impl Engine for Tendermint { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { - try!(Err(BlockError::NotAuthorized(address))) + try!(Err(EngineError::NotAuthorized(address))) } signature_count += 1; @@ -494,7 +494,6 @@ mod tests { use rlp::{UntrustedRlp, View}; use io::{IoContext, IoHandler}; use block::*; - use state_db::StateDB; use error::{Error, BlockError}; use header::Header; use env_info::EnvInfo; @@ -629,7 +628,7 @@ mod tests { header.set_seal(seal); // Bad proposer. match engine.verify_block_unordered(&header, None) { - Err(Error::Block(BlockError::NotProposer(_))) => {}, + Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } } @@ -663,7 +662,7 @@ mod tests { // One good and one bad signature. match engine.verify_block_unordered(&header, None) { - Err(Error::Block(BlockError::NotAuthorized(_))) => {}, + Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), } } @@ -714,29 +713,4 @@ mod tests { ::std::thread::sleep(::std::time::Duration::from_millis(500)); assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); } - - #[test] - #[ignore] - fn precommit_step() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - - let not_authority_addr = insert_and_unlock(&tap, "101"); - let proposer_addr = insert_and_unlock(&tap, "0"); - propose_default(&spec, proposer_addr); - - let not_proposer_addr = insert_and_unlock(&tap, "1"); - - } - - #[test] - #[ignore] - fn timeout_switching() { - let tender = { - let engine = Spec::new_test_tendermint().engine; - Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) - }; - - println!("Waiting for timeout"); - } } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index cadb4fb1f17..5a0fc21673b 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -168,12 +168,6 @@ pub enum BlockError { UnknownParent(H256), /// Uncle parent given is unknown. UnknownUncleParent(H256), - /// The same author issued different votes at the same step. - DoubleVote(H160), - /// The received block is from an incorrect proposer. - NotProposer(Mismatch), - /// Signature does not belong to an authority. - NotAuthorized(H160) } impl fmt::Display for BlockError { @@ -207,9 +201,6 @@ 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 too many blocks.", address), - NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), - NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) @@ -294,8 +285,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Engine(ref err) => - f.write_fmt(format_args!("Bad vote: {:?}", err)), + Error::Engine(ref err) => err.fmt(f), Error::Ethkey(ref err) => err.fmt(f), } } From 7929a145e7e5d31eeb0d8e3652bf632364385677 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 14:55:54 +0000 Subject: [PATCH 114/382] fix deadlock --- ethcore/src/engines/tendermint/mod.rs | 9 +++--- .../src/engines/tendermint/vote_collector.rs | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1168872c136..28aae2b1003 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -354,14 +354,15 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); // Check if the message is known. - if self.votes.is_known(&message) { + if !self.votes.is_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { try!(Err(EngineError::NotAuthorized(sender))); } - self.votes.vote(message.clone(), sender); trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); + self.votes.vote(message.clone(), sender); + self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, @@ -381,8 +382,6 @@ impl Engine for Tendermint { self.increment_round(1); Some(Step::Propose) } else { - // Remove old messages. - self.votes.throw_out_old(&message); Some(Step::Commit) } }, @@ -504,7 +503,6 @@ mod tests { use spec::Spec; use engines::{Engine, EngineError}; use super::*; - use super::params::TendermintParams; use super::message::*; /// Accounts inserted with "1" and "2" are authorities. First proposer is "0". @@ -712,5 +710,6 @@ mod tests { ::std::thread::sleep(::std::time::Duration::from_millis(500)); assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); + println!("{:?}", *test_io.received.read()); } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 923b3c9a778..095b0fa372a 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -58,12 +58,14 @@ impl VoteCollector { /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - if { + let is_new = { let guard = self.votes.read(); guard.keys().next().map_or(true, |oldest| &message > oldest) - } { + }; + if is_new { self.votes.write().insert(message, voter) } else { + trace!(target: "poa", "vote: Old message ignored {:?}.", message); None } } @@ -80,17 +82,22 @@ impl VoteCollector { } pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { - let guard = self.votes.read(); let bh = Some(block_hash); - let mut current_signatures = guard.keys() - .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); - current_signatures.next().map(|proposal| SealSignatures { - proposal: proposal.signature, - votes: current_signatures - .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) - .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) - .map(|m| m.signature.clone()) - .collect() + let (proposal, votes) = { + let guard = self.votes.read(); + let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); + let proposal = current_signatures.next().cloned(); + let votes = current_signatures + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) + .cloned() + .collect::>(); + (proposal, votes) + }; + votes.last().map(|m| self.throw_out_old(m)); + proposal.map(|p| SealSignatures { + proposal: p.signature, + votes: votes.into_iter().map(|m| m.signature).collect() }) } From 95f81b2a2fcda7f5de812504be5118646790c4f0 Mon Sep 17 00:00:00 2001 From: arkpar Date: Tue, 29 Nov 2016 16:54:30 +0100 Subject: [PATCH 115/382] Moved consensus networking into Parity handler --- ethcore/src/client/client.rs | 2 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 4 +- sync/src/api.rs | 52 +------- sync/src/chain.rs | 28 ++++- sync/src/infinity.rs | 191 ------------------------------ sync/src/lib.rs | 1 - 7 files changed, 30 insertions(+), 250 deletions(-) delete mode 100644 sync/src/infinity.rs diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b6976a933d4..5ed51eee8e9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1236,7 +1236,7 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } - fn queue_infinity_message(&self, message: Bytes) { + fn queue_consensus_message(&self, message: Bytes) { if let Err(e) = self.io_channel.lock().send(ClientIoMessage::NewMessage(message)) { debug!("Ignoring the message, error queueing: {}", e); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index cf713cb4f70..6a9ab4b6898 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -651,7 +651,7 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } - fn queue_infinity_message(&self, _packet: Bytes) { + fn queue_consensus_message(&self, _packet: Bytes) { unimplemented!(); } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 61077ceb1c5..493f623f6aa 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -189,8 +189,8 @@ pub trait BlockChainClient : Sync + Send { /// Queue transactions for importing. fn queue_transactions(&self, transactions: Vec); - /// Queue packet - fn queue_infinity_message(&self, message: Bytes); + /// Queue conensus engine message. + fn queue_consensus_message(&self, message: Bytes); /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/sync/src/api.rs b/sync/src/api.rs index 8d7d08037f3..ee9031d0e95 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -28,19 +28,16 @@ use ethcore::snapshot::SnapshotService; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; use chain::{ChainSync, SyncStatus}; -use infinity::{InfinitySync}; use std::net::{SocketAddr, AddrParseError}; use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; +/// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; - /// Ethereum sync protocol -pub const ETH_PROTOCOL: [u8; 3] = *b"eth"; -/// Infinity protocol -pub const INF_PROTOCOL: [u8; 3] = *b"inf"; +pub const ETH_PROTOCOL: ProtocolId = *b"eth"; /// Sync configuration #[derive(Debug, Clone, Copy)] @@ -124,8 +121,6 @@ pub struct EthSync { network: NetworkService, /// Ethereum Protocol handler eth_handler: Arc, - /// Infinity Protocol handler - inf_handler: Arc, /// The main subprotocol name subprotocol_name: [u8; 3], /// Configuration @@ -135,7 +130,6 @@ pub struct EthSync { impl EthSync { /// Creates and register protocol with the network service pub fn new(config: SyncConfig, chain: Arc, snapshot_service: Arc, network_config: NetworkConfiguration) -> Result, NetworkError> { - let inf_sync = InfinitySync::new(&config, chain.clone()); let chain_sync = ChainSync::new(config, &*chain); let service = try!(NetworkService::new(try!(network_config.clone().into_basic()))); let sync = Arc::new(EthSync{ @@ -146,12 +140,6 @@ impl EthSync { snapshot_service: snapshot_service.clone(), overlay: RwLock::new(HashMap::new()), }), - inf_handler: Arc::new(InfProtocolHandler { - sync: RwLock::new(inf_sync), - chain: chain, - snapshot_service: snapshot_service, - overlay: RwLock::new(HashMap::new()), - }), subprotocol_name: config.subprotocol_name, config: network_config, }); @@ -232,37 +220,6 @@ impl NetworkProtocolHandler for SyncProtocolHandler { } } -struct InfProtocolHandler { - /// Shared blockchain client. - chain: Arc, - /// Shared snapshot service. - snapshot_service: Arc, - /// Sync strategy - sync: RwLock, - /// Chain overlay used to cache data such as fork block. - overlay: RwLock>, -} - -impl NetworkProtocolHandler for InfProtocolHandler { - fn initialize(&self, _io: &NetworkContext) { - } - - fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - InfinitySync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer, packet_id, data); - } - - fn connected(&self, io: &NetworkContext, peer: &PeerId) { - self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer); - } - - fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { - self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer); - } - - fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) { - } -} - impl ChainNotify for EthSync { fn new_blocks(&self, imported: Vec, @@ -295,9 +252,6 @@ impl ChainNotify for EthSync { // register the warp sync subprotocol self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); - // register the inf sync subprotocol - self.network.register_protocol(self.inf_handler.clone(), INF_PROTOCOL, ETH_PACKET_COUNT, &[1u8]) - .unwrap_or_else(|e| warn!("Error registering infinity protocol: {:?}", e)); } fn stop(&self) { @@ -308,7 +262,7 @@ impl ChainNotify for EthSync { fn broadcast(&self, message: Vec) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); - self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); + self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message.clone()); }); } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index e36dcffa3db..e317232bacc 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -112,6 +112,7 @@ type PacketDecodeError = DecoderError; const PROTOCOL_VERSION_63: u8 = 63; const PROTOCOL_VERSION_1: u8 = 1; +const PROTOCOL_VERSION_2: u8 = 2; const MAX_BODIES_TO_SEND: usize = 256; const MAX_HEADERS_TO_SEND: usize = 512; const MAX_NODE_DATA_TO_SEND: usize = 1024; @@ -148,8 +149,9 @@ const GET_SNAPSHOT_MANIFEST_PACKET: u8 = 0x11; const SNAPSHOT_MANIFEST_PACKET: u8 = 0x12; const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13; const SNAPSHOT_DATA_PACKET: u8 = 0x14; +const CONSENSUS_DATA_PACKET: u8 = 0x15; -pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x15; +pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x16; const MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD: usize = 3; @@ -607,7 +609,7 @@ impl ChainSync { trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); return Ok(()); } - if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63) { + if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1 && peer.protocol_version != PROTOCOL_VERSION_2) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63) { io.disable_peer(peer_id); trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version); return Ok(()); @@ -1416,8 +1418,9 @@ impl ChainSync { /// Send Status message fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), NetworkError> { - let warp_protocol = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer) != 0; - let protocol = if warp_protocol { PROTOCOL_VERSION_1 } else { PROTOCOL_VERSION_63 }; + let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); + let warp_protocol = warp_protocol_version != 0; + let protocol = if warp_protocol { warp_protocol_version } else { PROTOCOL_VERSION_63 }; trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let chain = io.chain().chain_info(); @@ -1663,7 +1666,7 @@ impl ChainSync { GET_SNAPSHOT_DATA_PACKET => ChainSync::return_rlp(io, &rlp, peer, ChainSync::return_snapshot_data, |e| format!("Error sending snapshot data: {:?}", e)), - + CONSENSUS_DATA_PACKET => ChainSync::on_consensus_packet(io, peer, &rlp), _ => { sync.write().on_packet(io, peer, packet_id, data); Ok(()) @@ -1996,6 +1999,21 @@ impl ChainSync { self.restart(io); } } + + /// Called when peer sends us new consensus packet + fn on_consensus_packet(io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + io.chain().queue_consensus_message(r.as_raw().to_vec()); + Ok(()) + } + + /// Broadcast consensus message to peers. + pub fn propagate_consensus_packet(&mut self, io: &mut SyncIo, packet: Bytes) { + let lucky_peers: Vec<_> = self.peers.iter().filter_map(|(id, p)| if p.protocol_version == PROTOCOL_VERSION_2 { Some(*id) } else { None }).collect(); + trace!(target: "sync", "Sending consensus packet to {:?}", lucky_peers); + for peer_id in lucky_peers { + self.send_packet(io, peer_id, CONSENSUS_DATA_PACKET, packet.clone()); + } + } } #[cfg(test)] diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs deleted file mode 100644 index 936060a1da7..00000000000 --- a/sync/src/infinity.rs +++ /dev/null @@ -1,191 +0,0 @@ -// 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 . - -/// Infinity networking - -use util::*; -use network::*; -use rlp::{UntrustedRlp, DecoderError, RlpStream, View, Stream}; -use ethcore::client::{BlockChainClient}; -use sync_io::SyncIo; -use super::SyncConfig; - -known_heap_size!(0, PeerInfo); - -type PacketDecodeError = DecoderError; - -const PROTOCOL_VERSION: u8 = 1u8; - -const STATUS_PACKET: u8 = 0x00; -const GENERIC_PACKET: u8 = 0x01; - -/// Syncing status and statistics -#[derive(Clone)] -pub struct NetworkStatus { - pub protocol_version: u8, - /// The underlying p2p network version. - pub network_id: usize, - /// Total number of connected peers - pub num_peers: usize, - /// Total number of active peers - pub num_active_peers: usize, -} - -#[derive(Clone)] -/// Inf peer information -struct PeerInfo { - /// inf protocol version - protocol_version: u32, - /// Peer chain genesis hash - genesis: H256, - /// Peer network id - network_id: usize, -} - -/// Infinity protocol handler. -pub struct InfinitySync { - chain: Arc, - /// All connected peers - peers: HashMap, - /// Network ID - network_id: usize, -} - -impl InfinitySync { - /// Create a new instance of syncing strategy. - pub fn new(config: &SyncConfig, chain: Arc) -> InfinitySync { - let mut sync = InfinitySync { - chain: chain, - peers: HashMap::new(), - network_id: config.network_id, - }; - sync.reset(); - sync - } - - /// @returns Synchonization status - pub fn _status(&self) -> NetworkStatus { - NetworkStatus { - protocol_version: 1, - network_id: self.network_id, - num_peers: self.peers.len(), - num_active_peers: 0, - } - } - - #[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()` - /// Reset sync. Clear all downloaded data but keep the queue - fn reset(&mut self) { - } - - /// Called by peer to report status - fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { - let peer = PeerInfo { - protocol_version: try!(r.val_at(0)), - network_id: try!(r.val_at(1)), - genesis: try!(r.val_at(2)), - }; - trace!(target: "inf", "New peer {} (protocol: {}, network: {:?}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.genesis); - if self.peers.contains_key(&peer_id) { - debug!(target: "inf", "Unexpected status packet from {}:{}", peer_id, io.peer_info(peer_id)); - return Ok(()); - } - let chain_info = io.chain().chain_info(); - if peer.genesis != chain_info.genesis_hash { - io.disable_peer(peer_id); - trace!(target: "inf", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, chain_info.genesis_hash, peer.genesis); - return Ok(()); - } - if peer.network_id != self.network_id { - io.disable_peer(peer_id); - trace!(target: "inf", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); - return Ok(()); - } - - self.peers.insert(peer_id.clone(), peer); - Ok(()) - } - - /// Called when a new peer is connected - pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) { - trace!(target: "inf", "== Connected {}: {}", peer, io.peer_info(peer)); - if let Err(e) = self.send_status(io) { - debug!(target:"inf", "Error sending status request: {:?}", e); - io.disable_peer(peer); - } - } - - /// Generic packet sender - fn send_packet(&mut self, sync: &mut SyncIo, peer_id: PeerId, packet_id: PacketId, packet: Bytes) { - if self.peers.contains_key(&peer_id) { - if let Err(e) = sync.send(peer_id, packet_id, packet) { - debug!(target:"inf", "Error sending request: {:?}", e); - sync.disable_peer(peer_id); - } - } - } - - /// Called when peer sends us new transactions - fn on_peer_packet(&mut self, _io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { - self.chain.queue_infinity_message(r.as_raw().to_vec()); - Ok(()) - } - - /// Called by peer when it is disconnecting - pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) { - trace!(target: "inf", "== Disconnecting {}: {}", peer, io.peer_info(peer)); - if self.peers.contains_key(&peer) { - debug!(target: "inf", "Disconnected {}", peer); - self.peers.remove(&peer); - } - } - - /// Send Status message - fn send_status(&mut self, io: &mut SyncIo) -> Result<(), NetworkError> { - let mut packet = RlpStream::new_list(5); - let chain = io.chain().chain_info(); - packet.append(&(PROTOCOL_VERSION as u32)); - packet.append(&self.network_id); - packet.append(&chain.total_difficulty); - packet.append(&chain.best_block_hash); - packet.append(&chain.genesis_hash); - io.respond(STATUS_PACKET, packet.out()) - } - - pub fn dispatch_packet(sync: &RwLock, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { - let rlp = UntrustedRlp::new(data); - match packet_id { - STATUS_PACKET => sync.write().on_peer_status(io, peer, &rlp).unwrap_or_else( - |e| trace!(target: "inf", "Error processing packet: {:?}", e)), - GENERIC_PACKET => sync.write().on_peer_packet(io, peer, &rlp).unwrap_or_else( - |e| warn!(target: "inf", "Error queueing packet: {:?}", e)), - p @ _ => trace!(target: "inf", "Unexpected packet {} from {}", p, peer), - }; - } - - pub fn propagate_packet(&mut self, io: &mut SyncIo, packet: Bytes) { - let lucky_peers: Vec<_> = self.peers.keys().cloned().collect(); - trace!(target: "inf", "Sending packets to {:?}", lucky_peers); - for peer_id in lucky_peers { - self.send_packet(io, peer_id, GENERIC_PACKET, packet.clone()); - } - } -} - -#[cfg(test)] -mod tests { -} - diff --git a/sync/src/lib.rs b/sync/src/lib.rs index d7c2080300f..2061e4e3a42 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -50,7 +50,6 @@ mod chain; mod blocks; mod block_sync; mod sync_io; -mod infinity; mod snapshot; mod transactions_stats; From 8e2aca719f1b563241d7d0e33ca31beabf940c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 10:16:18 +0100 Subject: [PATCH 116/382] Shared hash-fetch --- ethcore/hash-fetch/src/client.rs | 9 ++++- ethcore/src/client/client.rs | 14 +++++-- ethcore/src/client/fetch.rs | 49 +++++++++++++++++++++++++ ethcore/src/client/mod.rs | 1 + ethcore/src/client/updater.rs | 63 +++++++++++--------------------- 5 files changed, 88 insertions(+), 48 deletions(-) create mode 100644 ethcore/src/client/fetch.rs diff --git a/ethcore/hash-fetch/src/client.rs b/ethcore/hash-fetch/src/client.rs index f5d19afa503..272c952e09c 100644 --- a/ethcore/hash-fetch/src/client.rs +++ b/ethcore/hash-fetch/src/client.rs @@ -26,7 +26,7 @@ use fetch::{Fetch, FetchError, Client as FetchClient}; use urlhint::{ContractClient, URLHintContract, URLHint, URLHintResult}; /// API for fetching by hash. -pub trait HashFetch { +pub trait HashFetch: Send + Sync + 'static { /// Fetch hash-addressed content. /// Parameters: /// 1. `hash` - content hash @@ -42,7 +42,12 @@ pub enum Error { /// Hash could not be resolved to a valid content address. NoResolution, /// Downloaded content hash does not match. - HashMismatch { expected: H256, got: H256 }, + HashMismatch { + /// Expected hash + expected: H256, + /// Computed hash + got: H256, + }, /// IO Error while validating hash. IO(io::Error), /// Error during fetch. diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 200527222d0..26750624d31 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -72,6 +72,8 @@ use state_db::StateDB; use rand::OsRng; use client::updater::Updater; use client::registry::Registry; +use client::fetch::FetchHandler; +use fetch::{self, HashFetch}; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -154,6 +156,7 @@ pub struct Client { rng: Mutex, on_mode_change: Mutex>>, registrar: Mutex>, + fetch_service: Mutex>>, } impl Client { @@ -226,8 +229,6 @@ impl Client { accountdb: Default::default(), }; - - let client = Arc::new(Client { sleep_state: Mutex::new(SleepState::new(awake)), liveness: AtomicBool::new(awake), @@ -255,18 +256,23 @@ impl Client { rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), on_mode_change: Mutex::new(None), registrar: Mutex::new(None), + fetch_service: Mutex::new(None), }); if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { trace!(target: "client", "Found registrar at {}", reg_addr); let weak = Arc::downgrade(&client); + let fetch = Arc::new(fetch::Client::new(Arc::new(FetchHandler::new(weak.clone())))); let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); + // TODO [ToDr] The address might not be available when client is starting (but may be available later). + // Shouldn't this be moved inside the `Updater`? if let Ok(ops_addr) = registrar.get_address(&(&b"operations"[..]).sha3(), "A") { - if !ops_addr.is_zero() { + if !ops_addr.is_zero() { trace!(target: "client", "Found operations at {}", ops_addr); - *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), ops_addr, client.config.update_policy.clone())); + *client.updater.lock() = Some(Updater::new(Arc::downgrade(&client), Arc::downgrade(&fetch), ops_addr, client.config.update_policy.clone())); } } *client.registrar.lock() = Some(registrar); + *client.fetch_service.lock() = Some(fetch); } Ok(client) } diff --git a/ethcore/src/client/fetch.rs b/ethcore/src/client/fetch.rs new file mode 100644 index 00000000000..a94e22b9960 --- /dev/null +++ b/ethcore/src/client/fetch.rs @@ -0,0 +1,49 @@ +// 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 . + +use std::sync::Weak; +use std::str::FromStr; +use util::{Bytes, Address}; + +use client::{Client, BlockChainClient}; +use fetch; + +/// Client wrapper implementing `fetch::urlhint::ContractClient` +pub struct FetchHandler { + client: Weak, +} + +impl FetchHandler { + /// Creates new wrapper + pub fn new(client: Weak) -> Self { + FetchHandler { client: client } + } +} + +impl fetch::urlhint::ContractClient for FetchHandler { + fn registrar(&self) -> Result { + self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? + .additional_params() + .get("registrar") + .and_then(|s| Address::from_str(s).ok()) + .ok_or_else(|| "Registrar not available".into()) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? + .call_contract(address, data) + } +} diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 7759d08ef45..621a4c919cf 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -24,6 +24,7 @@ mod test_client; mod trace; mod client; mod updater; +mod fetch; pub use self::client::*; pub use self::config::{Mode, ClientConfig, UpdatePolicy, UpdateFilter, DatabaseCompactionProfile, BlockChainConfig, VMType}; diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 11bdc0b5c92..9417df7bfdb 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -14,11 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::{Mutex, Weak, Arc}; +use std::sync::{Weak, Arc}; use std::path::PathBuf; use util::misc::{VersionInfo, ReleaseTrack, platform}; -use std::str::FromStr; -use util::{Bytes, Address, H160, H256, FixedHash}; +use util::{Bytes, Address, H160, H256, FixedHash, Mutex}; use client::operations::Operations; use client::{Client, BlockChainClient, UpdatePolicy, BlockId}; use fetch::HashFetch; @@ -42,9 +41,10 @@ pub struct OperationsInfo { pub struct Updater { client: Weak, + fetch: Weak, operations: Operations, update_policy: UpdatePolicy, - fetch_handler: Mutex>, + fetch_handler: Mutex>, // These don't change pub this: VersionInfo, @@ -56,35 +56,15 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; -struct FetchHandler { - client: Weak, -} - -impl fetch::urlhint::ContractClient for FetchHandler { - fn registrar(&self) -> Result { - self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? - .additional_params() - .get("registrar") - .and_then(|s| Address::from_str(s).ok()) - .ok_or_else(|| "Registrar not available".into()) - } - - fn call(&self, address: Address, data: Bytes) -> Result { - self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? - .call_contract(address, data) - } -} - -fn start_fetch(client: Weak, hash: H256, on_done: Box) + Send>) -> Result { - let f = fetch::Client::new(Arc::new(FetchHandler { client: client, })); - let r = f.fetch(hash, on_done); - r.map(|_| f) +fn start_fetch(fetch: Arc, hash: H256, on_done: Box) + Send>) -> Result<(), fetch::Error> { + fetch.fetch(hash, on_done) } impl Updater { - pub fn new(client: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { + pub fn new(client: Weak, fetch: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { let mut u = Updater { client: client.clone(), + fetch: fetch.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), update_policy: update_policy, fetch_handler: Mutex::new(None), @@ -107,12 +87,12 @@ impl Updater { } /// Is the currently running client capable of supporting the current chain? - /// `Some` answer or `None` if information on the running client is not available. + /// `Some` answer or `None` if information on the running client is not available. pub fn is_capable(&self) -> Option { self.latest.as_ref().and_then(|latest| { self.this_fork.map(|this_fork| { let current_number = self.client.upgrade().map_or(0, |c| c.block_number(BlockId::Latest).unwrap_or(0)); - this_fork >= latest.fork || current_number < latest.fork + this_fork >= latest.fork || current_number < latest.fork }) }) } @@ -124,15 +104,15 @@ impl Updater { } /// Actually upgrades the client. Assumes that the binary has been downloaded. - /// @returns `true` on success. + /// @returns `true` on success. pub fn execute_upgrade(&mut self) -> bool { unimplemented!() } - /// Our version info. + /// Our version info. pub fn version_info(&self) -> &VersionInfo { &self.this } - /// Information gathered concerning the release. + /// Information gathered concerning the release. pub fn info(&self) -> &Option { &self.latest } fn collect_release_info(&self, release_id: &H256) -> Result { @@ -172,9 +152,7 @@ impl Updater { } fn fetch_done(&self, _r: Result) { - if let Ok(mut x) = self.fetch_handler.lock() { - *x = None; - } + *self.fetch_handler.lock() = None; } pub fn tick(&mut self) { @@ -186,7 +164,7 @@ impl Updater { if let Some(ref latest) = self.latest { info!(target: "updater", "Latest release in our track is v{} it is {}critical ({} binary is {})", latest.track.version, - if latest.track.is_critical {""} else {"non-"}, + if latest.track.is_critical {""} else {"non-"}, platform(), if let Some(ref b) = latest.track.binary { format!("{}", b) @@ -195,11 +173,12 @@ impl Updater { } ); if let Some(b) = latest.track.binary { - if let Ok(mut fetch_handler) = self.fetch_handler.lock() { - if fetch_handler.is_none() { - let c = self.client.clone(); - let f = move |r: Result| if let Some(c) = c.upgrade() { c.updater().as_ref().expect("updater exists; updater only owned by client; qed").fetch_done(r); }; - *fetch_handler = start_fetch(self.client.clone(), b, Box::new(f)).ok(); + let mut fetch_handler = self.fetch_handler.lock(); + if fetch_handler.is_none() { + let c = self.client.clone(); + let f = move |r: Result| if let Some(c) = c.upgrade() { c.updater().as_ref().expect("updater exists; updater only owned by client; qed").fetch_done(r); }; + if let Some(fetch) = self.fetch.clone().upgrade() { + *fetch_handler = start_fetch(fetch, b, Box::new(f)).ok(); } } } From ad440a12bdad91766657d039d3423a703bed32ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 13:47:14 +0100 Subject: [PATCH 117/382] EthMultiStore --- ethcore/src/account_provider.rs | 25 ++++- ethstore/src/dir/disk.rs | 5 +- ethstore/src/dir/geth.rs | 5 +- ethstore/src/dir/mod.rs | 3 +- ethstore/src/dir/parity.rs | 5 +- ethstore/src/ethstore.rs | 143 +++++++++++++++++++++++++-- ethstore/tests/util/transient_dir.rs | 5 +- 7 files changed, 168 insertions(+), 23 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 917ae8b8b02..e2ccd1d83da 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -163,12 +163,19 @@ impl AddressBook { } } +fn transient_sstore() -> Box { + Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed"))) +} + /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { + address_book: Mutex, unlocked: Mutex>, + /// Accounts on disk sstore: Box, - address_book: Mutex, + /// Accounts unlocked with rolling tokens + transient_sstore: Box, } impl AccountProvider { @@ -178,6 +185,7 @@ impl AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::new(sstore.local_path().into())), sstore: sstore, + transient_sstore: transient_sstore(), } } @@ -186,8 +194,8 @@ impl AccountProvider { AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), - sstore: Box::new(EthStore::open(Box::new(NullDir::default())) - .expect("NullDir load always succeeds; qed")) + sstore: transient_sstore(), + transient_sstore: transient_sstore(), } } @@ -433,4 +441,15 @@ mod tests { ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } + + #[test] + fn should_sign_and_return_token() { + let kp = Random.generate().unwrap(); + // given + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); + + // when + let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap(); + } } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 56b2c1ccb85..22093171e7e 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -18,7 +18,6 @@ use std::{fs, io}; use std::path::{PathBuf, Path}; use std::collections::HashMap; use time; -use ethkey::Address; use {json, SafeAccount, Error}; use json::UUID; use super::KeyDirectory; @@ -138,12 +137,12 @@ impl KeyDirectory for DiskDirectory { Ok(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address let to_remove = try!(self.files()) .into_iter() - .find(|&(_, ref account)| &account.address == address); + .find(|&(_, ref acc)| acc == account); // remove it match to_remove { diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index f63ebbea2cc..a5367f98d36 100644 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -89,7 +88,7 @@ impl KeyDirectory for GethDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index e29bd1ec4b0..0da4d71fbac 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::Address; use std::path::{PathBuf}; use {SafeAccount, Error}; @@ -30,7 +29,7 @@ pub enum DirectoryType { pub trait KeyDirectory: Send + Sync { fn load(&self) -> Result, Error>; fn insert(&self, account: SafeAccount) -> Result; - fn remove(&self, address: &Address) -> Result<(), Error>; + fn remove(&self, account: &SafeAccount) -> Result<(), Error>; fn path(&self) -> Option<&PathBuf> { None } } diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 7aa50c80bf6..c5d0057d886 100644 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -68,7 +67,7 @@ impl KeyDirectory for ParityDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4991c471477..f83e5fd3ac8 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -53,7 +53,7 @@ impl EthStore { fn save(&self, account: SafeAccount) -> Result<(), Error> { // save to file - let account = try!(self.dir.insert(account.clone())); + let account = try!(self.dir.insert(account)); // update cache let mut cache = self.cache.write(); @@ -124,13 +124,11 @@ impl SecretStore for EthStore { } fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { - let can_remove = { - let account = try!(self.get(address)); - account.check_password(password) - }; + let account = try!(self.get(address)); + let can_remove = account.check_password(password); if can_remove { - try!(self.dir.remove(address)); + try!(self.dir.remove(&account)); let mut cache = self.cache.write(); cache.remove(address); Ok(()) @@ -197,3 +195,136 @@ impl SecretStore for EthStore { import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet) } } + +/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address` +pub struct EthMultiStore { + dir: Box, + iterations: u32, + cache: RwLock>>, +} + +impl EthMultiStore { + + pub fn open(directory: Box) -> Result { + Self::open_with_iterations(directory, KEY_ITERATIONS as u32) + } + + pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { + let mut store = EthMultiStore { + dir: directory, + iterations: iterations, + cache: Default::default(), + }; + try!(store.reload_accounts()); + Ok(store) + } + + fn reload_accounts(&self) -> Result<(), Error> { + let mut cache = self.cache.write(); + let accounts = try!(self.dir.load()); + + let mut new_accounts = BTreeMap::new(); + for account in accounts { + let mut entry = new_accounts.entry(account.address.clone()).or_insert_with(Vec::new); + entry.push(account); + } + mem::replace(&mut *cache, new_accounts); + Ok(()) + } + + fn get(&self, address: &Address) -> Result, Error> { + { + let cache = self.cache.read(); + if let Some(accounts) = cache.get(address) { + if !accounts.is_empty() { + return Ok(accounts.clone()) + } + } + } + + try!(self.reload_accounts()); + let cache = self.cache.read(); + let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount)); + if accounts.is_empty() { + Err(Error::InvalidAccount) + } else { + Ok(accounts) + } + } + + pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> { + //save to file + let account = try!(self.dir.insert(account)); + + // update cache + let mut cache = self.cache.write(); + let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account); + Ok(()) + } + + pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + + // Remove from dir + try!(self.dir.remove(&account)); + + // Remove from cache + let mut cache = self.cache.write(); + let is_empty = { + let mut accounts = cache.get_mut(address).expect("Entry exists, because it was returned by `get`; qed"); + if let Some(position) = accounts.iter().position(|acc| acc == &account) { + accounts.remove(position); + } + accounts.is_empty() + }; + + if is_empty { + cache.remove(address); + } + + return Ok(()); + } + Err(Error::InvalidPassword) + } + + pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + for account in accounts { + // First remove + try!(self.remove_account(&address, old_password)); + // Then insert back with new password + let new_account = try!(account.change_password(old_password, new_password, self.iterations)); + try!(self.insert_account(new_account)); + } + Ok(()) + } + + pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + let accounts = try!(self.get(address)); + for account in accounts { + if account.check_password(password) { + return account.sign(password, message); + } + } + + Err(Error::InvalidPassword) + } + + pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let accounts = try!(self.get(account)); + for account in accounts { + if account.check_password(password) { + return account.decrypt(password, shared_mac, message); + } + } + Err(Error::InvalidPassword) + } +} + diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 23523e48c88..76010182e94 100644 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -18,7 +18,6 @@ use std::path::PathBuf; use std::{env, fs}; use rand::{Rng, OsRng}; use ethstore::dir::{KeyDirectory, DiskDirectory}; -use ethstore::ethkey::Address; use ethstore::{Error, SafeAccount}; pub fn random_dir() -> PathBuf { @@ -68,7 +67,7 @@ impl KeyDirectory for TransientDir { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } From 4ef5badceaccfd643bea3a6676bf3d667910c45d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 12:58:45 +0000 Subject: [PATCH 118/382] fix parity tests merge --- parity/cli/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index e5f66cfbea6..3f4f647a0a0 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -574,16 +574,11 @@ mod tests { flag_gas_floor_target: "4700000".into(), flag_gas_cap: "6283184".into(), flag_extra_data: Some("Parity".into()), -<<<<<<< HEAD - flag_tx_queue_size: 2048usize, - flag_tx_queue_gas: "auto".into(), -======= flag_tx_queue_size: 1024usize, flag_tx_queue_gas: "auto".into(), flag_tx_queue_strategy: "gas_factor".into(), flag_tx_queue_ban_count: 1u16, flag_tx_queue_ban_time: 180u16, ->>>>>>> parity/master flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -740,16 +735,11 @@ mod tests { price_update_period: Some("hourly".into()), gas_floor_target: None, gas_cap: None, -<<<<<<< HEAD - tx_queue_size: Some(2048), - tx_queue_gas: Some("auto".into()), -======= tx_queue_size: Some(1024), tx_queue_gas: Some("auto".into()), tx_queue_strategy: None, tx_queue_ban_count: None, tx_queue_ban_time: None, ->>>>>>> parity/master tx_gas_limit: None, tx_time_limit: None, extra_data: None, From 34d5017950b2a9f0609146a516303974b036d9ce Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 12:59:33 +0000 Subject: [PATCH 119/382] hold password in engine, add rpc --- ethcore/src/engines/mod.rs | 3 +++ ethcore/src/engines/tendermint/mod.rs | 21 ++++++++++++++++----- ethcore/src/miner/miner.rs | 15 ++++++++++++++- ethcore/src/miner/mod.rs | 3 +++ rpc/src/v1/impls/parity_set.rs | 6 ++++++ rpc/src/v1/tests/helpers/miner_service.rs | 9 +++++++++ rpc/src/v1/traits/parity_set.rs | 4 ++++ 7 files changed, 55 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 91557f8c36d..67e89b83418 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -187,6 +187,9 @@ pub trait Engine : Sync + Send { /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc) {} + /// Register an account which signs consensus messages. + fn set_signer(&self, _address: Address, _password: String) {} + /// Check if new block should be chosen as the one in chain. fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 28aae2b1003..452aed8aaff 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -66,6 +66,8 @@ pub struct Tendermint { step_service: IoService, /// Address to be used as authority. authority: RwLock
, + /// Password used for signing messages. + password: RwLock>, /// Blockchain height. height: AtomicUsize, /// Consensus round. @@ -98,6 +100,7 @@ impl Tendermint { builtins: builtins, step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), + password: RwLock::new(None), height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), @@ -144,7 +147,7 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { message_full_rlp( - |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), + |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).ok().map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read(), @@ -333,7 +336,7 @@ impl Engine for Tendermint { let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); let vote_info = message_info_rlp(height, round, Step::Propose, bh); - if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()).map(H520::from) { + if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -472,6 +475,11 @@ impl Engine for Tendermint { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn set_signer(&self, address: Address, password: String) { + *self.authority.write() = address; + *self.password.write() = Some(password); + } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); @@ -695,7 +703,6 @@ mod tests { let proposal = Some(b.header().bare_hash()); // Register IoHandler remembers messages. - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let io_service = IoService::::start().unwrap(); let test_io = TestIo::new(); io_service.register_handler(test_io.clone()).unwrap(); @@ -708,8 +715,12 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(500)); - assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); - println!("{:?}", *test_io.received.read()); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); + let first = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); + let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); + assert!(first ^ second); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 5aaa5b479fe..b1984dd430c 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -735,6 +735,19 @@ impl MinerService for Miner { *self.author.write() = author; } + fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + if self.seals_internally { + if let Some(ref ap) = self.accounts { + try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); + } + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + self.engine.set_signer(address, password); + } + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 1fb2244fd4f..89937e115d6 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -76,6 +76,9 @@ pub trait MinerService : Send + Sync { /// Set the author that we will seal blocks as. fn set_author(&self, author: Address); + /// Set info necessary to sign consensus messages. + fn set_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 47634d518bb..11bc48268a3 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -116,6 +116,12 @@ impl ParitySet for ParitySetClient where Ok(true) } + fn set_sealer(&self, address: H160, password: String) -> Result { + try!(self.active()); + try!(take_weak!(self.miner).set_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); + Ok(true) + } + fn set_transactions_limit(&self, limit: usize) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ad55faa7b83..87efd2425d0 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -25,6 +25,7 @@ use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; +use ethcore::account_provider::Error as AccountError; /// Test miner service. pub struct TestMinerService { @@ -43,6 +44,7 @@ pub struct TestMinerService { min_gas_price: RwLock, gas_range_target: RwLock<(U256, U256)>, + password: RwLock, author: RwLock
, extra_data: RwLock, limit: RwLock, @@ -61,6 +63,7 @@ impl Default for TestMinerService { min_gas_price: RwLock::new(U256::from(20_000_000)), gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))), author: RwLock::new(Address::zero()), + password: RwLock::new(String::new()), extra_data: RwLock::new(vec![1, 2, 3, 4]), limit: RwLock::new(1024), tx_gas_limit: RwLock::new(!U256::zero()), @@ -83,6 +86,12 @@ impl MinerService for TestMinerService { *self.author.write() = author; } + fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + *self.author.write() = address; + *self.password.write() = password; + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index c83eff02206..89d92e04371 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -44,6 +44,10 @@ build_rpc_trait! { #[rpc(name = "parity_setAuthor")] fn set_author(&self, H160) -> Result; + /// Sets account for signing consensus messages. + #[rpc(name = "parity_setSealer")] + fn set_sealer(&self, H160, String) -> Result; + /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] fn set_transactions_limit(&self, usize) -> Result; From 61f1699c0eea4fd8b345d2a5f0f8680aed256e7d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 13:39:28 +0000 Subject: [PATCH 120/382] fix merge --- sync/src/chain.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 609055422bb..e8c9afb372e 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1419,14 +1419,9 @@ impl ChainSync { /// Send Status message fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), NetworkError> { -<<<<<<< HEAD let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); let warp_protocol = warp_protocol_version != 0; let protocol = if warp_protocol { warp_protocol_version } else { PROTOCOL_VERSION_63 }; -======= - let warp_protocol = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer) != 0; - let protocol = if warp_protocol { PROTOCOL_VERSION_1 } else { io.eth_protocol_version(peer) }; ->>>>>>> master trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let chain = io.chain().chain_info(); From 6397556cbb5f240a2f77dc227d9d02fcf42a0240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 15:08:38 +0100 Subject: [PATCH 121/382] Sign with token support --- ethcore/src/account_provider.rs | 72 ++++++++++---- ethstore/src/ethstore.rs | 160 ++++++++++++++------------------ ethstore/src/lib.rs | 6 +- ethstore/src/random.rs | 8 +- ethstore/src/secret_store.rs | 15 ++- 5 files changed, 148 insertions(+), 113 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index e2ccd1d83da..311204bb19d 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -20,8 +20,8 @@ use std::{fs, fmt}; use std::collections::HashMap; use std::path::PathBuf; use std::time::{Instant, Duration}; -use util::{Mutex, RwLock}; -use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; +use util::{Mutex, RwLock, Itertools}; +use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; use ethstore::dir::{KeyDirectory}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; @@ -72,21 +72,35 @@ impl From for Error { #[derive(Default)] struct NullDir { - accounts: RwLock>, + accounts: RwLock>>, } impl KeyDirectory for NullDir { fn load(&self) -> Result, SSError> { - Ok(self.accounts.read().values().cloned().collect()) + Ok(self.accounts.read().values().cloned().flatten().collect()) } fn insert(&self, account: SafeAccount) -> Result { - self.accounts.write().insert(account.address.clone(), account.clone()); + self.accounts.write() + .entry(account.address.clone()) + .or_insert_with(Vec::new) + .push(account.clone()); Ok(account) } - fn remove(&self, address: &Address) -> Result<(), SSError> { - self.accounts.write().remove(address); + fn remove(&self, account: &SafeAccount) -> Result<(), SSError> { + let mut accounts = self.accounts.write(); + let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { + if let Some(position) = accounts.iter().position(|acc| acc == account) { + accounts.remove(position); + } + accounts.is_empty() + } else { + false + }; + if is_empty { + accounts.remove(&account.address); + } Ok(()) } } @@ -163,10 +177,12 @@ impl AddressBook { } } -fn transient_sstore() -> Box { - Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed"))) +fn transient_sstore() -> EthMultiStore { + EthMultiStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed") } +type AccountToken = String; + /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { @@ -175,7 +191,7 @@ pub struct AccountProvider { /// Accounts on disk sstore: Box, /// Accounts unlocked with rolling tokens - transient_sstore: Box, + transient_sstore: EthMultiStore, } impl AccountProvider { @@ -194,7 +210,7 @@ impl AccountProvider { AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), - sstore: transient_sstore(), + sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), transient_sstore: transient_sstore(), } } @@ -285,11 +301,8 @@ impl AccountProvider { /// Returns `true` if the password for `account` is `password`. `false` if not. pub fn test_password(&self, account: &Address, password: &str) -> Result { - match self.sstore.sign(account, password, &Default::default()) { - Ok(_) => Ok(true), - Err(SSError::InvalidPassword) => Ok(false), - Err(e) => Err(Error::SStore(e)), - } + self.sstore.test_password(account, password) + .map_err(Into::into) } /// Permanently removes an account. @@ -368,6 +381,26 @@ impl AccountProvider { Ok(try!(self.sstore.sign(&account, &password, &message))) } + /// Signs given message with supplied token. Returns a token to use in next signing within this session. + pub fn sign_with_token(&self, account: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), Error> { + let is_std_password = try!(self.sstore.test_password(&account, &token)); + + let new_token = random_string(16); + let signature = if is_std_password { + // Insert to transient store + try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + // sign + try!(self.sstore.sign(&account, &token, &message)) + } else { + // check transient store + try!(self.transient_sstore.change_password(&account, &token, &new_token)); + // and sign + try!(self.transient_sstore.sign(&account, &new_token, &message)) + }; + + Ok((signature, new_token)) + } + /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); @@ -450,6 +483,11 @@ mod tests { assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); // when - let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap(); + let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap(); + + // then + ap.sign_with_token(kp.address(), token.clone(), Default::default()) + .expect("First usage of token should be correct."); + assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail."); } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index f83e5fd3ac8..158a7c55ac1 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -22,7 +22,7 @@ use random::Random; use ethkey::{Signature, Address, Message, Secret, Public}; use dir::KeyDirectory; use account::SafeAccount; -use {Error, SecretStore}; +use {Error, SimpleSecretStore, SecretStore}; use json; use json::UUID; use parking_lot::RwLock; @@ -30,9 +30,7 @@ use presale::PresaleWallet; use import; pub struct EthStore { - dir: Box, - iterations: u32, - cache: RwLock>, + store: EthMultiStore, } impl EthStore { @@ -41,57 +39,46 @@ impl EthStore { } pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { - let accounts = try!(directory.load()); - let cache = accounts.into_iter().map(|account| (account.address.clone(), account)).collect(); - let store = EthStore { - dir: directory, - iterations: iterations, - cache: RwLock::new(cache), - }; - Ok(store) + Ok(EthStore { + store: try!(EthMultiStore::open_with_iterations(directory, iterations)), + }) } - fn save(&self, account: SafeAccount) -> Result<(), Error> { - // save to file - let account = try!(self.dir.insert(account)); + fn get(&self, address: &Address) -> Result { + let mut accounts = try!(self.store.get(address)).into_iter(); + accounts.next().ok_or(Error::InvalidAccount) + } +} - // update cache - let mut cache = self.cache.write(); - cache.insert(account.address.clone(), account); - Ok(()) +impl SimpleSecretStore for EthStore { + fn insert_account(&self, secret: Secret, password: &str) -> Result { + self.store.insert_account(secret, password) } - fn reload_accounts(&self) -> Result<(), Error> { - let mut cache = self.cache.write(); - let accounts = try!(self.dir.load()); - let new_accounts: BTreeMap<_, _> = accounts.into_iter().map(|account| (account.address.clone(), account)).collect(); - mem::replace(&mut *cache, new_accounts); - Ok(()) + fn accounts(&self) -> Result, Error> { + self.store.accounts() } - fn get(&self, address: &Address) -> Result { - { - let cache = self.cache.read(); - if let Some(account) = cache.get(address) { - return Ok(account.clone()) - } - } - try!(self.reload_accounts()); - let cache = self.cache.read(); - cache.get(address).cloned().ok_or(Error::InvalidAccount) + fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + self.store.change_password(address, old_password, new_password) } -} -impl SecretStore for EthStore { - fn insert_account(&self, secret: Secret, password: &str) -> Result { - let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); - let id: [u8; 16] = Random::random(); - let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); - let address = account.address.clone(); - try!(self.save(account)); - Ok(address) + fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + self.store.remove_account(address, password) + } + + fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + let account = try!(self.get(address)); + account.sign(password, message) + } + + fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let account = try!(self.get(account)); + account.decrypt(password, shared_mac, message) } +} +impl SecretStore for EthStore { fn import_presale(&self, json: &[u8], password: &str) -> Result { let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))); let wallet = PresaleWallet::from(json_wallet); @@ -105,46 +92,20 @@ impl SecretStore for EthStore { let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)); safe_account.address = try!(KeyPair::from_secret(secret)).address(); let address = safe_account.address.clone(); - try!(self.save(safe_account)); + try!(self.store.save(safe_account)); Ok(address) } - fn accounts(&self) -> Result, Error> { - try!(self.reload_accounts()); - Ok(self.cache.read().keys().cloned().collect()) - } - - fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { - // change password + fn test_password(&self, address: &Address, password: &str) -> Result { let account = try!(self.get(address)); - let account = try!(account.change_password(old_password, new_password, self.iterations)); - - // save to file - self.save(account) + Ok(account.check_password(password)) } - fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + fn copy_account(&self, new_store: &SimpleSecretStore, address: &Address, password: &str, new_password: &str) -> Result<(), Error> { let account = try!(self.get(address)); - let can_remove = account.check_password(password); - - if can_remove { - try!(self.dir.remove(&account)); - let mut cache = self.cache.write(); - cache.remove(address); - Ok(()) - } else { - Err(Error::InvalidPassword) - } - } - - fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { - let account = try!(self.get(address)); - account.sign(password, message) - } - - fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let account = try!(self.get(account)); - account.decrypt(password, shared_mac, message) + let secret = try!(account.crypto.secret(password)); + try!(new_store.insert_account(secret, new_password)); + Ok(()) } fn public(&self, account: &Address, password: &str) -> Result { @@ -172,7 +133,7 @@ impl SecretStore for EthStore { account.name = name; // save to file - self.save(account) + self.store.save(account) } fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> { @@ -180,11 +141,11 @@ impl SecretStore for EthStore { account.meta = meta; // save to file - self.save(account) + self.store.save(account) } fn local_path(&self) -> String { - self.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) + self.store.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) } fn list_geth_accounts(&self, testnet: bool) -> Vec
{ @@ -192,7 +153,7 @@ impl SecretStore for EthStore { } fn import_geth_accounts(&self, desired: Vec
, testnet: bool) -> Result, Error> { - import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet) + import::import_geth_accounts(&*self.store.dir, desired.into_iter().collect(), testnet) } } @@ -210,7 +171,7 @@ impl EthMultiStore { } pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { - let mut store = EthMultiStore { + let store = EthMultiStore { dir: directory, iterations: iterations, cache: Default::default(), @@ -252,7 +213,7 @@ impl EthMultiStore { } } - pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> { + fn save(&self, account: SafeAccount) -> Result<(), Error> { //save to file let account = try!(self.dir.insert(account)); @@ -263,7 +224,24 @@ impl EthMultiStore { Ok(()) } - pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { +} + +impl SimpleSecretStore for EthMultiStore { + fn insert_account(&self, secret: Secret, password: &str) -> Result { + let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); + let id: [u8; 16] = Random::random(); + let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); + let address = account.address.clone(); + try!(self.save(account)); + Ok(address) + } + + fn accounts(&self) -> Result, Error> { + try!(self.reload_accounts()); + Ok(self.cache.read().keys().cloned().collect()) + } + + fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { let accounts = try!(self.get(address)); for account in accounts { @@ -294,19 +272,19 @@ impl EthMultiStore { Err(Error::InvalidPassword) } - pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { let accounts = try!(self.get(address)); for account in accounts { // First remove try!(self.remove_account(&address, old_password)); // Then insert back with new password let new_account = try!(account.change_password(old_password, new_password, self.iterations)); - try!(self.insert_account(new_account)); + try!(self.save(new_account)); } Ok(()) } - pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { let accounts = try!(self.get(address)); for account in accounts { if account.check_password(password) { @@ -317,7 +295,7 @@ impl EthMultiStore { Err(Error::InvalidPassword) } - pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let accounts = try!(self.get(account)); for account in accounts { if account.check_password(password) { @@ -328,3 +306,9 @@ impl EthMultiStore { } } +#[cfg(test)] +mod tests { + fn should_have_some_tests() { + assert_eq!(true, false) + } +} diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index f8619ff192c..3fe56b7d31c 100644 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -50,8 +50,8 @@ mod secret_store; pub use self::account::SafeAccount; pub use self::error::Error; -pub use self::ethstore::EthStore; +pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::import::{import_accounts, read_geth_accounts}; pub use self::presale::PresaleWallet; -pub use self::secret_store::SecretStore; -pub use self::random::random_phrase; +pub use self::secret_store::{SimpleSecretStore, SecretStore}; +pub use self::random::{random_phrase, random_string}; diff --git a/ethstore/src/random.rs b/ethstore/src/random.rs index 954ec500f98..d98f2fcdfde 100644 --- a/ethstore/src/random.rs +++ b/ethstore/src/random.rs @@ -51,10 +51,16 @@ pub fn random_phrase(words: usize) -> String { .map(|s| s.to_owned()) .collect(); } - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng::new().expect("Not able to operate without random source."); (0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ") } +/// Generate a random string of given length. +pub fn random_string(length: usize) -> String { + let mut rng = OsRng::new().expect("Not able to operate without random source."); + rng.gen_ascii_chars().take(length).collect() +} + #[cfg(test)] mod tests { use super::random_phrase; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 06f38922b3d..b62189acab7 100644 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -18,18 +18,25 @@ use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::UUID; -pub trait SecretStore: Send + Sync { +pub trait SimpleSecretStore: Send + Sync { fn insert_account(&self, secret: Secret, password: &str) -> Result; - fn import_presale(&self, json: &[u8], password: &str) -> Result; - fn import_wallet(&self, json: &[u8], password: &str) -> Result; fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>; fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>; fn sign(&self, account: &Address, password: &str, message: &Message) -> Result; fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; - fn public(&self, account: &Address, password: &str) -> Result; fn accounts(&self) -> Result, Error>; +} + +pub trait SecretStore: SimpleSecretStore { + fn import_presale(&self, json: &[u8], password: &str) -> Result; + fn import_wallet(&self, json: &[u8], password: &str) -> Result; + fn copy_account(&self, new_store: &SimpleSecretStore, account: &Address, password: &str, new_password: &str) -> Result<(), Error>; + fn test_password(&self, account: &Address, password: &str) -> Result; + + fn public(&self, account: &Address, password: &str) -> Result; + fn uuid(&self, account: &Address) -> Result; fn name(&self, account: &Address) -> Result; fn meta(&self, account: &Address) -> Result; From 73e7908325e011eb1e2cb9afeb572d3ea0647308 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 14:30:21 +0000 Subject: [PATCH 122/382] test password registration --- ethcore/src/engines/tendermint/mod.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 452aed8aaff..b70c7a64ffb 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -498,6 +498,7 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; + use util::trie::TrieSpec; use rlp::{UntrustedRlp, View}; use io::{IoContext, IoHandler}; use block::*; @@ -524,7 +525,7 @@ mod tests { fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec) { let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); @@ -563,6 +564,12 @@ mod tests { addr } + fn insert_and_register(tap: &Arc, engine: &Arc, acc: &str) -> Address { + let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + engine.set_signer(addr.clone(), acc.into()); + addr + } + struct TestIo { received: RwLock> } @@ -676,8 +683,8 @@ mod tests { #[test] fn can_generate_seal() { let (spec, tap) = setup(); - - let proposer = insert_and_unlock(&tap, "0"); + + let proposer = insert_and_register(&tap, &spec.engine, "0"); let (b, seal) = propose_default(&spec, proposer); assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); @@ -685,12 +692,11 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); From ca87d2cde9dd19a33c980b1e16acd6af0c981798 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 15:01:43 +0000 Subject: [PATCH 123/382] add set_sealer rpc test --- rpc/src/v1/tests/eth.rs | 5 +++-- rpc/src/v1/tests/helpers/miner_service.rs | 3 ++- rpc/src/v1/tests/mocked/parity_set.rs | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 1ff5e17719e..b31cc8bdfe3 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -50,7 +50,7 @@ fn sync_provider() -> Arc { })) } -fn miner_service(spec: &Spec) -> Arc { +fn miner_service(spec: &Spec, accounts: Arc) -> Arc { Miner::new( MinerOptions { new_work_notify: vec![], @@ -69,6 +69,7 @@ fn miner_service(spec: &Spec) -> Arc { }, GasPricer::new_fixed(20_000_000_000u64.into()), &spec, + Some(accounts), ) } @@ -116,7 +117,7 @@ impl EthTester { let dir = RandomTempPath::new(); let account_provider = account_provider(); spec.engine.register_account_provider(account_provider.clone()); - let miner_service = miner_service(&spec); + let miner_service = miner_service(&spec, account_provider.clone()); let snapshot_service = snapshot_service(); let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 87efd2425d0..68caa137bab 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -41,10 +41,11 @@ pub struct TestMinerService { pub pending_receipts: Mutex>, /// Last nonces. pub last_nonces: RwLock>, + /// Password held by Engine. + pub password: RwLock, min_gas_price: RwLock, gas_range_target: RwLock<(U256, U256)>, - password: RwLock, author: RwLock
, extra_data: RwLock, limit: RwLock, diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 3202374a7b5..835dc0abef4 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -106,6 +106,23 @@ fn rpc_parity_set_author() { assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } +#[test] +fn rpc_parity_set_sealer() { + let miner = miner_service(); + let client = client_service(); + let network = network_service(); + let io = IoHandler::new(); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_setSealer", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); + assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(*miner.password.read(), "password".to_string()); +} + + #[test] fn rpc_parity_set_transactions_limit() { let miner = miner_service(); From c028f106b1924b6656bbd1f66d7fbdf836e219f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:11:41 +0100 Subject: [PATCH 124/382] RPC for confirming with token --- ethcore/src/account_provider.rs | 24 +++++- rpc/src/v1/helpers/dispatch.rs | 114 +++++++++++++++++++++++------ rpc/src/v1/impls/personal.rs | 4 +- rpc/src/v1/impls/signer.rs | 51 ++++++++----- rpc/src/v1/impls/signing.rs | 4 +- rpc/src/v1/impls/signing_unsafe.rs | 3 +- rpc/src/v1/tests/mocked/signer.rs | 46 ++++++++++++ rpc/src/v1/traits/signer.rs | 6 +- rpc/src/v1/types/confirmations.rs | 25 +++++++ rpc/src/v1/types/mod.rs.in | 4 +- 10 files changed, 232 insertions(+), 49 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 311204bb19d..637a20401a6 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -401,6 +401,28 @@ impl AccountProvider { Ok((signature, new_token)) } + /// Decrypts a message with given token. Returns a token to use in next operation for this account. + pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8]) + -> Result<(Vec, AccountToken), Error> + { + let is_std_password = try!(self.sstore.test_password(&account, &token)); + + let new_token = random_string(16); + let message = if is_std_password { + // Insert to transient store + try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + // decrypt + try!(self.sstore.decrypt(&account, &token, shared_mac, message)) + } else { + // check transient store + try!(self.transient_sstore.change_password(&account, &token, &new_token)); + // and decrypt + try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message)) + }; + + Ok((message, new_token)) + } + /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); @@ -477,8 +499,8 @@ mod tests { #[test] fn should_sign_and_return_token() { - let kp = Random.generate().unwrap(); // given + let kp = Random.generate().unwrap(); let ap = AccountProvider::transient_provider(); assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index a66bc816de1..3ac310be6d0 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt::Debug; use rlp; use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; @@ -37,46 +38,101 @@ use v1::types::{ pub const DEFAULT_MAC: [u8; 2] = [0, 0]; -pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: Option) -> Result +type AccountToken = String; + +#[derive(Debug, Clone, PartialEq)] +pub enum SignWith { + Nothing, + Password(String), + Token(AccountToken), +} + +#[derive(Debug)] +pub enum WithToken { + No(T), + Yes(T, AccountToken), +} + +impl WithToken { + pub fn map(self, f: F) -> WithToken where + S: Debug, + F: FnOnce(T) -> S, + { + match self { + WithToken::No(v) => WithToken::No(f(v)), + WithToken::Yes(v, token) => WithToken::Yes(f(v), token), + } + } + + pub fn into_value(self) -> T { + match self { + WithToken::No(v) => v, + WithToken::Yes(v, ..) => v, + } + } +} + +impl From<(T, AccountToken)> for WithToken { + fn from(tuple: (T, AccountToken)) -> Self { + WithToken::Yes(tuple.0, tuple.1) + } +} + +pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { match payload { ConfirmationPayload::SendTransaction(request) => { sign_and_dispatch(client, miner, accounts, request, pass) - .map(RpcH256::from) - .map(ConfirmationResponse::SendTransaction) + .map(|result| result + .map(RpcH256::from) + .map(ConfirmationResponse::SendTransaction) + ) }, ConfirmationPayload::SignTransaction(request) => { sign_no_dispatch(client, miner, accounts, request, pass) - .map(RpcRichRawTransaction::from) - .map(ConfirmationResponse::SignTransaction) + .map(|result| result + .map(RpcRichRawTransaction::from) + .map(ConfirmationResponse::SignTransaction) + ) }, ConfirmationPayload::Signature(address, hash) => { signature(accounts, address, hash, pass) - .map(RpcH520::from) - .map(ConfirmationResponse::Signature) + .map(|result| result + .map(RpcH520::from) + .map(ConfirmationResponse::Signature) + ) }, ConfirmationPayload::Decrypt(address, data) => { decrypt(accounts, address, data, pass) - .map(RpcBytes) - .map(ConfirmationResponse::Decrypt) + .map(|result| result + .map(RpcBytes) + .map(ConfirmationResponse::Decrypt) + ) }, } } -fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: Option) -> Result { - accounts.sign(address, password.clone(), hash).map_err(|e| match password { - Some(_) => errors::from_password_error(e), - None => errors::from_signing_error(e), +fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: SignWith) -> Result, Error> { + match password.clone() { + SignWith::Nothing => accounts.sign(address, None, hash).map(WithToken::No), + SignWith::Password(pass) => accounts.sign(address, Some(pass), hash).map(WithToken::No), + SignWith::Token(token) => accounts.sign_with_token(address, token, hash).map(Into::into), + }.map_err(|e| match password { + SignWith::Nothing => errors::from_signing_error(e), + _ => errors::from_password_error(e), }) } -fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: Option) -> Result { - accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg) - .map_err(|e| match password { - Some(_) => errors::from_password_error(e), - None => errors::from_signing_error(e), - }) +fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result, Error> { + match password.clone() { + SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No), + SignWith::Password(pass) => accounts.decrypt(address, Some(pass), &DEFAULT_MAC, &msg).map(WithToken::No), + SignWith::Token(token) => accounts.decrypt_with_token(address, token, &DEFAULT_MAC, &msg).map(Into::into), + }.map_err(|e| match password { + SignWith::Nothing => errors::from_signing_error(e), + _ => errors::from_password_error(e), + }) } pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result @@ -88,7 +144,7 @@ pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: Sig .map(|_| hash) } -pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result +pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { let network_id = client.signing_network_id(); @@ -110,20 +166,32 @@ pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, let hash = t.hash(network_id); let signature = try!(signature(accounts, address, hash, password)); - t.with_signature(signature, network_id) + signature.map(|sig| { + t.with_signature(sig, network_id) + }) }; Ok(signed_transaction) } -pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result +pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { let network_id = client.signing_network_id(); let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password)); + let (signed_transaction, token) = match signed_transaction { + WithToken::No(signed_transaction) => (signed_transaction, None), + WithToken::Yes(signed_transaction, token) => (signed_transaction, Some(token)), + }; + trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); - dispatch_transaction(&*client, &*miner, signed_transaction) + dispatch_transaction(&*client, &*miner, signed_transaction).map(|hash| { + match token { + Some(ref token) => WithToken::Yes(hash, token.clone()), + None => WithToken::No(hash), + } + }) } pub fn fill_optional_fields(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 1515e3fa1e1..48ed584cfa5 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -114,7 +114,7 @@ impl Personal for PersonalClient where C: MiningBl &*miner, &*accounts, request, - Some(password) - ).map(Into::into) + dispatch::SignWith::Password(password) + ).map(|v| v.into_value().into()) } } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 66f46ba0185..bdb34dab753 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -26,7 +26,7 @@ use ethcore::miner::MinerService; use jsonrpc_core::Error; use v1::traits::Signer; -use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes}; +use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::dispatch::{self, dispatch_transaction}; @@ -60,24 +60,10 @@ impl SignerClient where C: MiningBlockChainClient, take_weak!(self.client).keep_alive(); Ok(()) } -} - -impl Signer for SignerClient where C: MiningBlockChainClient, M: MinerService { - fn requests_to_confirm(&self) -> Result, Error> { - try!(self.active()); - let signer = take_weak!(self.signer); - - Ok(signer.requests() - .into_iter() - .map(Into::into) - .collect() - ) - } - - // TODO [ToDr] TransactionModification is redundant for some calls - // might be better to replace it in future - fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { + fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result where + F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, + { try!(self.active()); let id = id.into(); @@ -95,14 +81,41 @@ impl Signer for SignerClient where C: MiningBlockC }, _ => {}, } + let result = f(&*client, &*miner, &*accounts, payload); // Execute - let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); if let Ok(ref response) = result { signer.request_confirmed(id, Ok(response.clone())); } result }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) } +} + +impl Signer for SignerClient where C: MiningBlockChainClient, M: MinerService { + + fn requests_to_confirm(&self) -> Result, Error> { + try!(self.active()); + let signer = take_weak!(self.signer); + + Ok(signer.requests() + .into_iter() + .map(Into::into) + .collect() + ) + } + + // TODO [ToDr] TransactionModification is redundant for some calls + // might be better to replace it in future + fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { + self.confirm_internal(id, modification, move |client, miner, accounts, payload| { + dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Password(pass)) + .map(|v| v.into_value()) + }) + } + + fn confirm_request_with_token(&self, id: U256, modification: TransactionModification, token: String) -> Result { + unimplemented!() + } fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { try!(self.active()); diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 262e04dfb50..c055628c044 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -99,7 +99,9 @@ impl SigningQueueClient where let sender = payload.sender(); if accounts.is_unlocked(sender) { - return dispatch::execute(&*client, &*miner, &*accounts, payload, None).map(DispatchResult::Value); + return dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing) + .map(|v| v.into_value()) + .map(DispatchResult::Value); } take_weak!(self.signer).add_request(payload) diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 46ffe6dedbb..3f03f76097d 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -75,7 +75,8 @@ impl SigningUnsafeClient where let accounts = take_weak!(self.accounts); let payload = dispatch::from_rpc(payload, &*client, &*miner); - dispatch::execute(&*client, &*miner, &*accounts, payload, None) + dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing) + .map(|v| v.into_value()) } } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index e2ba580e037..c4df0260643 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -209,6 +209,52 @@ fn should_confirm_transaction_and_dispatch() { assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } +#[test] +fn should_confirm_transaction_with_token() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: address, + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + let (signature, token) = tester.accounts.sign_with_token(address, "test".into(), t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestWithToken", + "params":["0x1", {"gasPrice":"0x1000"}, ""#.to_owned() + &token + r#""], + "id":1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() + + format!("0x{:?}", t.hash()).as_ref() + + r#""token":""},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().len(), 1); +} + #[test] fn should_confirm_transaction_with_rlp() { // given diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs index eafa520d447..5014dc4a0a0 100644 --- a/rpc/src/v1/traits/signer.rs +++ b/rpc/src/v1/traits/signer.rs @@ -18,7 +18,7 @@ use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse}; +use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken}; build_rpc_trait! { @@ -33,6 +33,10 @@ build_rpc_trait! { #[rpc(name = "signer_confirmRequest")] fn confirm_request(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with token. + #[rpc(name = "signer_confirmRequestWithToken")] + fn confirm_request_with_token(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with already signed data. #[rpc(name = "signer_confirmRequestRaw")] fn confirm_request_raw(&self, U256, Bytes) -> Result; diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index bbbad83f3c1..795d2472691 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -101,6 +101,15 @@ impl Serialize for ConfirmationResponse { } } +/// Confirmation response with additional token for further requests +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct ConfirmationResponseWithToken { + /// Actual response + pub result: ConfirmationResponse, + /// New token + pub token: String, +} + /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub enum ConfirmationPayload { @@ -247,5 +256,21 @@ mod tests { gas_price: None, }); } + + #[test] + fn should_serialize_confirmation_response_with_token() { + // given + let response = ConfirmationResponseWithToken { + result: ConfirmationResponse::SendTransaction(H256::default()), + token: "test-token".into(), + }; + + // when + let res = serde_json::to_string(&response); + let expected = r#"{"result":"0x0000000000000000000000000000000000000000","token":"test-token"}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 55e8fd27bd6..6b6f01443b7 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -38,7 +38,9 @@ pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; -pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, TransactionModification, SignRequest, DecryptRequest, Either}; +pub use self::confirmations::{ + ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, TransactionModification, SignRequest, DecryptRequest, Either +}; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; From 022ccb5bcef2ef7eab2dafcad52188dcad76f6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:21:57 +0100 Subject: [PATCH 125/382] Fixing tests --- ethcore/src/account_provider.rs | 9 +++++---- ethstore/src/ethstore.rs | 3 +++ rpc/src/v1/types/confirmations.rs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 637a20401a6..da5992f0c49 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -81,10 +81,11 @@ impl KeyDirectory for NullDir { } fn insert(&self, account: SafeAccount) -> Result { - self.accounts.write() - .entry(account.address.clone()) - .or_insert_with(Vec::new) - .push(account.clone()); + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + // If the filename is the same we just need to replace the entry + accounts.retain(|acc| acc.filename != account.filename); + accounts.push(account.clone()); Ok(account) } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 158a7c55ac1..ec3cc16b921 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -220,6 +220,9 @@ impl EthMultiStore { // update cache let mut cache = self.cache.write(); let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + // TODO [ToDr] That is crappy way of overcoming set_name, set_meta, etc. + // Avoid cloning instead! + accounts.retain(|acc| acc.filename != account.filename); accounts.push(account); Ok(()) } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 795d2472691..a4284fa5c63 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -267,7 +267,7 @@ mod tests { // when let res = serde_json::to_string(&response); - let expected = r#"{"result":"0x0000000000000000000000000000000000000000","token":"test-token"}"#; + let expected = r#"{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","token":"test-token"}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); From 1d76bb7048739d7d65530425df20bc2aa339cd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:41:37 +0100 Subject: [PATCH 126/382] Fixing ethstore tests --- ethstore/src/ethstore.rs | 1 + ethstore/tests/api.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index ec3cc16b921..8cbce0f1c33 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -311,6 +311,7 @@ impl SimpleSecretStore for EthMultiStore { #[cfg(test)] mod tests { + #[test] fn should_have_some_tests() { assert_eq!(true, false) } diff --git a/ethstore/tests/api.rs b/ethstore/tests/api.rs index e1667607bd3..a26da41326b 100644 --- a/ethstore/tests/api.rs +++ b/ethstore/tests/api.rs @@ -19,7 +19,7 @@ extern crate ethstore; mod util; -use ethstore::{SecretStore, EthStore}; +use ethstore::{SecretStore, EthStore, SimpleSecretStore}; use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address}; use ethstore::dir::DiskDirectory; use util::TransientDir; From 84cf27c3efb797a031506a946c1acd5d4941149a Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 30 Nov 2016 16:47:20 +0100 Subject: [PATCH 127/382] Advertise protocol version 2 --- sync/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/api.rs b/sync/src/api.rs index ee9031d0e95..1bb8f267ea7 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -250,7 +250,7 @@ impl ChainNotify for EthSync { self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); // register the warp sync subprotocol - self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) + self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8, 2u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); } From bb8347477a81f40d81cb4bb4a417659d53f1b7ad Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:01:20 +0000 Subject: [PATCH 128/382] gossip when not enough votes --- ethcore/src/engines/tendermint/transition.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index ace5661b653..b20b97fba55 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -85,12 +85,23 @@ impl IoHandler for TransitionHandler { set_timeout(io, engine.our_params.timeouts.precommit); Some(Step::Precommit) }, + Step::Prevote => { + trace!(target: "poa", "timeout: Prevote timeout without enough votes."); + set_timeout(io, engine.our_params.timeouts.precommit); + Some(Step::Prevote) + }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); Some(Step::Propose) }, + Step::Precommit => { + trace!(target: "poa", "timeout: Precommit timeout without enough votes."); + set_timeout(io, engine.our_params.timeouts.propose); + engine.increment_round(1); + Some(Step::Propose) + }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); From d128c20dc21353b884fd1b8f8f27d4e5425a4c71 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:02:05 +0000 Subject: [PATCH 129/382] remove proposer_nonce --- ethcore/src/engines/tendermint/mod.rs | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b70c7a64ffb..e7eb486dc63 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -74,8 +74,6 @@ pub struct Tendermint { round: AtomicUsize, /// Consensus step. step: RwLock, - /// Used to swith proposer. - proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, /// Channel for updating the sealing. @@ -104,7 +102,6 @@ impl Tendermint { height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), message_channel: Mutex::new(None), account_provider: Mutex::new(None), @@ -180,7 +177,7 @@ impl Tendermint { Some(ref m) => m.block_hash, None => None, }; - self.generate_and_broadcast_message(block_hash) + self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { trace!(target: "poa", "to_step: Transitioning to Precommit."); @@ -225,7 +222,7 @@ impl Tendermint { /// Round proposer switching. fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; - let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); + let proposer_nonce = self.height.load(AtomicOrdering::SeqCst) + self.round.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) @@ -243,13 +240,11 @@ impl Tendermint { } fn increment_round(&self, n: Round) { - self.proposer_nonce.fetch_add(n, AtomicOrdering::SeqCst); self.round.fetch_add(n, AtomicOrdering::SeqCst); } fn reset_round(&self) { self.last_lock.store(0, AtomicOrdering::SeqCst); - self.proposer_nonce.fetch_add(1, AtomicOrdering::SeqCst); self.height.fetch_add(1, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); } @@ -627,7 +622,7 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "0"); + let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); @@ -684,7 +679,7 @@ mod tests { fn can_generate_seal() { let (spec, tap) = setup(); - let proposer = insert_and_register(&tap, &spec.engine, "0"); + let proposer = insert_and_register(&tap, &spec.engine, "1"); let (b, seal) = propose_default(&spec, proposer); assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); @@ -705,7 +700,7 @@ mod tests { let r = 0; // Propose - let (b, mut seal) = propose_default(&spec, v0.clone()); + let (b, mut seal) = propose_default(&spec, v1.clone()); let proposal = Some(b.header().bare_hash()); // Register IoHandler remembers messages. @@ -729,4 +724,19 @@ mod tests { let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); assert!(first ^ second); } + + #[test] + fn timeout_transitioning() { + ::env_logger::init().unwrap(); + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v = insert_and_register(&tap, &engine, "0"); + + ::std::thread::sleep(::std::time::Duration::from_millis(15000)); + println!("done"); + } } From dcb7e1e6387f1bdccdb47b4ad49a1dc181c57766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 17:05:31 +0100 Subject: [PATCH 130/382] Implementing RPC --- ethstore/tests/api.rs | 2 +- rpc/src/v1/helpers/dispatch.rs | 14 +++++++++++++- rpc/src/v1/impls/signer.rs | 21 ++++++++++++++------- rpc/src/v1/tests/mocked/signer.rs | 5 +++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ethstore/tests/api.rs b/ethstore/tests/api.rs index a26da41326b..dd9ec331180 100644 --- a/ethstore/tests/api.rs +++ b/ethstore/tests/api.rs @@ -19,7 +19,7 @@ extern crate ethstore; mod util; -use ethstore::{SecretStore, EthStore, SimpleSecretStore}; +use ethstore::{EthStore, SimpleSecretStore}; use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address}; use ethstore::dir::DiskDirectory; use util::TransientDir; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 3ac310be6d0..1a70f7e101b 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::fmt::Debug; +use std::ops::Deref; use rlp; use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; @@ -53,6 +54,17 @@ pub enum WithToken { Yes(T, AccountToken), } +impl Deref for WithToken { + type Target = T; + + fn deref(&self) -> &Self::Target { + match *self { + WithToken::No(ref v) => v, + WithToken::Yes(ref v, _) => v, + } + } +} + impl WithToken { pub fn map(self, f: F) -> WithToken where S: Debug, @@ -67,7 +79,7 @@ impl WithToken { pub fn into_value(self) -> T { match self { WithToken::No(v) => v, - WithToken::Yes(v, ..) => v, + WithToken::Yes(v, _) => v, } } } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index bdb34dab753..97d3de8093b 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -28,7 +28,7 @@ use jsonrpc_core::Error; use v1::traits::Signer; use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; -use v1::helpers::dispatch::{self, dispatch_transaction}; +use v1::helpers::dispatch::{self, dispatch_transaction, WithToken}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -61,8 +61,8 @@ impl SignerClient where C: MiningBlockChainClient, Ok(()) } - fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result where - F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, + fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result, Error> where + F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, Error>, { try!(self.active()); @@ -84,7 +84,7 @@ impl SignerClient where C: MiningBlockChainClient, let result = f(&*client, &*miner, &*accounts, payload); // Execute if let Ok(ref response) = result { - signer.request_confirmed(id, Ok(response.clone())); + signer.request_confirmed(id, Ok((*response).clone())); } result }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) @@ -109,12 +109,19 @@ impl Signer for SignerClient where C: MiningBlockC fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { self.confirm_internal(id, modification, move |client, miner, accounts, payload| { dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Password(pass)) - .map(|v| v.into_value()) - }) + }).map(|v| v.into_value()) } fn confirm_request_with_token(&self, id: U256, modification: TransactionModification, token: String) -> Result { - unimplemented!() + self.confirm_internal(id, modification, move |client, miner, accounts, payload| { + dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Token(token)) + }).and_then(|v| match v { + WithToken::No(_) => Err(errors::internal("Unexpected response without token.", "")), + WithToken::Yes(response, token) => Ok(ConfirmationResponseWithToken { + result: response, + token: token, + }), + }) } fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index c4df0260643..eb7fa6c4819 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -247,10 +247,11 @@ fn should_confirm_transaction_with_token() { }"#; let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + - r#""token":""},"id":1}"#; + r#"","token":""#; // then - assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + let result = tester.io.handle_request_sync(&request).unwrap(); + assert!(result.starts_with(&response), "Should return correct result. Expected: {:?}, Got: {:?}", response, result); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } From dbf82c2e98e8ce13d6ef358c93c822a2bd48ef03 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:40:16 +0000 Subject: [PATCH 131/382] fix tests --- ethcore/src/engines/tendermint/mod.rs | 9 +++++---- ethcore/src/engines/tendermint/transition.rs | 10 ++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e7eb486dc63..e8b4973fe15 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -647,7 +647,7 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let proposer = insert_and_unlock(&tap, "0"); + let proposer = insert_and_unlock(&tap, "1"); header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); @@ -687,6 +687,7 @@ mod tests { #[test] fn step_transitioning() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -717,11 +718,11 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(500)); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - let first = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()); + let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); + let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); assert!(first ^ second); } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index b20b97fba55..a7aebfd30f1 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -49,9 +49,9 @@ impl Default for TendermintTimeouts { fn default() -> Self { TendermintTimeouts { propose: Duration::milliseconds(2000), - prevote: Duration::milliseconds(1000), - precommit: Duration::milliseconds(1000), - commit: Duration::milliseconds(1000) + prevote: Duration::milliseconds(2000), + precommit: Duration::milliseconds(2000), + commit: Duration::milliseconds(2000) } } } @@ -99,8 +99,7 @@ impl IoHandler for TransitionHandler { Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.propose); - engine.increment_round(1); - Some(Step::Propose) + Some(Step::Precommit) }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); @@ -108,7 +107,6 @@ impl IoHandler for TransitionHandler { engine.reset_round(); Some(Step::Propose) }, - _ => None, }; if let Some(step) = next_step { From 66b4f1ac47e12b5c00283893cd3571910ad54587 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 14:10:22 +0000 Subject: [PATCH 132/382] remove unnecessary option --- ethcore/src/engines/tendermint/transition.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a7aebfd30f1..a494cfc0f2c 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -78,40 +78,38 @@ impl IoHandler for TransitionHandler { Step::Propose => { trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); - Some(Step::Prevote) + Step::Prevote }, Step::Prevote if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); - Some(Step::Precommit) + Step::Precommit }, Step::Prevote => { trace!(target: "poa", "timeout: Prevote timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.precommit); - Some(Step::Prevote) + set_timeout(io, engine.our_params.timeouts.prevote); + Step::Prevote }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); - Some(Step::Propose) + Step::Propose }, Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.propose); - Some(Step::Precommit) + set_timeout(io, engine.our_params.timeouts.precommit); + Step::Precommit }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); - Some(Step::Propose) + Step::Propose }, }; - if let Some(step) = next_step { - engine.to_step(step) - } + engine.to_step(next_step) } } } From 344999aaf7f3b7befc3f47ef8f7e7ff464550790 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 14:10:42 +0000 Subject: [PATCH 133/382] return signing failure error --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 3366510d26f..c57a194f24b 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -165,7 +165,7 @@ pub fn message_info_rlp_from_header(header: &Header) -> Result(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { +pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Result where F: FnOnce(H256) -> Result { let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { let mut s = RlpStream::new_list(2); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e8b4973fe15..cdbf1429ae2 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -138,18 +138,26 @@ impl Tendermint { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), } + } else { + warn!(target: "poa", "broadcast_message: No IoChannel available."); } } fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - message_full_rlp( - |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).ok().map(H520::from), + match message_full_rlp( + |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read(), block_hash - ) + ) { + Ok(m) => Some(m), + Err(e) => { + warn!(target: "poa", "generate_message: Could not sign the message {}", e); + None + }, + } } else { warn!(target: "poa", "generate_message: No AccountProvider available."); None @@ -166,12 +174,12 @@ impl Tendermint { *self.step.write() = step; match step { Step::Propose => { - trace!(target: "poa", "to_step: Transitioning to Propose."); + trace!(target: "poa", "to_step: Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { - trace!(target: "poa", "to_step: Transitioning to Prevote."); + trace!(target: "poa", "to_step: Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -180,7 +188,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { - trace!(target: "poa", "to_step: Transitioning to Precommit."); + trace!(target: "poa", "to_step: Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -191,7 +199,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - trace!(target: "poa", "to_step: Transitioning to Commit."); + trace!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { @@ -482,6 +490,7 @@ impl Engine for Tendermint { } fn register_message_channel(&self, message_channel: IoChannel) { + trace!(target: "poa", "register_message_channel: Register the IoChannel."); *self.message_channel.lock() = Some(message_channel); } From 4eca687bbbaf7f87b7c36ce7209ffd73f3ead94e Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 1 Dec 2016 15:48:56 +0100 Subject: [PATCH 134/382] Fixed network context --- sync/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/api.rs b/sync/src/api.rs index 1bb8f267ea7..b277132036f 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -260,7 +260,7 @@ impl ChainNotify for EthSync { } fn broadcast(&self, message: Vec) { - self.network.with_context(ETH_PROTOCOL, |context| { + self.network.with_context(WARP_SYNC_PROTOCOL_ID, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message.clone()); }); From e40e398eaa7caf4c9f0078cb58a0bd4d1be28262 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:19:40 +0000 Subject: [PATCH 135/382] clean up some tracing --- ethcore/src/service.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index c7dccaa896b..ea4a92bed02 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -224,21 +224,10 @@ impl IoHandler for ClientIoHandler { debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e); } }, - ClientIoMessage::UpdateSealing => { - trace!(target: "poa", "message: UpdateSealing"); - self.client.update_sealing(); - }, - ClientIoMessage::SubmitSeal(ref hash, ref seal) => { - trace!(target: "poa", "message: SubmitSeal"); - self.client.submit_seal(*hash, seal.clone()); - }, - ClientIoMessage::BroadcastMessage(ref message) => { - trace!(target: "poa", "message: BroadcastMessage"); - self.client.broadcast_message(message.clone()); - }, - ClientIoMessage::NewMessage(ref message) => { - self.client.handle_queued_message(message); - }, + ClientIoMessage::UpdateSealing => self.client.update_sealing(), + ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), + ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_message(message.clone()), + ClientIoMessage::NewMessage(ref message) => self.client.handle_queued_message(message), _ => {} // ignore other messages } } From 498b2fb0b1922a938511e2ed76c4c09bbae3c87a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:20:16 +0000 Subject: [PATCH 136/382] show verification error --- ethcore/src/miner/miner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 032e15d3121..54902839fbf 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -468,8 +468,8 @@ impl Miner { let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); - block.lock().try_seal(&*self.engine, seal).or_else(|_| { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); Err(None) }) } else { From 9290fdde857df28100e75fd092b904f93134ed49 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:21:51 +0000 Subject: [PATCH 137/382] fix tests --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index c57a194f24b..5cc37b5f433 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -215,7 +215,7 @@ mod tests { tap.unlock_account_permanently(addr, "0".into()).unwrap(); let raw_rlp = message_full_rlp( - |mh| tap.sign(addr, None, mh).ok().map(H520::from), + |mh| tap.sign(addr, None, mh).map(H520::from), 123, 2, Step::Precommit, diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index cdbf1429ae2..33c768007a7 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -134,6 +134,7 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { + trace!(target: "poa", "broadcast_message: {:?}", &message); match channel.send(ClientIoMessage::BroadcastMessage(message)) { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), @@ -154,7 +155,7 @@ impl Tendermint { ) { Ok(m) => Some(m), Err(e) => { - warn!(target: "poa", "generate_message: Could not sign the message {}", e); + trace!(target: "poa", "generate_message: Could not sign the message {}", e); None }, } @@ -325,9 +326,9 @@ impl Engine for Tendermint { *self.authority.write() = *block.header().author() } - /// Round proposer switching. + /// Should this node participate. fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_proposer(address).is_ok()) + Some(self.is_authority(address)) } /// Attempt to seal the block internally using all available signatures. @@ -335,17 +336,21 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); + // Only proposer can generate seal. + if self.is_proposer(author).is_err() { return None; } let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); let vote_info = message_info_rlp(height, round, Step::Propose, bh); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { + // Insert Propose vote. self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); + // Remember proposal for later seal submission. *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&signature).to_vec(), - Vec::new() + ::rlp::EMPTY_LIST_RLP.to_vec() ]) } else { warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); @@ -538,7 +543,7 @@ mod tests { (b, seal) } - fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Option { + fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } @@ -720,11 +725,11 @@ mod tests { engine.register_message_channel(io_service.channel()); // Prevote. - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(50)); From f1ef4a49355bd516d8587d8dce08f006c36fd88c Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 1 Dec 2016 19:11:36 +0100 Subject: [PATCH 138/382] Import sealed block immedtiatelly --- ethcore/src/miner/miner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 54902839fbf..1947cc9afb2 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -482,7 +482,7 @@ impl Miner { fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_block(sealed.rlp_bytes()).is_ok() { + if chain.import_sealed_block(sealed).is_ok() { trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } From 39ea703c693cc8561ad50504d15a6726cfff8a77 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 20:50:24 +0000 Subject: [PATCH 139/382] vote on message generation --- ethcore/src/engines/authority_round.rs | 7 ++++- ethcore/src/engines/tendermint/message.rs | 33 +++++++++++--------- ethcore/src/engines/tendermint/mod.rs | 37 ++++++++++++++--------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ade14a28d9c..dbfcbda3159 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -28,6 +28,8 @@ use spec::CommonParams; use engines::{Engine, EngineError}; use header::Header; use error::{Error, BlockError}; +use blockchain::extras::BlockDetails; +use views::HeaderView; use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; @@ -196,7 +198,6 @@ impl Engine for AuthorityRound { } 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; @@ -308,6 +309,10 @@ impl Engine for AuthorityRound { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.number() > best_header.number() + } + fn register_message_channel(&self, message_channel: IoChannel) { *self.message_channel.lock() = Some(message_channel); } diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 5cc37b5f433..06ee551baab 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -39,6 +39,16 @@ fn consensus_round(header: &Header) -> Result { } impl ConsensusMessage { + pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option) -> Self { + ConsensusMessage { + signature: signature, + height: height, + round: round, + step: step, + block_hash: block_hash + } + } + pub fn new_proposal(header: &Header) -> Result { Ok(ConsensusMessage { signature: try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()), @@ -156,7 +166,7 @@ impl Encodable for ConsensusMessage { pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { // TODO: figure out whats wrong with nested list encoding let mut s = RlpStream::new_list(5); - s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero)); s.out() } @@ -165,13 +175,10 @@ pub fn message_info_rlp_from_header(header: &Header) -> Result(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Result where F: FnOnce(H256) -> Result { - let vote_info = message_info_rlp(height, round, step, block_hash); - signer(vote_info.sha3()).map(|ref signature| { - let mut s = RlpStream::new_list(2); - s.append(signature).append_raw(&vote_info, 1); - s.out() - }) +pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes { + let mut s = RlpStream::new_list(2); + s.append(signature).append_raw(vote_info, 1); + s.out() } #[cfg(test)] @@ -214,13 +221,9 @@ mod tests { let addr = tap.insert_account("0".sha3(), "0").unwrap(); tap.unlock_account_permanently(addr, "0".into()).unwrap(); - let raw_rlp = message_full_rlp( - |mh| tap.sign(addr, None, mh).map(H520::from), - 123, - 2, - Step::Precommit, - Some(H256::default()) - ).unwrap(); + let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default())); + + let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi); let rlp = UntrustedRlp::new(&raw_rlp); let message: ConsensusMessage = rlp.as_val().unwrap(); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 33c768007a7..0cc8085d08e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -134,7 +134,6 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { - trace!(target: "poa", "broadcast_message: {:?}", &message); match channel.send(ClientIoMessage::BroadcastMessage(message)) { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), @@ -146,14 +145,17 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - match message_full_rlp( - |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).map(H520::from), - self.height.load(AtomicOrdering::SeqCst), - self.round.load(AtomicOrdering::SeqCst), - *self.step.read(), - block_hash - ) { - Ok(m) => Some(m), + let h = self.height.load(AtomicOrdering::SeqCst); + let r = self.round.load(AtomicOrdering::SeqCst); + let s = self.step.read(); + let vote_info = message_info_rlp(h, r, *s, block_hash); + let authority = self.authority.read(); + match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { + Ok(signature) => { + let message_rlp = message_full_rlp(&signature, &vote_info); + self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); + Some(message_rlp) + }, Err(e) => { trace!(target: "poa", "generate_message: Could not sign the message {}", e); None @@ -309,7 +311,6 @@ impl Engine for Tendermint { } 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; @@ -344,7 +345,7 @@ impl Engine for Tendermint { let vote_info = message_info_rlp(height, round, Step::Propose, bh); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { // Insert Propose vote. - self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); + self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -489,9 +490,14 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - new_signatures > best_signatures + trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); + if new_header.number() > best_header.number() { + true + } else { + let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + new_signatures > best_signatures + } } fn register_message_channel(&self, message_channel: IoChannel) { @@ -544,7 +550,8 @@ mod tests { } fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { - let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); + let mi = message_info_rlp(height, round, step, block_hash); + let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } From e76ead40d1e5de93dc8dafdeb1f338c99776b7b4 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 21:55:43 +0000 Subject: [PATCH 140/382] update tracing message --- ethcore/src/miner/miner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 1947cc9afb2..c819cef3f1a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -464,7 +464,7 @@ impl Miner { /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { - trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); + trace!(target: "miner", "seal_block_internally: attempting internal seal."); let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); From df1cce8e7f649328b93d8d1f186bc902d8698a1a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 21:56:38 +0000 Subject: [PATCH 141/382] simplify seal verification --- ethcore/src/engines/tendermint/message.rs | 8 ++-- ethcore/src/engines/tendermint/mod.rs | 37 +++++++++++++------ .../src/engines/tendermint/vote_collector.rs | 2 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 06ee551baab..40d84867947 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -85,6 +85,10 @@ impl ConsensusMessage { let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3())); Ok(public_to_address(&public_key)) } + + pub fn precommit_hash(&self) -> H256 { + message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3() + } } impl PartialOrd for ConsensusMessage { @@ -170,10 +174,6 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op s.out() } -pub fn message_info_rlp_from_header(header: &Header) -> Result { - let round = try!(consensus_round(header)); - Ok(message_info_rlp(header.number() as Height, round, Step::Precommit, Some(header.bare_hash()))) -} pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes { let mut s = RlpStream::new_list(2); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 0cc8085d08e..ae7d804d539 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -324,7 +324,7 @@ impl Engine for Tendermint { /// Get the address to be used as authority. fn on_new_block(&self, block: &mut ExecutedBlock) { - *self.authority.write() = *block.header().author() + *self.authority.write() = *block.header().author(); } /// Should this node participate. @@ -337,8 +337,11 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - // Only proposer can generate seal. - if self.is_proposer(author).is_err() { return None; } + // Only proposer can generate seal if None was generated. + if self.is_proposer(author).is_err() && self.proposal.read().is_none() { + return None; + } + let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); @@ -421,6 +424,7 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // TODO: check total length of the last field let seal_length = header.seal().len(); if seal_length == self.seal_fields() { Ok(()) @@ -431,28 +435,37 @@ impl Engine for Tendermint { } } - /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = try!(ConsensusMessage::new_proposal(header)); let proposer = try!(proposal.verify()); - try!(self.is_proposer(&proposer)); - self.votes.vote(proposal, proposer); - let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); - + if !self.is_authority(&proposer) { + try!(Err(EngineError::NotAuthorized(proposer))) + } + let precommit_hash = proposal.precommit_hash(); // TODO: use addresses recovered during precommit vote let mut signature_count = 0; + let mut origins = HashSet::new(); for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); - let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); + let address = public_to_address(&try!(recover(&signature.into(), &precommit_hash))); if !self.our_params.authorities.contains(&address) { try!(Err(EngineError::NotAuthorized(address))) } - signature_count += 1; + if origins.insert(address) { + signature_count += 1; + } else { + warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address) + } } - if signature_count > self.our_params.authority_n { - try!(Err(BlockError::InvalidSealArity(Mismatch { expected: self.our_params.authority_n, found: signature_count }))) + + // Check if its just a proposal if there is not enough precommits. + if !self.is_above_threshold(signature_count) { + try!(self.is_proposer(&proposer)); + *self.proposal.write() = proposal.block_hash.clone(); + self.votes.vote(proposal, proposer); } + Ok(()) } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 095b0fa372a..93052a44a3e 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -120,7 +120,7 @@ impl VoteCollector { if origins.insert(origin) { n += 1; } else { - warn!("count_step_votes: authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) + warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) } } } From f0e9eae244f5d838388adfe4bfc40cd17031c17a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 22:07:18 +0000 Subject: [PATCH 142/382] remove difficulty check --- ethcore/src/engines/authority_round.rs | 4 ---- ethcore/src/engines/tendermint/mod.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dbfcbda3159..6bce4cf833b 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -287,10 +287,6 @@ impl Engine for AuthorityRound { try!(Err(EngineError::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; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index ae7d804d539..e35dc6749a1 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -475,10 +475,6 @@ impl Engine for Tendermint { 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().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; From 2c8c09059862f740e3eba02e184d51111b8f6a51 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:30:43 +0000 Subject: [PATCH 143/382] stricter size verification --- ethcore/src/engines/tendermint/mod.rs | 95 ++++++++++++++++++++------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e35dc6749a1..c5149f55549 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -56,8 +56,6 @@ pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; -type Signatures = Vec; - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, @@ -153,6 +151,7 @@ impl Tendermint { match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { Ok(signature) => { let message_rlp = message_full_rlp(&signature, &vote_info); + // TODO: memoize the rlp for consecutive broadcasts self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); Some(message_rlp) }, @@ -173,16 +172,22 @@ impl Tendermint { } } + fn broadcast_old_messages(&self) { + if let Some(ref lc) = *self.lock_change.read() { + for m in self.votes.get_older_than(lc).into_iter() { + self.broadcast_message(m); + } + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => { - trace!(target: "poa", "to_step: Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { - trace!(target: "poa", "to_step: Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -191,7 +196,6 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { - trace!(target: "poa", "to_step: Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -202,7 +206,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - trace!(target: "poa", "to_step: Commit."); + debug!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { @@ -251,10 +255,12 @@ impl Tendermint { } fn increment_round(&self, n: Round) { + debug!(target: "poa", "increment_round: New round."); self.round.fetch_add(n, AtomicOrdering::SeqCst); } fn reset_round(&self) { + debug!(target: "poa", "reset_round: New height."); self.last_lock.store(0, AtomicOrdering::SeqCst); self.height.fetch_add(1, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); @@ -386,7 +392,7 @@ impl Engine for Tendermint { if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - trace!(target: "poa", "handle_message: Lock change."); + debug!(target: "poa", "handle_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -424,15 +430,24 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - // TODO: check total length of the last field let seal_length = header.seal().len(); if seal_length == self.seal_fields() { - Ok(()) + let signatures_len = header.seal()[2].len(); + if signatures_len >= 1 { + Ok(()) + } else { + Err(From::from(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(1), + max: None, + found: signatures_len + }))) + } } else { Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.seal_fields(), found: seal_length } ))) } + } fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -441,11 +456,13 @@ impl Engine for Tendermint { if !self.is_authority(&proposer) { try!(Err(EngineError::NotAuthorized(proposer))) } + let precommit_hash = proposal.precommit_hash(); // TODO: use addresses recovered during precommit vote + let ref signatures_field = header.seal()[2]; let mut signature_count = 0; let mut origins = HashSet::new(); - for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { + for rlp in UntrustedRlp::new(signatures_field).iter() { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &precommit_hash))); if !self.our_params.authorities.contains(&address) { @@ -455,12 +472,22 @@ impl Engine for Tendermint { if origins.insert(address) { signature_count += 1; } else { - warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address) + warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); + try!(Err(BlockError::InvalidSeal)); } } - // Check if its just a proposal if there is not enough precommits. + // Check if its a proposal if there is not enough precommits. if !self.is_above_threshold(signature_count) { + let signatures_len = signatures_field.len(); + // Proposal has to have an empty signature list. + if signatures_len != 1 { + try!(Err(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(1), + max: Some(1), + found: signatures_len + }))); + } try!(self.is_proposer(&proposer)); *self.proposal.write() = proposal.block_hash.clone(); self.votes.vote(proposal, proposer); @@ -470,17 +497,28 @@ impl Engine for Tendermint { } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> 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() }))); + try!(Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } 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() }))); + try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); } + + // Commit is longer than empty signature list. + let parent_signature_len = parent.seal()[2].len(); + if parent_signature_len < 1 { + try!(Err(EngineError::BadSealFieldSize(OutOfBounds { + // One signature. + min: Some(136), + max: None, + found: parent_signature_len + }))); + } + Ok(()) } @@ -571,7 +609,7 @@ mod tests { vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), - Vec::new() + ::rlp::EMPTY_LIST_RLP.to_vec() ] } @@ -681,21 +719,30 @@ mod tests { header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - let voter = insert_and_unlock(&tap, "1"); let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); - let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); + let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); + + seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); + header.set_seal(seal.clone()); - seal[2] = ::rlp::encode(&vec![H520::from(signature.clone())]).to_vec(); + // One good signature is not enough. + match engine.verify_block_unordered(&header, None) { + Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, + _ => panic!(), + } + let voter = insert_and_unlock(&tap, "0"); + let signature0 = tap.sign(voter, None, vote_info.sha3()).unwrap(); + + seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); header.set_seal(seal.clone()); - // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); - seal[2] = ::rlp::encode(&vec![H520::from(signature), H520::from(bad_signature)]).to_vec(); + seal[2] = ::rlp::encode(&vec![H520::from(signature1), H520::from(bad_signature)]).to_vec(); header.set_seal(seal); // One good and one bad signature. @@ -717,7 +764,6 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -758,16 +804,17 @@ mod tests { #[test] fn timeout_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + println!("{:?}", ::rlp::EMPTY_LIST_RLP.to_vec().len()); + println!("{:?}", ::rlp::encode(&vec![H520::default()]).to_vec().len()); let v = insert_and_register(&tap, &engine, "0"); - ::std::thread::sleep(::std::time::Duration::from_millis(15000)); println!("done"); + } } From 0eb55cbd4d716dea0f6463f16253fa8d89a210fc Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:31:28 +0000 Subject: [PATCH 144/382] update message test --- ethcore/src/engines/tendermint/message.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 40d84867947..5eae139d54a 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -254,13 +254,16 @@ mod tests { #[test] fn message_info_from_header() { - let mut header = Header::default(); - let seal = vec![ - ::rlp::encode(&0u8).to_vec(), - ::rlp::encode(&H520::default()).to_vec(), - Vec::new() - ]; - header.set_seal(seal); - assert_eq!(message_info_rlp_from_header(&header).unwrap().to_vec(), vec![228, 128, 128, 2, 160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]); + let header = Header::default(); + let pro = ConsensusMessage { + signature: Default::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: Some(header.bare_hash()) + }; + let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); + + assert_eq!(pro.precommit_hash(), pre.sha3()); } } From e0f2fac4413822bfa869298b0569f487a79f90f1 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:32:00 +0000 Subject: [PATCH 145/382] new error type --- ethcore/src/engines/authority_round.rs | 1 - ethcore/src/engines/mod.rs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 6bce4cf833b..f59fd0b17f6 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -275,7 +275,6 @@ impl Engine for AuthorityRound { } 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() }))); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 67e89b83418..f157cc50508 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -56,6 +56,8 @@ pub enum EngineError { NotProposer(Mismatch), /// Message was not expected. UnexpectedMessage, + /// Seal field has an unexpected size. + BadSealFieldSize(OutOfBounds), } impl fmt::Display for EngineError { @@ -66,6 +68,7 @@ impl fmt::Display for EngineError { NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), UnexpectedMessage => "This Engine should not be fed messages.".into(), + BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob), }; f.write_fmt(format_args!("Engine error ({})", msg)) From 91099f62c90122f320be44958df2f3f29cbee8ae Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:37:49 +0000 Subject: [PATCH 146/382] add more gossip if step is stuck --- ethcore/src/engines/tendermint/transition.rs | 18 +++++++++++------- .../src/engines/tendermint/vote_collector.rs | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a494cfc0f2c..1ca230b3ec8 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -78,38 +78,42 @@ impl IoHandler for TransitionHandler { Step::Propose => { trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); - Step::Prevote + Some(Step::Prevote) }, Step::Prevote if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); - Step::Precommit + Some(Step::Precommit) }, Step::Prevote => { trace!(target: "poa", "timeout: Prevote timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.prevote); - Step::Prevote + engine.broadcast_old_messages(); + None }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); - Step::Propose + Some(Step::Propose) }, Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.precommit); - Step::Precommit + engine.broadcast_old_messages(); + None }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); - Step::Propose + Some(Step::Propose) }, }; - engine.to_step(next_step) + if let Some(s) = next_step { + engine.to_step(s) + } } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 93052a44a3e..8bb271a3558 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -126,6 +126,11 @@ impl VoteCollector { } n } + + pub fn get_older_than(&self, message: &ConsensusMessage) -> Vec { + let guard = self.votes.read(); + guard.keys().take_while(|m| *m <= message).map(|m| ::rlp::encode(m).to_vec()).collect() + } } #[cfg(test)] From c8a3db4c5290410f0244f5c4f2d7245a786032a5 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:59:54 +0000 Subject: [PATCH 147/382] new error proposal test --- ethcore/src/engines/tendermint/mod.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5149f55549..c5a556a6290 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -510,10 +510,10 @@ impl Engine for Tendermint { // Commit is longer than empty signature list. let parent_signature_len = parent.seal()[2].len(); - if parent_signature_len < 1 { + if parent_signature_len <= 1 { try!(Err(EngineError::BadSealFieldSize(OutOfBounds { // One signature. - min: Some(136), + min: Some(69), max: None, found: parent_signature_len }))); @@ -695,11 +695,10 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); - let mut header = Header::default(); - let random = insert_and_unlock(&tap, "101"); - header.set_author(random); + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. @@ -707,6 +706,16 @@ mod tests { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } + + let random = insert_and_unlock(&tap, "101"); + header.set_author(random); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Not authority. + match engine.verify_block_unordered(&header, None) { + Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, + _ => panic!(), + } } #[test] From ff6240eff3851b0172f313636bb6cb06973d0924 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 20:03:15 +0000 Subject: [PATCH 148/382] insert block into queue when sealing --- ethcore/src/miner/miner.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c819cef3f1a..cb97f35ba68 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1030,6 +1030,11 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } self.seal_and_import_block_internally(chain, block); } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); @@ -1052,7 +1057,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1060,22 +1065,22 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| { println!("should be {:?}, but is {:?}", b.hash(), &block_hash); &b.hash() == &block_hash } ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) }) } else { - warn!(target: "miner", "Mined solution rejected: Block unknown or out of date."); + warn!(target: "miner", "Submitted solution rejected: Block unknown or out of date."); Err(Error::PowHashInvalid) }; result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); try!(chain.import_sealed_block(sealed)); - info!(target: "miner", "Mined block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); + info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); Ok(()) }) } From 9084e6242d4c6c94e836d65b46c8130590b2062e Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 20:04:12 +0000 Subject: [PATCH 149/382] lock ordering --- ethcore/src/engines/tendermint/mod.rs | 104 ++++++++++++++------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5a556a6290..bc29f78dd36 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -152,7 +152,10 @@ impl Tendermint { Ok(signature) => { let message_rlp = message_full_rlp(&signature, &vote_info); // TODO: memoize the rlp for consecutive broadcasts - self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); + let message = ConsensusMessage::new(signature, h, r, *s, block_hash); + self.votes.vote(message.clone(), *authority); + self.handle_valid_message(&message); + Some(message_rlp) }, Err(e) => { @@ -189,15 +192,15 @@ impl Tendermint { }, Step::Prevote => { let block_hash = match *self.lock_change.read() { - Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), - Some(ref m) => m.block_hash, - None => None, + Some(ref m) if !self.should_unlock(m.round) => m.block_hash, + _ => self.proposal.read().clone(), }; self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { let block_hash = match *self.lock_change.read() { - Some(ref m) if self.is_round(m) => { + Some(ref m) if self.is_round(m) && m.block_hash.is_some() => { + trace!(target: "poa", "to_step: Setting last lock: {}", m.round); self.last_lock.store(m.round, AtomicOrdering::SeqCst); m.block_hash }, @@ -290,6 +293,51 @@ impl Tendermint { let aligned_count = self.votes.count_aligned_votes(&message); self.is_above_threshold(aligned_count) } + + fn handle_valid_message(&self, message: &ConsensusMessage) { + trace!(target: "poa", "handle_valid_message: Processing valid message: {:?}", message); + let is_newer_than_lock = match *self.lock_change.read() { + Some(ref lock) => message > lock, + None => true, + }; + if is_newer_than_lock + && message.step == Step::Prevote + && message.block_hash.is_some() + && self.has_enough_aligned_votes(message) { + debug!(target: "poa", "handle_valid_message: Lock change."); + *self.lock_change.write() = Some(message.clone()); + } + // Check if it can affect the step transition. + if self.is_height(message) { + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(message) => { + if message.block_hash.is_none() { + self.increment_round(1); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_future_step_votes(message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_future_step_votes(message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); + Some(Step::Prevote) + }, + _ => None, + }; + + if let Some(step) = next_step { + trace!(target: "poa", "handle_valid_message: Transition triggered."); + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) + } + } + } + } } impl Engine for Tendermint { @@ -381,50 +429,9 @@ impl Engine for Tendermint { try!(Err(EngineError::NotAuthorized(sender))); } - trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); self.votes.vote(message.clone(), sender); - self.broadcast_message(rlp.as_raw().to_vec()); - let is_newer_than_lock = match *self.lock_change.read() { - Some(ref lock) => &message > lock, - None => true, - }; - if is_newer_than_lock - && message.step == Step::Prevote - && self.has_enough_aligned_votes(&message) { - debug!(target: "poa", "handle_message: Lock change."); - *self.lock_change.write() = Some(message.clone()); - } - // Check if it can affect the step transition. - if self.is_height(&message) { - let next_step = match *self.step.read() { - Step::Precommit if self.has_enough_aligned_votes(&message) => { - if message.block_hash.is_none() { - self.increment_round(1); - Some(Step::Propose) - } else { - Some(Step::Commit) - } - }, - Step::Precommit if self.has_enough_future_step_votes(&message) => { - self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); - Some(Step::Precommit) - }, - Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_future_step_votes(&message) => { - self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); - Some(Step::Prevote) - }, - _ => None, - }; - - if let Some(step) = next_step { - trace!(target: "poa", "handle_message: Transition triggered."); - if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to next step {}.", io_err) - } - } - } + self.handle_valid_message(&message); } Ok(()) } @@ -534,6 +541,9 @@ impl Engine for Tendermint { fn set_signer(&self, address: Address, password: String) { *self.authority.write() = address; *self.password.write() = Some(password); + if let Err(io_err) = self.step_service.send_message(Step::Propose) { + warn!(target: "poa", "Could not reset the round {}.", io_err); + } } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { From f1542b56120150f40ca1525c7e3e31a511d02e51 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 3 Dec 2016 16:19:51 +0000 Subject: [PATCH 150/382] better genesis seal rlp --- ethcore/res/tendermint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index e411d54e2dd..28c0fd43cb1 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -21,7 +21,7 @@ "seal": { "generic": { "fields": 3, - "rlp": "0x40010" + "rlp": "80b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } }, "difficulty": "0x20000", From 356aca2db56da299d993a96455379a91ea28268f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 4 Dec 2016 09:13:23 -0800 Subject: [PATCH 151/382] Fix build. --- ethcore/src/client/test_client.rs | 4 ++-- ethcore/src/client/traits.rs | 4 ++-- ethcore/src/client/updater.rs | 4 ++-- parity/blockchain.rs | 2 +- parity/configuration.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 44e56f54092..eb4e9547d00 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -385,7 +385,7 @@ impl BlockChainClient for TestBlockChainClient { } } - fn storage_root(&self, _address: &Address, _id: BlockID) -> Option { + fn storage_root(&self, _address: &Address, _id: BlockId) -> Option { None } @@ -424,7 +424,7 @@ impl BlockChainClient for TestBlockChainClient { None } - fn list_storage(&self, _id: BlockID, _account: &Address, _after: Option<&H256>, _count: u64) -> Option> { + fn list_storage(&self, _id: BlockId, _account: &Address, _after: Option<&H256>, _count: u64) -> Option> { None } fn transaction(&self, _id: TransactionId) -> Option { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7bffda6bced..a7e8041ef03 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -69,8 +69,8 @@ pub trait BlockChainClient : Sync + Send { fn nonce(&self, address: &Address, id: BlockId) -> Option; /// Attempt to get address storage root at given block. - /// May not fail on BlockID::Latest. - fn storage_root(&self, address: &Address, id: BlockID) -> Option; + /// May not fail on BlockId::Latest. + fn storage_root(&self, address: &Address, id: BlockId) -> Option; /// Get address nonce at the latest block's state. fn latest_nonce(&self, address: &Address) -> U256 { diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 9417df7bfdb..ada6749f021 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -17,9 +17,9 @@ use std::sync::{Weak, Arc}; use std::path::PathBuf; use util::misc::{VersionInfo, ReleaseTrack, platform}; -use util::{Bytes, Address, H160, H256, FixedHash, Mutex}; +use util::{Address, H160, H256, FixedHash, Mutex}; use client::operations::Operations; -use client::{Client, BlockChainClient, UpdatePolicy, BlockId}; +use client::{Client, UpdatePolicy, BlockId}; use fetch::HashFetch; use fetch; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 26c6fec52d3..84996b5a59e 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -117,7 +117,7 @@ pub struct ExportState { pub wal: bool, pub fat_db: Switch, pub tracing: Switch, - pub at: BlockID, + pub at: BlockId, pub storage: bool, pub code: bool, pub min_balance: Option, diff --git a/parity/configuration.rs b/parity/configuration.rs index 5d50d26179d..4031c7164d9 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -855,7 +855,7 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - at: BlockID::Latest, + at: BlockId::Latest, storage: true, code: true, min_balance: None, From b8c492644bda74c861d78dce67e0e8adfdb9e599 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 4 Dec 2016 10:47:05 -0800 Subject: [PATCH 152/382] Fix more build. --- parity/cli/mod.rs | 3 +++ parity/configuration.rs | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 340c06e8ac8..2e4996c066f 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -689,6 +689,9 @@ mod tests { mode: Some("dark".into()), mode_timeout: Some(15u64), mode_alarm: Some(10u64), + auto_update: None, + no_download: None, + no_consensus: None, chain: Some("./chain.json".into()), db_path: None, keys_path: None, diff --git a/parity/configuration.rs b/parity/configuration.rs index 4031c7164d9..3dee4054b74 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -725,7 +725,7 @@ mod tests { use super::*; use cli::Args; use ethcore_rpc::NetworkSettings; - use ethcore::client::{VMType, BlockId}; + use ethcore::client::{VMType, BlockId, UpdatePolicy, UpdateFilter}; use ethcore::miner::{MinerOptions, PrioritizationStrategy}; use helpers::{replace_home, default_network_config}; use run::RunCmd; @@ -938,6 +938,7 @@ mod tests { check_seal: true, download_old_blocks: true, require_consensus: true, + update_policy: Default::default(), })); } @@ -972,11 +973,8 @@ mod tests { // then assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, filter: UpdateFilter::Critical}); - mining_options.tx_queue_strategy = PrioritizationStrategy::GasFactorAndGasPrice; assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, filter: UpdateFilter::All}); - mining_options.tx_queue_strategy = PrioritizationStrategy::GasPriceOnly; assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, filter: UpdateFilter::Patch}); - mining_options.tx_queue_strategy = PrioritizationStrategy::GasAndGasPrice; assert!(conf3.update_policy().is_err()); } From edef7a185fb34f3d3ae5c63f37c01f629ee9fbae Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 4 Dec 2016 19:42:53 +0000 Subject: [PATCH 153/382] remove tracing --- ethcore/src/miner/miner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index cb97f35ba68..f32fee4138d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1065,7 +1065,7 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| { println!("should be {:?}, but is {:?}", b.hash(), &block_hash); &b.hash() == &block_hash } + |b| &b.hash() == &block_hash ) { trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { From f7a01b87b5963ba54cbfdae67c1726f93b1ef05b Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 4 Dec 2016 19:43:24 +0000 Subject: [PATCH 154/382] better gossip, better proposal collection --- ethcore/src/engines/tendermint/mod.rs | 135 ++++++++++-------- ethcore/src/engines/tendermint/transition.rs | 2 +- .../src/engines/tendermint/vote_collector.rs | 22 ++- 3 files changed, 84 insertions(+), 75 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bc29f78dd36..5244861cff9 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -154,6 +154,7 @@ impl Tendermint { // TODO: memoize the rlp for consecutive broadcasts let message = ConsensusMessage::new(signature, h, r, *s, block_hash); self.votes.vote(message.clone(), *authority); + debug!(target: "poa", "Generated a message for height {:?}.", message); self.handle_valid_message(&message); Some(message_rlp) @@ -176,13 +177,20 @@ impl Tendermint { } fn broadcast_old_messages(&self) { - if let Some(ref lc) = *self.lock_change.read() { - for m in self.votes.get_older_than(lc).into_iter() { - self.broadcast_message(m); - } + for m in self.votes.get_up_to(self.height.load(AtomicOrdering::SeqCst)).into_iter() { + self.broadcast_message(m); } } + fn to_height(&self, height: Height) { + debug!(target: "poa", "Transitioning to height {}.", height); + self.last_lock.store(0, AtomicOrdering::SeqCst); + self.height.store(height, AtomicOrdering::SeqCst); + self.round.store(0, AtomicOrdering::SeqCst); + *self.lock_change.write() = None; + } + + /// Use via step_service to transition steps. fn to_step(&self, step: Step) { *self.step.write() = step; match step { @@ -209,22 +217,26 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - debug!(target: "poa", "to_step: Commit."); + trace!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); + let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { - if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, block_hash) { - let seal = vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(&seal.proposal).to_vec(), - ::rlp::encode(&seal.votes).to_vec() - ]; - self.submit_seal(block_hash, seal); + // Generate seal and remove old votes. + if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { + trace!(target: "poa", "to_step: Collected seal: {:?}", seal); + if self.is_proposer(&*self.authority.read()).is_ok() { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() + ]; + self.submit_seal(block_hash, seal); + } } else { warn!(target: "poa", "Proposal was not found!"); } } - *self.lock_change.write() = None; }, } } @@ -237,10 +249,11 @@ impl Tendermint { n > self.our_params.authority_n * 2/3 } - /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { + /// Check if address is a proposer for given round. + fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; - let proposer_nonce = self.height.load(AtomicOrdering::SeqCst) + self.round.load(AtomicOrdering::SeqCst); + let proposer_nonce = height + round; + trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) @@ -249,6 +262,11 @@ impl Tendermint { } } + /// Check if address is the current proposer. + fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { + self.is_round_proposer(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), address) + } + fn is_height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } @@ -258,15 +276,12 @@ impl Tendermint { } fn increment_round(&self, n: Round) { - debug!(target: "poa", "increment_round: New round."); + trace!(target: "poa", "increment_round: New round."); self.round.fetch_add(n, AtomicOrdering::SeqCst); } - fn reset_round(&self) { - debug!(target: "poa", "reset_round: New height."); - self.last_lock.store(0, AtomicOrdering::SeqCst); - self.height.fetch_add(1, AtomicOrdering::SeqCst); - self.round.store(0, AtomicOrdering::SeqCst); + fn new_height(&self) { + self.to_height(self.height.load(AtomicOrdering::SeqCst) + 1); } fn should_unlock(&self, lock_change_round: Round) -> bool { @@ -295,7 +310,6 @@ impl Tendermint { } fn handle_valid_message(&self, message: &ConsensusMessage) { - trace!(target: "poa", "handle_valid_message: Processing valid message: {:?}", message); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => message > lock, None => true, @@ -304,7 +318,7 @@ impl Tendermint { && message.step == Step::Prevote && message.block_hash.is_some() && self.has_enough_aligned_votes(message) { - debug!(target: "poa", "handle_valid_message: Lock change."); + trace!(target: "poa", "handle_valid_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -376,11 +390,6 @@ impl Engine for Tendermint { }); } - /// Get the address to be used as authority. - fn on_new_block(&self, block: &mut ExecutedBlock) { - *self.authority.write() = *block.header().author(); - } - /// Should this node participate. fn is_sealer(&self, address: &Address) -> Option { Some(self.is_authority(address)) @@ -399,14 +408,15 @@ impl Engine for Tendermint { let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); - let vote_info = message_info_rlp(height, round, Step::Propose, bh); + let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone()); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { // Insert Propose vote. + debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round); self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. - *self.proposal.write() = Some(header.bare_hash()); + *self.proposal.write() = bh; Some(vec![ - ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), ::rlp::EMPTY_LIST_RLP.to_vec() ]) @@ -422,8 +432,7 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); - // Check if the message is known. - if !self.votes.is_known(&message) { + if !self.votes.is_old_or_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { try!(Err(EngineError::NotAuthorized(sender))); @@ -431,7 +440,10 @@ impl Engine for Tendermint { self.votes.vote(message.clone(), sender); self.broadcast_message(rlp.as_raw().to_vec()); + trace!(target: "poa", "Handling a valid message: {:?}", message); self.handle_valid_message(&message); + } else { + trace!(target: "poa", "handle_message: Old or known message ignored {:?}.", message); } Ok(()) } @@ -483,9 +495,14 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } } - + + if self.is_above_threshold(signature_count) { + // Skip ahead if block is from the future. + if proposal.height > self.height.load(AtomicOrdering::SeqCst) { + self.to_height(proposal.height); + } // Check if its a proposal if there is not enough precommits. - if !self.is_above_threshold(signature_count) { + } else { let signatures_len = signatures_field.len(); // Proposal has to have an empty signature list. if signatures_len != 1 { @@ -495,11 +512,13 @@ impl Engine for Tendermint { found: signatures_len }))); } - try!(self.is_proposer(&proposer)); - *self.proposal.write() = proposal.block_hash.clone(); - self.votes.vote(proposal, proposer); + try!(self.is_round_proposer(proposal.height, proposal.round, &proposer)); + if self.is_round(&proposal) { + debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); + *self.proposal.write() = proposal.block_hash.clone(); + self.votes.vote(proposal, proposer); + } } - Ok(()) } @@ -548,12 +567,22 @@ impl Engine for Tendermint { fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); - if new_header.number() > best_header.number() { - true + let new_number = new_header.number(); + let best_number = best_header.number(); + if new_number != best_number { + new_number > best_number } else { - let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - new_signatures > best_signatures + let new_seal = new_header.seal(); + let best_seal = best_header.seal(); + let new_signatures = new_seal.get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_seal.get(2).expect("Tendermint seal should have three elements.").len(); + if new_signatures > best_signatures { + true + } else { + let new_round: Round = ::rlp::Rlp::new(&new_seal.get(0).expect("Tendermint seal should have three elements.")).as_val(); + let best_round: Round = ::rlp::Rlp::new(&best_seal.get(0).expect("Tendermint seal should have three elements.")).as_val(); + new_round > best_round + } } } @@ -820,20 +849,4 @@ mod tests { let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); assert!(first ^ second); } - - #[test] - fn timeout_transitioning() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - - println!("{:?}", ::rlp::EMPTY_LIST_RLP.to_vec().len()); - println!("{:?}", ::rlp::encode(&vec![H520::default()]).to_vec().len()); - let v = insert_and_register(&tap, &engine, "0"); - - println!("done"); - - } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 1ca230b3ec8..22d4d9498bb 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -106,7 +106,7 @@ impl IoHandler for TransitionHandler { Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); - engine.reset_round(); + engine.new_height(); Some(Step::Propose) }, }; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 8bb271a3558..271ff5f9aa6 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -58,20 +58,15 @@ impl VoteCollector { /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - let is_new = { - let guard = self.votes.read(); - guard.keys().next().map_or(true, |oldest| &message > oldest) - }; - if is_new { - self.votes.write().insert(message, voter) - } else { - trace!(target: "poa", "vote: Old message ignored {:?}.", message); - None - } + self.votes.write().insert(message, voter) } - pub fn is_known(&self, message: &ConsensusMessage) -> bool { + pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool { self.votes.read().contains_key(message) + || { + let guard = self.votes.read(); + guard.keys().next().map_or(true, |oldest| message <= oldest) + } } /// Throws out messages older than message, leaves message as marker for the oldest. @@ -94,6 +89,7 @@ impl VoteCollector { .collect::>(); (proposal, votes) }; + // Remove messages that are no longer relevant. votes.last().map(|m| self.throw_out_old(m)); proposal.map(|p| SealSignatures { proposal: p.signature, @@ -127,9 +123,9 @@ impl VoteCollector { n } - pub fn get_older_than(&self, message: &ConsensusMessage) -> Vec { + pub fn get_up_to(&self, height: Height) -> Vec { let guard = self.votes.read(); - guard.keys().take_while(|m| *m <= message).map(|m| ::rlp::encode(m).to_vec()).collect() + guard.keys().take_while(|m| m.height <= height).map(|m| ::rlp::encode(m).to_vec()).collect() } } From 9d79cac36b81456ab7501dcf9faf9d22a00b07ac Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 4 Dec 2016 11:56:12 -0800 Subject: [PATCH 155/382] Cleanups. --- ethcore/src/client/updater.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index ada6749f021..8ce942959bd 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::{Weak, Arc}; +use std::sync::{Weak}; use std::path::PathBuf; use util::misc::{VersionInfo, ReleaseTrack, platform}; use util::{Address, H160, H256, FixedHash, Mutex}; @@ -44,7 +44,7 @@ pub struct Updater { fetch: Weak, operations: Operations, update_policy: UpdatePolicy, - fetch_handler: Mutex>, + fetching: Mutex, // These don't change pub this: VersionInfo, @@ -56,10 +56,6 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; -fn start_fetch(fetch: Arc, hash: H256, on_done: Box) + Send>) -> Result<(), fetch::Error> { - fetch.fetch(hash, on_done) -} - impl Updater { pub fn new(client: Weak, fetch: Weak, operations: Address, update_policy: UpdatePolicy) -> Self { let mut u = Updater { @@ -67,7 +63,7 @@ impl Updater { fetch: fetch.clone(), operations: Operations::new(operations, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))), update_policy: update_policy, - fetch_handler: Mutex::new(None), + fetching: Mutex::new(false), this: VersionInfo::this(), this_fork: None, latest: None, @@ -152,7 +148,11 @@ impl Updater { } fn fetch_done(&self, _r: Result) { - *self.fetch_handler.lock() = None; + match _r { + Ok(b) => info!("Fetched latest version OK: {}", b.display()), + Err(e) => warn!("Unable to fetch latest version: {:?}", e), + } + *self.fetching.lock() = false; } pub fn tick(&mut self) { @@ -173,12 +173,13 @@ impl Updater { } ); if let Some(b) = latest.track.binary { - let mut fetch_handler = self.fetch_handler.lock(); - if fetch_handler.is_none() { + let mut fetching = self.fetching.lock(); + if !*fetching { let c = self.client.clone(); let f = move |r: Result| if let Some(c) = c.upgrade() { c.updater().as_ref().expect("updater exists; updater only owned by client; qed").fetch_done(r); }; if let Some(fetch) = self.fetch.clone().upgrade() { - *fetch_handler = start_fetch(fetch, b, Box::new(f)).ok(); + fetch.fetch(b, Box::new(f)).ok(); + *fetching = true; } } } From b30c1d56020efd34bba4ee3a337a1fdba1527a0d Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 13:24:22 +0000 Subject: [PATCH 156/382] fix tests --- ethcore/res/ethereum/tests | 2 +- ethcore/src/engines/tendermint/mod.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index e8f4624b7f1..d509c75936e 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be +Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 5244861cff9..bec41d37f1e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -667,7 +667,7 @@ mod tests { } fn insert_and_register(tap: &Arc, engine: &Arc, acc: &str) -> Address { - let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + let addr = insert_and_unlock(tap, acc); engine.set_signer(addr.clone(), acc.into()); addr } @@ -729,14 +729,14 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "1"); + let validator = insert_and_unlock(&tap, "0"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); - let validator = insert_and_unlock(&tap, "0"); + let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); @@ -812,14 +812,15 @@ mod tests { #[test] fn step_transitioning() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - let v0 = insert_and_unlock(&tap, "0"); - let v1 = insert_and_unlock(&tap, "1"); + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); let h = 1; let r = 0; @@ -842,7 +843,7 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(50)); + ::std::thread::sleep(::std::time::Duration::from_millis(100)); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); From db59bd8731c1baa3428464a0afd948f5ced9f994 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 14:28:50 +0000 Subject: [PATCH 157/382] update genesis seal --- ethcore/res/tendermint.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 28c0fd43cb1..38e5334a3e4 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -20,8 +20,7 @@ "genesis": { "seal": { "generic": { - "fields": 3, - "rlp": "80b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "rlp": "f88980b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } }, "difficulty": "0x20000", From 459babb1a8742aeead92086b287fda5710910320 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 5 Dec 2016 06:39:56 -0800 Subject: [PATCH 158/382] Logging. --- ethcore/src/client/updater.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethcore/src/client/updater.rs b/ethcore/src/client/updater.rs index 8ce942959bd..d4a5b76ee2d 100644 --- a/ethcore/src/client/updater.rs +++ b/ethcore/src/client/updater.rs @@ -175,6 +175,7 @@ impl Updater { if let Some(b) = latest.track.binary { let mut fetching = self.fetching.lock(); if !*fetching { + info!("Attempting to get parity binary {}", b); let c = self.client.clone(); let f = move |r: Result| if let Some(c) = c.upgrade() { c.updater().as_ref().expect("updater exists; updater only owned by client; qed").fetch_done(r); }; if let Some(fetch) = self.fetch.clone().upgrade() { From 4f857642b59586167f37c2dbcd0cc4cf6b8962f5 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 16:28:56 +0000 Subject: [PATCH 159/382] rename set_sealer --- ethcore/src/miner/miner.rs | 2 +- ethcore/src/miner/mod.rs | 2 +- rpc/src/v1/impls/parity_set.rs | 4 ++-- rpc/src/v1/tests/helpers/miner_service.rs | 2 +- rpc/src/v1/tests/mocked/parity_set.rs | 4 ++-- rpc/src/v1/traits/parity_set.rs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index f32fee4138d..4b38a7e07c2 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -737,7 +737,7 @@ impl MinerService for Miner { *self.author.write() = author; } - fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { if self.seals_internally { if let Some(ref ap) = self.accounts { try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 89937e115d6..26cefb295ff 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -77,7 +77,7 @@ pub trait MinerService : Send + Sync { fn set_author(&self, author: Address); /// Set info necessary to sign consensus messages. - fn set_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 11bc48268a3..e28f9a5738f 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -116,9 +116,9 @@ impl ParitySet for ParitySetClient where Ok(true) } - fn set_sealer(&self, address: H160, password: String) -> Result { + fn set_consensus_signer(&self, address: H160, password: String) -> Result { try!(self.active()); - try!(take_weak!(self.miner).set_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); + try!(take_weak!(self.miner).set_consensus_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); Ok(true) } diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 68caa137bab..6037cdd4a07 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -87,7 +87,7 @@ impl MinerService for TestMinerService { *self.author.write() = author; } - fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { *self.author.write() = address; *self.password.write() = password; Ok(()) diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 8ae3976f94c..fdf3f2d0f3d 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -107,14 +107,14 @@ fn rpc_parity_set_author() { } #[test] -fn rpc_parity_set_sealer() { +fn rpc_parity_set_consensus_signer() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "parity_setSealer", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setConsensusSigner", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index 89d92e04371..e196a7d212a 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -45,8 +45,8 @@ build_rpc_trait! { fn set_author(&self, H160) -> Result; /// Sets account for signing consensus messages. - #[rpc(name = "parity_setSealer")] - fn set_sealer(&self, H160, String) -> Result; + #[rpc(name = "parity_setConsensusSigner")] + fn set_consensus_signer(&self, H160, String) -> Result; /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] From d9eb5e7f1dea3c573ddff5a36977025a02d9064a Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 09:32:36 +0100 Subject: [PATCH 160/382] remove uncles --- ethcore/src/engines/tendermint/mod.rs | 3 +++ ethcore/src/snapshot/service.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bec41d37f1e..c6c5c1ad4d2 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -363,6 +363,9 @@ impl Engine for Tendermint { fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } + fn maximum_uncle_count(&self) -> usize { 0 } + fn maximum_uncle_age(&self) -> usize { 0 } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { let message = ConsensusMessage::new_proposal(header).expect("Invalid header."); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index a7ab8bcd20d..89ee68de02d 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -32,7 +32,6 @@ use engines::Engine; use error::Error; use ids::BlockID; use service::ClientIoMessage; -use spec::Spec; use io::IoChannel; From a296c5e2266e53407fc087bc2fcd2b370356a02f Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 11:38:09 +0100 Subject: [PATCH 161/382] test client message handling --- ethcore/src/client/test_client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 973772f814d..19064264a66 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -156,7 +156,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -659,8 +659,8 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } - fn queue_consensus_message(&self, _packet: Bytes) { - unimplemented!(); + fn queue_consensus_message(&self, message: Bytes) { + self.spec.engine.handle_message(UntrustedRlp::new(&message)).unwrap(); } fn pending_transactions(&self) -> Vec { From 6440ca2f68a1689d9aa89a8b76681a844f0e1abb Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 14:39:37 +0100 Subject: [PATCH 162/382] move stuff around --- ethcore/src/engines/tendermint/mod.rs | 1 + ethcore/src/miner/miner.rs | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c6c5c1ad4d2..6f7ce209799 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -382,6 +382,7 @@ impl Engine for Tendermint { } 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; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 806ff69645b..892e37f401e 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -480,6 +480,9 @@ impl Miner { /// Uses Engine to seal the block internally and then imports it to chain. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_sealed_block(sealed).is_ok() { @@ -1015,7 +1018,6 @@ impl MinerService for Miner { self.transaction_queue.lock().last_nonce(address) } - /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &MiningBlockChainClient) { @@ -1030,11 +1032,6 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); - } self.seal_and_import_block_internally(chain, block); } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); From aa9caac750e80ec7fdcc046a9f48ebcf26a64f9d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 15:36:20 +0100 Subject: [PATCH 163/382] revert cli default --- parity/cli/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 1c9aa50849e..f5e2cbf4a79 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -206,7 +206,7 @@ usage! { or |c: &Config| otry!(c.mining).gas_cap.clone(), flag_extra_data: Option = None, or |c: &Config| otry!(c.mining).extra_data.clone().map(Some), - flag_tx_queue_size: usize = 2048usize, + flag_tx_queue_size: usize = 1024usize, or |c: &Config| otry!(c.mining).tx_queue_size.clone(), flag_tx_queue_gas: String = "auto", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), From 347634ac6c9dfbcd9defb53f434158a6f7853cf9 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 16:42:58 +0100 Subject: [PATCH 164/382] dont rebroadcast propose --- ethcore/src/engines/tendermint/mod.rs | 9 +++++++++ ethcore/src/engines/tendermint/vote_collector.rs | 7 ++++++- ethcore/src/miner/miner.rs | 8 +++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 6f7ce209799..a1f2fb03ab1 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -52,6 +52,15 @@ pub enum Step { Commit } +impl Step { + pub fn is_pre(self) -> bool { + match self { + Step::Prevote | Step::Precommit => true, + _ => false, + } + } +} + pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 271ff5f9aa6..9df2574ecf7 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -125,7 +125,12 @@ impl VoteCollector { pub fn get_up_to(&self, height: Height) -> Vec { let guard = self.votes.read(); - guard.keys().take_while(|m| m.height <= height).map(|m| ::rlp::encode(m).to_vec()).collect() + guard + .keys() + .filter(|m| m.step.is_pre()) + .take_while(|m| m.height <= height) + .map(|m| ::rlp::encode(m).to_vec()) + .collect() } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 892e37f401e..6e2bbb27bf1 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -480,9 +480,11 @@ impl Miner { /// Uses Engine to seal the block internally and then imports it to chain. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_sealed_block(sealed).is_ok() { From 3ebfa1481deb218477639f0d40f7adca6007ad67 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 12:03:34 +0100 Subject: [PATCH 165/382] better proposal block handling --- ethcore/src/client/chain_notify.rs | 4 +- ethcore/src/client/client.rs | 36 +++++++-- ethcore/src/client/test_client.rs | 2 + ethcore/src/client/traits.rs | 5 +- ethcore/src/engines/authority_round.rs | 20 ++--- ethcore/src/engines/basic_authority.rs | 14 ++-- ethcore/src/engines/instant_seal.rs | 13 +-- ethcore/src/engines/mod.rs | 23 +++++- ethcore/src/engines/tendermint/mod.rs | 58 +++++++------- ethcore/src/miner/miner.rs | 68 ++++++++-------- ethcore/src/snapshot/watcher.rs | 4 +- sync/src/api.rs | 4 +- sync/src/chain.rs | 106 +++++++++++++++++-------- sync/src/tests/helpers.rs | 2 +- 14 files changed, 228 insertions(+), 131 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index f9c1732e0ba..628536ff1f9 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use ipc::IpcConfig; -use util::H256; +use util::{H256, Bytes}; /// Represents what has to be handled by actor listening to chain events #[ipc] @@ -27,6 +27,8 @@ pub trait ChainNotify : Send + Sync { _enacted: Vec, _retracted: Vec, _sealed: Vec, + // Block bytes and total difficulty. + _proposed: Vec, _duration: u64) { // does nothing by default } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b26b19ad391..a7d65feecf8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -391,9 +391,10 @@ impl Client { /// This is triggered by a message coming from a block queue when the block is ready for insertion pub fn import_verified_blocks(&self) -> usize { let max_blocks_to_import = 4; - let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = { + let (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration, is_empty) = { let mut imported_blocks = Vec::with_capacity(max_blocks_to_import); let mut invalid_blocks = HashSet::new(); + let mut proposed_blocks = Vec::with_capacity(max_blocks_to_import); let mut import_results = Vec::with_capacity(max_blocks_to_import); let _import_lock = self.import_lock.lock(); @@ -412,12 +413,17 @@ impl Client { continue; } if let Ok(closed_block) = self.check_and_close_block(&block) { - imported_blocks.push(header.hash()); + if self.engine.is_proposal(&block.header) { + proposed_blocks.push(block.bytes); + invalid_blocks.insert(header.hash()); + } else { + imported_blocks.push(header.hash()); - let route = self.commit_block(closed_block, &header.hash(), &block.bytes); - import_results.push(route); + let route = self.commit_block(closed_block, &header.hash(), &block.bytes); + import_results.push(route); - self.report.write().accrue_block(&block); + self.report.write().accrue_block(&block); + } } else { invalid_blocks.insert(header.hash()); } @@ -431,7 +437,7 @@ impl Client { } let is_empty = self.block_queue.mark_as_good(&imported_blocks); let duration_ns = precise_time_ns() - start; - (imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty) + (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration_ns, is_empty) }; { @@ -449,6 +455,7 @@ impl Client { enacted.clone(), retracted.clone(), Vec::new(), + proposed_blocks.clone(), duration, ); }); @@ -1364,6 +1371,20 @@ impl MiningBlockChainClient for Client { &self.factories.vm } + fn broadcast_proposal_block(&self, block: SealedBlock) { + self.notify(|notify| { + notify.new_blocks( + vec![], + vec![], + vec![], + vec![], + vec![], + vec![block.rlp_bytes()], + 0, + ); + }); + } + fn import_sealed_block(&self, block: SealedBlock) -> ImportResult { let h = block.header().hash(); let start = precise_time_ns(); @@ -1388,6 +1409,7 @@ impl MiningBlockChainClient for Client { enacted.clone(), retracted.clone(), vec![h.clone()], + vec![], precise_time_ns() - start, ); }); @@ -1458,4 +1480,4 @@ mod tests { assert!(client.tree_route(&genesis, &new_hash).is_none()); } -} \ No newline at end of file +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 19064264a66..c8f8cdbf792 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -360,6 +360,8 @@ impl MiningBlockChainClient for TestBlockChainClient { fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult { Ok(H256::default()) } + + fn broadcast_proposal_block(&self, _block: SealedBlock) {} } impl BlockChainClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 4290831e806..cb5e774994c 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -273,6 +273,9 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; + /// Broadcast a block proposal. + fn broadcast_proposal_block(&self, block: SealedBlock); + /// Import sealed block. Skips all verifications. fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; @@ -299,4 +302,4 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Get code by address hash. fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; -} \ No newline at end of file +} diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 5b61b13fa6f..807e31c9ac6 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, Rlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError}; +use engines::{Engine, Seal, EngineError}; use header::Header; use error::{Error, BlockError}; use blockchain::extras::BlockDetails; @@ -218,8 +218,8 @@ impl Engine for AuthorityRound { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock) -> Option> { - if self.proposed.load(AtomicOrdering::SeqCst) { return None; } + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { + if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { @@ -228,7 +228,8 @@ impl Engine for AuthorityRound { if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); - return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]; + return Seal::Regular(rlps); } else { warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); } @@ -236,7 +237,7 @@ impl Engine for AuthorityRound { warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); } } - None + Seal::None } /// Check the number of seal fields. @@ -339,6 +340,7 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use std::time::UNIX_EPOCH; + use engines::Seal; #[test] fn has_valid_metadata() { @@ -408,17 +410,17 @@ mod tests { let b2 = b2.close_and_lock(); engine.set_signer(addr1, "1".into()); - if let Some(seal) = engine.generate_seal(b1.block()) { + if let Seal::Regular(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block()).is_none()); + assert!(engine.generate_seal(b1.block()) == Seal::None); } engine.set_signer(addr2, "2".into()); - if let Some(seal) = engine.generate_seal(b2.block()) { + if let Seal::Regular(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block()).is_none()); + assert!(engine.generate_seal(b2.block()) == Seal::None); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 1070d3a3dbf..3f99963d998 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -21,7 +21,7 @@ use account_provider::AccountProvider; use block::*; use builtin::Builtin; use spec::CommonParams; -use engines::Engine; +use engines::{Engine, Seal}; use env_info::EnvInfo; use error::{BlockError, Error}; use evm::Schedule; @@ -112,20 +112,20 @@ impl Engine for BasicAuthority { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) { - return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); + return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); } } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); } - None + Seal::None } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -199,6 +199,7 @@ mod tests { use account_provider::AccountProvider; use header::Header; use spec::Spec; + use engines::Seal; /// Create a new test chain spec with `BasicAuthority` consensus engine. fn new_test_authority() -> Spec { @@ -269,8 +270,9 @@ mod tests { 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()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + if let Seal::Regular(seal) = engine.generate_seal(b.block()) { + assert!(b.try_seal(engine, seal).is_ok()); + } } #[test] diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 83335fb03c8..87354f5ffed 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -17,12 +17,11 @@ use std::collections::BTreeMap; use util::Address; use builtin::Builtin; -use engines::Engine; +use engines::{Engine, Seal}; use env_info::EnvInfo; use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; -use util::Bytes; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -59,8 +58,8 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { - Some(Vec::new()) + fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { + Seal::Regular(Vec::new()) } } @@ -72,6 +71,7 @@ mod tests { use spec::Spec; use header::Header; use block::*; + use engines::Seal; #[test] fn instant_can_seal() { @@ -84,8 +84,9 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + if let Seal::Regular(seal) = engine.generate_seal(b.block()) { + assert!(b.try_seal(engine, seal).is_ok()); + } } #[test] diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 17cb461389d..31ce746fc2c 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -49,11 +49,11 @@ use views::HeaderView; #[derive(Debug)] pub enum EngineError { /// Signature does not belong to an authority. - NotAuthorized(H160), + NotAuthorized(Address), /// The same author issued different votes at the same step. - DoubleVote(H160), + DoubleVote(Address), /// The received block is from an incorrect proposer. - NotProposer(Mismatch), + NotProposer(Mismatch
), /// Message was not expected. UnexpectedMessage, /// Seal field has an unexpected size. @@ -75,6 +75,17 @@ impl fmt::Display for EngineError { } } +/// Seal type. +#[derive(Debug, PartialEq, Eq)] +pub enum Seal { + /// Proposal seal; should be broadcasted, but not inserted into blockchain. + Proposal(Vec), + /// Regular block seal; should be part of the blockchain. + Regular(Vec), + /// Engine does generate seal for this block right now. + None, +} + /// 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. pub trait Engine : Sync + Send { @@ -127,7 +138,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::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. @@ -189,6 +200,10 @@ pub trait Engine : Sync + Send { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) } + /// Find out if the block is a proposal block and should not be inserted into the DB. + /// Takes a header of a fully verified block. + fn is_proposal(&self, _verified_header: &Header) -> bool { false } + /// Register an account which signs consensus messages. fn set_signer(&self, _address: Address, _password: String) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a1f2fb03ab1..efb2f7479ce 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -33,7 +33,7 @@ use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError}; +use engines::{Engine, Seal, EngineError}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; @@ -408,14 +408,14 @@ impl Engine for Tendermint { Some(self.is_authority(address)) } - /// Attempt to seal the block internally using all available signatures. - fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + /// Attempt to seal generate a proposal seal. + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. if self.is_proposer(author).is_err() && self.proposal.read().is_none() { - return None; + return Seal::None; } let height = header.number() as Height; @@ -428,18 +428,18 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; - Some(vec![ + Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), ::rlp::EMPTY_LIST_RLP.to_vec() ]) } else { warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); - None + Seal::None } } else { warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); - None + Seal::None } } @@ -526,11 +526,6 @@ impl Engine for Tendermint { }))); } try!(self.is_round_proposer(proposal.height, proposal.round, &proposer)); - if self.is_round(&proposal) { - debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); - *self.proposal.write() = proposal.block_hash.clone(); - self.votes.vote(proposal, proposer); - } } Ok(()) } @@ -547,17 +542,6 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); } - // Commit is longer than empty signature list. - let parent_signature_len = parent.seal()[2].len(); - if parent_signature_len <= 1 { - try!(Err(EngineError::BadSealFieldSize(OutOfBounds { - // One signature. - min: Some(69), - max: None, - found: parent_signature_len - }))); - } - Ok(()) } @@ -599,6 +583,22 @@ impl Engine for Tendermint { } } + fn is_proposal(&self, header: &Header) -> bool { + let signatures_len = header.seal()[2].len(); + // Signatures have to be an empty list rlp. + if signatures_len != 1 { + return false; + } + let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); + let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); + debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); + if self.is_round(&proposal) { + *self.proposal.write() = proposal.block_hash.clone(); + } + self.votes.vote(proposal, proposer); + true + } + fn register_message_channel(&self, message_channel: IoChannel) { trace!(target: "poa", "register_message_channel: Register the IoChannel."); *self.message_channel.lock() = Some(message_channel); @@ -624,7 +624,7 @@ mod tests { use io::IoService; use service::ClientIoMessage; use spec::Spec; - use engines::{Engine, EngineError}; + use engines::{Engine, EngineError, Seal}; use super::*; use super::message::*; @@ -644,8 +644,11 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = spec.engine.generate_seal(b.block()).unwrap(); - (b, seal) + if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) { + (b, seal) + } else { + panic!() + } } fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { @@ -737,6 +740,7 @@ mod tests { } #[test] + #[ignore] fn allows_correct_proposer() { let (spec, tap) = setup(); let engine = spec.engine; @@ -825,7 +829,7 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); + //::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 6e2bbb27bf1..2b2661acffa 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -26,12 +26,12 @@ use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use client::TransactionImportResult; use executive::contract_address; -use block::{ClosedBlock, SealedBlock, IsBlock, Block}; +use block::{ClosedBlock, IsBlock, Block}; use error::*; use transaction::{Action, SignedTransaction}; use receipt::{Receipt, RichReceipt}; use spec::Spec; -use engines::Engine; +use engines::{Engine, Seal}; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; @@ -461,39 +461,41 @@ impl Miner { } } - /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), - /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. - fn seal_block_internally(&self, block: ClosedBlock) -> Result> { - trace!(target: "miner", "seal_block_internally: attempting internal seal."); - let s = self.engine.generate_seal(block.block()); - if let Some(seal) = s { - trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); - block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); - Err(None) - }) - } else { - trace!(target: "miner", "seal_block_internally: unable to generate seal internally"); - Err(Some(block)) - } - } - - /// Uses Engine to seal the block internally and then imports it to chain. + /// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); - } if !block.transactions().is_empty() || self.forced_sealing() { - if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_sealed_block(sealed).is_ok() { - trace!(target: "miner", "import_block_internally: imported internally sealed block"); - return true - } + trace!(target: "miner", "seal_block_internally: attempting internal seal."); + match self.engine.generate_seal(block.block()) { + // Save proposal for later seal submission and broadcast it. + Seal::Proposal(seal) => { + trace!(target: "miner", "Received a Proposal seal."); + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| { chain.broadcast_proposal_block(sealed); true }) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }) + }, + // Directly import a regular seal. + Seal::Regular(seal) => + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| chain.import_sealed_block(sealed).is_ok()) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }), + Seal::None => false, } + } else { + false } - false } /// Prepares work which has to be done to seal. @@ -1034,7 +1036,9 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - self.seal_and_import_block_internally(chain, block); + if self.seal_and_import_block_internally(chain, block) { + trace!(target: "miner", "update_sealing: imported internally sealed block"); + } } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); self.prepare_work(block, original_work_hash); diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 43439e437cf..c73e074553d 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -23,7 +23,7 @@ use service::ClientIoMessage; use views::HeaderView; use io::IoChannel; -use util::hash::H256; +use util::{H256, Bytes}; use std::sync::Arc; @@ -107,6 +107,7 @@ impl ChainNotify for Watcher { _: Vec, _: Vec, _: Vec, + _: Vec, _duration: u64) { if self.oracle.is_major_importing() { return } @@ -174,6 +175,7 @@ mod tests { vec![], vec![], vec![], + vec![], 0, ); } diff --git a/sync/src/api.rs b/sync/src/api.rs index 79e5806e128..233f7808753 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -227,6 +227,7 @@ impl ChainNotify for EthSync { enacted: Vec, retracted: Vec, sealed: Vec, + proposed: Vec, _duration: u64) { self.network.with_context(self.subprotocol_name, |context| { @@ -237,7 +238,8 @@ impl ChainNotify for EthSync { &invalid, &enacted, &retracted, - &sealed); + &sealed, + &proposed); }); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 319a7f61112..ee3a4da3edd 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -249,6 +249,15 @@ enum PeerAsking { SnapshotData, } +/// Peer type semantic boolean. +#[derive(Clone)] +enum PeerStatus { + /// Have the same latest_hash as we. + Current, + /// Is lagging in blocks. + Lagging +} + #[derive(PartialEq, Eq, Debug, Clone, Copy)] /// Block downloader channel. enum BlockSet { @@ -1797,32 +1806,42 @@ impl ChainSync { } } - /// creates latest block rlp for the given client - fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { + /// creates rlp from block bytes and total difficulty + fn create_block_rlp(bytes: &Bytes, total_difficulty: U256) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); - rlp_stream.append(&chain.chain_info().total_difficulty); + rlp_stream.append_raw(bytes, 1); + rlp_stream.append(&total_difficulty); rlp_stream.out() } + /// creates latest block rlp for the given client + fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { + ChainSync::create_block_rlp( + &chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), + chain.chain_info().total_difficulty + ) + } + /// creates latest block rlp for the given client fn create_new_block_rlp(chain: &BlockChainClient, hash: &H256) -> Bytes { - let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); - rlp_stream.append(&chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.")); - rlp_stream.out() + ChainSync::create_block_rlp( + &chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), + chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.") + ) } - /// returns peer ids that have less blocks than our chain - fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { + + /// Returns peer ids that either have less blocks than our (Lagging) chain or are Current. + fn get_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo, peer_status: PeerStatus) -> Vec { let latest_hash = chain_info.best_block_hash; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { - if peer_info.latest_hash != latest_hash { - Some(id) - } else { - None + match (peer_info.latest_hash == latest_hash, peer_status.clone()) { + (false, PeerStatus::Lagging) => Some(id), + (true, PeerStatus::Lagging) => None, + (false, PeerStatus::Current) => None, + (true, PeerStatus::Current) => Some(id), } }, _ => None @@ -1830,7 +1849,7 @@ impl ChainSync { .collect::>() } - fn select_random_lagging_peers(&mut self, peers: &[PeerId]) -> Vec { + fn select_random_peers(&mut self, peers: &[PeerId]) -> Vec { use rand::Rng; // take sqrt(x) peers let mut peers = peers.to_vec(); @@ -1842,16 +1861,16 @@ impl ChainSync { peers } - /// propagates latest block to lagging peers - fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[PeerId]) -> usize { + /// propagates latest block to a set of peers + fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, blocks: &[H256], peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewBlocks to {:?}", peers); let mut sent = 0; for peer_id in peers { - if sealed.is_empty() { + if blocks.is_empty() { let rlp = ChainSync::create_latest_block_rlp(io.chain()); self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } else { - for h in sealed { + for h in blocks { let rlp = ChainSync::create_new_block_rlp(io.chain(), h); self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } @@ -1969,10 +1988,10 @@ impl ChainSync { fn propagate_latest_blocks(&mut self, io: &mut SyncIo, sealed: &[H256]) { let chain_info = io.chain().chain_info(); if (((chain_info.best_block_number as i64) - (self.last_sent_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION { - let mut peers = self.get_lagging_peers(&chain_info, io); + let mut peers = self.get_peers(&chain_info, io, PeerStatus::Lagging); if sealed.is_empty() { let hashes = self.propagate_new_hashes(&chain_info, io, &peers); - peers = self.select_random_lagging_peers(&peers); + peers = self.select_random_peers(&peers); let blocks = self.propagate_blocks(&chain_info, io, sealed, &peers); if blocks != 0 || hashes != 0 { trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes); @@ -1987,6 +2006,22 @@ impl ChainSync { self.last_sent_block_number = chain_info.best_block_number; } + /// Distribute valid proposed blocks to subset of current peers. + fn propagate_proposed_blocks(&mut self, io: &mut SyncIo, proposed: &[Bytes]) { + let chain_info = io.chain().chain_info(); + let mut peers = self.get_peers(&chain_info, io, PeerStatus::Current); + peers = self.select_random_peers(&peers); + for block in proposed { + let rlp = ChainSync::create_block_rlp( + block, + chain_info.total_difficulty + ); + for peer_id in &peers { + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp.clone()); + } + } + } + /// Maintain other peers. Send out any new blocks and transactions pub fn maintain_sync(&mut self, io: &mut SyncIo) { self.maybe_start_snapshot_sync(io); @@ -1994,9 +2029,10 @@ impl ChainSync { } /// called when block is imported to chain - propagates the blocks and updates transactions sent to peers - pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256]) { + pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256], proposed: &[Bytes]) { if io.is_chain_queue_empty() { self.propagate_latest_blocks(io, sealed); + self.propagate_proposed_blocks(io, proposed); } if !invalid.is_empty() { trace!(target: "sync", "Bad blocks in the queue, restarting"); @@ -2032,7 +2068,7 @@ mod tests { use rlp::{Rlp, RlpStream, UntrustedRlp, View, Stream}; use super::*; use ::SyncConfig; - use super::{PeerInfo, PeerAsking}; + use super::{PeerInfo, PeerAsking, PeerStatus}; use ethcore::views::BlockView; use ethcore::header::*; use ethcore::client::*; @@ -2250,7 +2286,7 @@ mod tests { let ss = TestSnapshotService::new(); let io = TestIo::new(&mut client, &ss, &mut queue, None); - let lagging_peers = sync.get_lagging_peers(&chain_info, &io); + let lagging_peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); assert_eq!(1, lagging_peers.len()) } @@ -2282,7 +2318,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers); // 1 message should be send @@ -2302,7 +2338,7 @@ mod tests { let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers); // 1 message should be send @@ -2323,7 +2359,7 @@ mod tests { let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers); // 1 message should be send @@ -2347,7 +2383,7 @@ mod tests { // Try to propagate same transactions for the second time let peer_count2 = sync.propagate_new_transactions(&mut io); // Even after new block transactions should not be propagated twice - sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // Try to propagate same transactions for the third time let peer_count3 = sync.propagate_new_transactions(&mut io); @@ -2373,7 +2409,7 @@ mod tests { let peer_count = sync.propagate_new_transactions(&mut io); io.chain.insert_transaction_to_queue(); // New block import should trigger propagation. - sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // 2 message should be send assert_eq!(2, io.queue.len()); @@ -2534,7 +2570,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); sync.propagate_new_hashes(&chain_info, &mut io, &peers); let data = &io.queue[0].data.clone(); @@ -2554,7 +2590,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); sync.propagate_blocks(&chain_info, &mut io, &[], &peers); let data = &io.queue[0].data.clone(); @@ -2589,7 +2625,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); - sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 1); } @@ -2604,7 +2640,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks); - sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); } // then @@ -2630,10 +2666,10 @@ mod tests { let mut io = TestIo::new(&mut client, &ss, &mut queue, None); // when - sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 0); - sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); // then let status = io.chain.miner.status(); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 10c1277a65f..f3254fbade2 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -244,6 +244,6 @@ impl TestNet { pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { let mut peer = self.peer_mut(peer_id); - peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[]); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[], &[]); } } From dca752e9bb2c3dbb1b425b73350491d8197d03ad Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 20:09:30 +0100 Subject: [PATCH 166/382] docs, tweaks --- ethcore/src/engines/tendermint/mod.rs | 45 ++++++++++---------- ethcore/src/engines/tendermint/transition.rs | 1 - ethcore/src/miner/miner.rs | 8 ++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index efb2f7479ce..421ee849a87 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -14,7 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +/// Tendermint BFT consensus engine with round robin proof-of-authority. +/// At each blockchain `Height` there can be multiple `Round`s of voting. +/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`. +/// Signatures always sign `Height`, `Round`, `Step` and `BlockHash` which is a block hash without seal. +/// First a block with `Seal::Proposal` is issued by the designated proposer. +/// Once enough votes have been gathered the proposer issues that block in the `Commit` step. mod message; mod transition; @@ -191,10 +196,11 @@ impl Tendermint { } } - fn to_height(&self, height: Height) { - debug!(target: "poa", "Transitioning to height {}.", height); + fn to_next_height(&self, height: Height) { + let new_height = height + 1; + debug!(target: "poa", "Received a Commit, transitioning to height {}.", new_height); self.last_lock.store(0, AtomicOrdering::SeqCst); - self.height.store(height, AtomicOrdering::SeqCst); + self.height.store(new_height, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); *self.lock_change.write() = None; } @@ -232,18 +238,19 @@ impl Tendermint { let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { // Generate seal and remove old votes. - if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { - trace!(target: "poa", "to_step: Collected seal: {:?}", seal); - if self.is_proposer(&*self.authority.read()).is_ok() { + if self.is_proposer(&*self.authority.read()).is_ok() { + if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { + trace!(target: "poa", "to_step: Collected seal: {:?}", seal); let seal = vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&seal.proposal).to_vec(), ::rlp::encode(&seal.votes).to_vec() ]; self.submit_seal(block_hash, seal); + self.to_next_height(height); + } else { + warn!(target: "poa", "Proposal was not found!"); } - } else { - warn!(target: "poa", "Proposal was not found!"); } } }, @@ -289,10 +296,6 @@ impl Tendermint { self.round.fetch_add(n, AtomicOrdering::SeqCst); } - fn new_height(&self) { - self.to_height(self.height.load(AtomicOrdering::SeqCst) + 1); - } - fn should_unlock(&self, lock_change_round: Round) -> bool { self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round && lock_change_round < self.round.load(AtomicOrdering::SeqCst) @@ -414,7 +417,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. - if self.is_proposer(author).is_err() && self.proposal.read().is_none() { + if self.is_proposer(author).is_err() || self.proposal.read().is_some() { return Seal::None; } @@ -428,6 +431,7 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; + assert!(self.is_round_proposer(height, round, author).is_ok()); Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), @@ -509,13 +513,8 @@ impl Engine for Tendermint { } } - if self.is_above_threshold(signature_count) { - // Skip ahead if block is from the future. - if proposal.height > self.height.load(AtomicOrdering::SeqCst) { - self.to_height(proposal.height); - } // Check if its a proposal if there is not enough precommits. - } else { + if !self.is_above_threshold(signature_count) { let signatures_len = signatures_field.len(); // Proposal has to have an empty signature list. if signatures_len != 1 { @@ -563,9 +562,9 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); let new_number = new_header.number(); let best_number = best_header.number(); + trace!(target: "poa", "new_header: {}, best_header: {}", new_number, best_number); if new_number != best_number { new_number > best_number } else { @@ -586,10 +585,12 @@ impl Engine for Tendermint { fn is_proposal(&self, header: &Header) -> bool { let signatures_len = header.seal()[2].len(); // Signatures have to be an empty list rlp. + let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); if signatures_len != 1 { + // New Commit received, skip to next height. + self.to_next_height(proposal.height); return false; } - let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); if self.is_round(&proposal) { diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 22d4d9498bb..35f56ac87a6 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -106,7 +106,6 @@ impl IoHandler for TransitionHandler { Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); - engine.new_height(); Some(Step::Propose) }, }; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 2b2661acffa..72686ab400d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -469,9 +469,11 @@ impl Miner { // Save proposal for later seal submission and broadcast it. Seal::Proposal(seal) => { trace!(target: "miner", "Received a Proposal seal."); - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } block .lock() .seal(&*self.engine, seal) From 9ecb07434f07e30ff166412e4ab01f6a32410eb2 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:13:32 +0100 Subject: [PATCH 167/382] fix informant --- ethcore/src/client/client.rs | 2 +- parity/informant.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a7d65feecf8..93c666dbf8a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -414,8 +414,8 @@ impl Client { } if let Ok(closed_block) = self.check_and_close_block(&block) { if self.engine.is_proposal(&block.header) { + self.block_queue.mark_as_good(&[header.hash()]); proposed_blocks.push(block.bytes); - invalid_blocks.insert(header.hash()); } else { imported_blocks.push(header.hash()); diff --git a/parity/informant.rs b/parity/informant.rs index d3e3c8a2015..6b6f51d7b71 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -23,7 +23,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::time::{Instant, Duration}; use isatty::{stdout_isatty}; use ethsync::{SyncProvider, ManageNetwork}; -use util::{Uint, RwLock, Mutex, H256, Colour}; +use util::{Uint, RwLock, Mutex, H256, Colour, Bytes}; use ethcore::client::*; use ethcore::views::BlockView; use ethcore::snapshot::service::Service as SnapshotService; @@ -176,14 +176,13 @@ impl Informant { } impl ChainNotify for Informant { - fn new_blocks(&self, imported: Vec, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, duration: u64) { + fn new_blocks(&self, imported: Vec, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, _proposed: Vec, duration: u64) { let mut last_import = self.last_import.lock(); let sync_state = self.sync.as_ref().map(|s| s.status().state); let importing = is_major_importing(sync_state, self.client.queue_info()); - let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing; let txs_imported = imported.iter() - .take(imported.len() - if ripe {1} else {0}) + .take(imported.len().saturating_sub(if ripe { 1 } else { 0 })) .filter_map(|h| self.client.block(BlockID::Hash(*h))) .map(|b| BlockView::new(&b).transactions_count()) .sum(); From 79ef64349c1c3f123a1d9f1d323a5d60b69190df Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:27:49 +0100 Subject: [PATCH 168/382] remove assert --- ethcore/src/engines/tendermint/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 421ee849a87..bbcf24750ef 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -431,7 +431,6 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; - assert!(self.is_round_proposer(height, round, author).is_ok()); Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), From 74770e47738b63847ee0a394ac35b4a7deaa6dda Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:49:55 +0100 Subject: [PATCH 169/382] better docstrings --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/miner/miner.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 628536ff1f9..f082ab010a0 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -27,7 +27,7 @@ pub trait ChainNotify : Send + Sync { _enacted: Vec, _retracted: Vec, _sealed: Vec, - // Block bytes and total difficulty. + // Block bytes. _proposed: Vec, _duration: u64) { // does nothing by default diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 72686ab400d..f5072d04bb9 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -483,7 +483,7 @@ impl Miner { false }) }, - // Directly import a regular seal. + // Directly import a regular sealed block. Seal::Regular(seal) => block .lock() From 7c4224146697645d2346ddda3b7f0ea228fac3c7 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 22:00:26 +0100 Subject: [PATCH 170/382] remove merge code --- ethcore/src/miner/transaction_queue.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 41338ed2e10..cd2d3ba470f 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1040,16 +1040,6 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); - { - // Rough size sanity check - let gas = &tx.transaction.gas; - if U256::from(tx.transaction.data.len()) > *gas { - // Droping transaction - trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); - return Err(TransactionError::LimitReached); - } - } - // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. From 2b34d76b8ca02347e0bb8f2851c42c7805853d70 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 12:54:13 +0100 Subject: [PATCH 171/382] pull out fetchMeta --- js/src/contracts/badgereg.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 45760b277f4..02d08e516b5 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -38,16 +38,9 @@ export default class BadgeReg { .then((badgeReg) => { return badgeReg.instance.fromName.call({}, [name]) .then(([ id, address ]) => { - return Promise.all([ - badgeReg.instance.meta.call({}, [id, 'TITLE']), - badgeReg.instance.meta.call({}, [id, 'IMG']) - ]) - .then(([ title, img ]) => { - title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(img) === ZERO) img = null; - - const data = { address, name, title, icon: img }; + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; this.certifiers[name] = data; return data; }); @@ -55,6 +48,22 @@ export default class BadgeReg { }); } + fetchMeta (id) { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return Promise.all([ + badgeReg.instance.meta.call({}, [id, 'TITLE']), + badgeReg.instance.meta.call({}, [id, 'IMG']) + ]); + }) + .then(([ title, icon ]) => { + title = bytesToHex(title); + title = title === ZERO ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO) icon = null; + return { title, icon }; + }); + } + checkIfCertified (certifier, address) { if (!this.contracts[certifier]) { this.contracts[certifier] = this._api.newContract(ABI, certifier); From b32b636697dac347b2d9de259d6c97ccd6acbd46 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 13:07:27 +0100 Subject: [PATCH 172/382] fetch certifiers by id --- js/src/contracts/badgereg.js | 44 ++++++++++++------- .../providers/certifications/middleware.js | 29 +++++++++--- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 02d08e516b5..45f7df3158e 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -18,7 +18,8 @@ import { bytesToHex, hex2Ascii } from '~/api/util/format'; import ABI from './abi/certifier.json'; -const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const ZERO20 = '0x0000000000000000000000000000000000000000'; +const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; export default class BadgeReg { constructor (api, registry) { @@ -26,25 +27,36 @@ export default class BadgeReg { this._registry = registry; registry.getContract('badgereg'); - this.certifiers = {}; // by name + this.certifiers = []; // by id this.contracts = {}; // by name } - fetchCertifier (name) { - if (this.certifiers[name]) { - return Promise.resolve(this.certifiers[name]); + nrOfCertifiers () { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return badgeReg.instance.badgeCount.call({}, []) + .then((count) => count.valueOf()); + }); + } + + fetchCertifier (id) { + if (this.certifiers[id]) { + return Promise.resolve(this.certifiers[id]); } return this._registry.getContract('badgereg') .then((badgeReg) => { - return badgeReg.instance.fromName.call({}, [name]) - .then(([ id, address ]) => { - return this.fetchMeta(id) - .then(({ title, icon }) => { - const data = { address, name, title, icon }; - this.certifiers[name] = data; - return data; - }); - }); + return badgeReg.instance.badge.call({}, [ id ]); + }) + .then(([ address, name ]) => { + if (address === ZERO20) throw new Error(`Certifier ${id} does not exist.`); + name = bytesToHex(name); + name = name === ZERO32 ? null : hex2Ascii(name); + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; + this.certifiers[id] = data; + return data; + }); }); } @@ -58,8 +70,8 @@ export default class BadgeReg { }) .then(([ title, icon ]) => { title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(icon) === ZERO) icon = null; + title = title === ZERO32 ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO32) icon = null; return { title, icon }; }); } diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index a5406051fb8..6c443cea53d 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -17,20 +17,39 @@ import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ 'smsverification' ]; +const knownCertifiers = [ + 0 // sms verification +]; export default class CertificationsMiddleware { toMiddleware () { return (store) => (next) => (action) => { - if (action.type !== 'fetchCertifications') { + if (action.type === 'fetchCertifiers') { + badgeReg.nrOfCertifiers().then((count) => { + new Array(+count).fill(null).forEach((_, id) => { + badgeReg.fetchCertifier(id) + .then((cert) => { + const { address, name, title, icon } = cert; + store.dispatch(addCertifier(address, name, title, icon)); + }) + .catch((err) => { + if (err) { + console.error(`Failed to fetch certifier ${id}:`, err); + } + }); + }); + }); + } + + else if (action.type !== 'fetchCertifications') { return next(action); } const { address } = action; const badgeReg = Contracts.get().badgeReg; - knownCertifiers.forEach((name) => { - badgeReg.fetchCertifier(name) + knownCertifiers.forEach((id) => { + badgeReg.fetchCertifier(id) .then((cert) => { return badgeReg.checkIfCertified(cert.address, address) .then((isCertified) => { @@ -42,7 +61,7 @@ export default class CertificationsMiddleware { }) .catch((err) => { if (err) { - console.error(`Failed to check if ${address} certified by ${name}:`, err); + console.error(`Failed to check if ${address} certified by ${id}:`, err); } }); }); From 409c4adfbfd2a396f46534d5098a7af738e85607 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:05:16 +0100 Subject: [PATCH 173/382] fetch certifiers from BadgeReg --- .../redux/providers/certifications/actions.js | 4 + .../providers/certifications/middleware.js | 84 +++++++++++-------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js index c84f7db5539..a8cc43f035b 100644 --- a/js/src/redux/providers/certifications/actions.js +++ b/js/src/redux/providers/certifications/actions.js @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +export const fetchCertifiers = () => ({ + type: 'fetchCertifiers' +}); + export const fetchCertifications = (address) => ({ type: 'fetchCertifications', address }); diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6c443cea53d..715a5cb59bb 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -14,57 +14,73 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { uniq } from 'lodash'; + +import ABI from '~/contracts/abi/certifier.json'; +import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ - 0 // sms verification -]; - export default class CertificationsMiddleware { toMiddleware () { + const api = Contracts.get()._api; + const badgeReg = Contracts.get().badgeReg; + const contract = new Contract(api, ABI); + const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); + + let certifiers = []; + let accounts = []; // these are addresses + + const fetchConfirmedEvents = (dispatch) => { + if (certifiers.length === 0 || accounts.length === 0) return; + api.eth.getLogs({ + fromBlock: 0, + toBlock: 'latest', + address: certifiers.map((c) => c.address), + topics: [ Confirmed.signature, accounts ] + }) + .then((logs) => contract.parseEventLogs(logs)) + .then((logs) => { + logs.forEach((log) => { + const certifier = certifiers.find((c) => c.address === log.address); + if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); + const { name, title, icon } = certifier; + dispatch(addCertification(log.params.who.value, name, title, icon)); + }); + }) + .catch((err) => { + console.error('Failed to fetch Confirmed events:', err); + }); + }; + return (store) => (next) => (action) => { if (action.type === 'fetchCertifiers') { badgeReg.nrOfCertifiers().then((count) => { new Array(+count).fill(null).forEach((_, id) => { badgeReg.fetchCertifier(id) .then((cert) => { - const { address, name, title, icon } = cert; - store.dispatch(addCertifier(address, name, title, icon)); + if (!certifiers.some((c) => c.address === cert.address)) { + certifiers = certifiers.concat(cert); + fetchConfirmedEvents(store.dispatch); + } }) .catch((err) => { - if (err) { - console.error(`Failed to fetch certifier ${id}:`, err); - } + console.warn(`Could not fetch certifier ${id}:`, err); }); }); }); - } - - else if (action.type !== 'fetchCertifications') { - return next(action); - } - - const { address } = action; - const badgeReg = Contracts.get().badgeReg; + } else if (action.type === 'fetchCertifications') { + const { address } = action; - knownCertifiers.forEach((id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - return badgeReg.checkIfCertified(cert.address, address) - .then((isCertified) => { - if (isCertified) { - const { name, title, icon } = cert; - store.dispatch(addCertification(address, name, title, icon)); - } - }); - }) - .catch((err) => { - if (err) { - console.error(`Failed to check if ${address} certified by ${id}:`, err); - } - }); - }); + if (!accounts.includes(address)) { + accounts = accounts.concat(address); + fetchConfirmedEvents(store.dispatch); + } + } else if (action.type === 'setVisibleAccounts') { + const { addresses } = action; + accounts = uniq(accounts.concat(addresses)); + fetchConfirmedEvents(store.dispatch); + } else return next(action); }; } } From e1c5796a5c43643aab7075ffe2be984a2f4a6d3a Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:58:24 +0100 Subject: [PATCH 174/382] fetch certifications in account view --- js/src/ui/Certifications/certifications.js | 16 ++-------------- js/src/views/Account/account.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 871b14e9c74..b034deda4cc 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -19,7 +19,6 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; -import { fetchCertifications } from '~/redux/providers/certifications/actions'; import defaultIcon from '../../../assets/images/certifications/unknown.svg'; @@ -29,14 +28,7 @@ class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired, - - fetchCertifications: PropTypes.func.isRequired - } - - componentWillMount () { - const { account, fetchCertifications } = this.props; - fetchCertifications(account); + dappsUrl: PropTypes.string.isRequired } render () { @@ -77,11 +69,7 @@ function mapStateToProps (_, initProps) { }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({ fetchCertifications }, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(Certifications); diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e975d..ec9551d6b76 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -31,6 +31,7 @@ import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import VerificationStore from '~/modals/SMSVerification/store'; @@ -43,6 +44,8 @@ class Account extends Component { static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired, images: PropTypes.object.isRequired, params: PropTypes.object, @@ -62,6 +65,7 @@ class Account extends Component { } componentDidMount () { + this.props.fetchCertifiers(); this.setVisibleAccounts(); } @@ -88,9 +92,10 @@ class Account extends Component { } setVisibleAccounts (props = this.props) { - const { params, setVisibleAccounts } = props; + const { params, setVisibleAccounts, fetchCertifications } = props; const addresses = [ params.address ]; setVisibleAccounts(addresses); + fetchCertifications(params.address); } render () { @@ -344,7 +349,9 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return bindActionCreators({ - setVisibleAccounts + setVisibleAccounts, + fetchCertifiers, + fetchCertifications }, dispatch); } From 5862f2a9ebd2d72f2d639802de1c2d7b96f5b3b4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:30:05 +0100 Subject: [PATCH 175/382] Certifications: read dappsUrl from state --- js/src/ui/Certifications/certifications.js | 6 +++--- js/src/views/Account/Header/header.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index b034deda4cc..47cf369427b 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -27,8 +27,7 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired + certifications: PropTypes.array.isRequired } render () { @@ -65,7 +64,8 @@ function mapStateToProps (_, initProps) { return (state) => { const certifications = state.certifications[account] || []; - return { certifications }; + const dappsUrl = state.api.dappsUrl; + return { certifications, dappsUrl }; }; } diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index b126951b294..c77c4987dd6 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -78,7 +78,6 @@ export default class Header extends Component { balance={ balance } /> { children } From a84cd9143f014c9bba9a5b2bc6c9790bf9d9f27b Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:34:28 +0100 Subject: [PATCH 176/382] show certifications in accounts list --- js/src/views/Accounts/List/list.js | 40 ++++++++++++++++++++++-- js/src/views/Accounts/Summary/summary.js | 17 +++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 4d54b640f69..8c29d17f08a 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -15,13 +15,16 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Container } from '~/ui'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import Summary from '../Summary'; import styles from './list.css'; -export default class List extends Component { +class List extends Component { static propTypes = { accounts: PropTypes.object, walletsOwners: PropTypes.object, @@ -31,7 +34,11 @@ export default class List extends Component { empty: PropTypes.bool, order: PropTypes.string, orderFallback: PropTypes.string, - handleAddSearchToken: PropTypes.func + certifications: PropTypes.object.isRequired, + + handleAddSearchToken: PropTypes.func, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired }; render () { @@ -42,6 +49,14 @@ export default class List extends Component { ); } + componentWillMount () { + const { fetchCertifiers, accounts, fetchCertifications } = this.props; + fetchCertifiers(); + for (let address in accounts) { + fetchCertifications(address); + } + } + renderAccounts () { const { accounts, balances, link, empty, handleAddSearchToken, walletsOwners } = this.props; @@ -72,7 +87,9 @@ export default class List extends Component { account={ account } balance={ balance } owners={ owners } - handleAddSearchToken={ handleAddSearchToken } /> + handleAddSearchToken={ handleAddSearchToken } + showCertifications + /> ); }); @@ -207,3 +224,20 @@ export default class List extends Component { }); } } + +function mapStateToProps (state) { + const { certifications } = state; + return { certifications }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + fetchCertifiers, + fetchCertifications + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(List); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 764f24edfe0..0894e86999e 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -20,6 +20,7 @@ import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; +import Certifications from '~/ui/Certifications'; import { nullableProptype } from '~/util/proptypes'; import styles from '../accounts.css'; @@ -35,12 +36,14 @@ export default class Summary extends Component { link: PropTypes.string, name: PropTypes.string, noLink: PropTypes.bool, + showCertifications: PropTypes.bool, handleAddSearchToken: PropTypes.func, owners: nullableProptype(PropTypes.array) }; static defaultProps = { - noLink: false + noLink: false, + showCertifications: false }; shouldComponentUpdate (nextProps) { @@ -107,6 +110,7 @@ export default class Summary extends Component { { this.renderOwners() } { this.renderBalance() } + { this.renderCertifications() } ); } @@ -172,4 +176,15 @@ export default class Summary extends Component { ); } + + renderCertifications () { + const { showCertifications, account } = this.props; + if (!showCertifications) { + return null; + } + + return ( + + ); + } } From e53629089230f23899f73840e8b218f5d4062c8d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:38:44 +0100 Subject: [PATCH 177/382] fix linting issues --- js/src/ui/Certifications/certifications.js | 4 ++-- js/src/views/Account/Header/header.js | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 47cf369427b..36635ff5899 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -16,7 +16,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; @@ -27,7 +26,8 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired + certifications: PropTypes.array.isRequired, + dappsUrl: PropTypes.string.isRequired } render () { diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index c77c4987dd6..c2b9151e7bf 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -23,10 +23,6 @@ import Certifications from '~/ui/Certifications'; import styles from './header.css'; export default class Header extends Component { - static contextTypes = { - api: PropTypes.object - }; - static propTypes = { account: PropTypes.object, balance: PropTypes.object, @@ -40,7 +36,6 @@ export default class Header extends Component { }; render () { - const { api } = this.context; const { account, balance, className, children } = this.props; const { address, meta, uuid } = account; From 600a7e5ccc84609f8e088d9eb33624cff6ae8bf6 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 15:09:11 +0100 Subject: [PATCH 178/382] make SMS verification contract general purpose --- js/src/contracts/contracts.js | 7 +++++-- js/src/contracts/{sms-verification.js => verification.js} | 0 js/src/modals/SMSVerification/store.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) rename js/src/contracts/{sms-verification.js => verification.js} (100%) diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index f61a6369094..f30f67efbf3 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -19,7 +19,7 @@ import Registry from './registry'; import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; -import * as smsVerification from './sms-verification'; +import * as verification from './verification'; import BadgeReg from './badgereg'; let instance = null; @@ -58,7 +58,10 @@ export default class Contracts { } get smsVerification () { - return smsVerification; + return verification; + } + get emailVerification () { + return verification; } static create (api) { diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/verification.js similarity index 100% rename from js/src/contracts/sms-verification.js rename to js/src/contracts/verification.js diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/SMSVerification/store.js index 49b91fa70f9..279329ca5cc 100644 --- a/js/src/modals/SMSVerification/store.js +++ b/js/src/modals/SMSVerification/store.js @@ -20,7 +20,7 @@ import { sha3 } from '~/api/util/sha3'; import Contracts from '~/contracts'; -import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; +import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; import { postToServer } from '../../3rdparty/sms-verification'; import checkIfTxFailed from '../../util/check-if-tx-failed'; import waitForConfirmations from '../../util/wait-for-block-confirmations'; From b5b529f8c2442bbeb6675225f46141dd7a583085 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 15:09:54 +0100 Subject: [PATCH 179/382] modals/SMSVerification -> modals/Verification --- .../modals/{SMSVerification => Verification}/Done/done.css | 0 .../modals/{SMSVerification => Verification}/Done/done.js | 0 .../modals/{SMSVerification => Verification}/Done/index.js | 0 .../GatherData/gatherData.css | 0 .../GatherData/gatherData.js | 0 .../{SMSVerification => Verification}/GatherData/index.js | 0 .../{SMSVerification => Verification}/QueryCode/index.js | 0 .../QueryCode/queryCode.js | 0 .../SendConfirmation/index.js | 0 .../SendConfirmation/sendConfirmation.css | 0 .../SendConfirmation/sendConfirmation.js | 0 .../{SMSVerification => Verification}/SendRequest/index.js | 0 .../SendRequest/sendRequest.css | 0 .../SendRequest/sendRequest.js | 0 js/src/modals/{SMSVerification => Verification}/index.js | 2 +- js/src/modals/{SMSVerification => Verification}/store.js | 0 .../SMSVerification.js => Verification/verification.js} | 4 ++-- js/src/modals/index.js | 4 ++-- js/src/views/Account/account.js | 6 +++--- 19 files changed, 8 insertions(+), 8 deletions(-) rename js/src/modals/{SMSVerification => Verification}/Done/done.css (100%) rename js/src/modals/{SMSVerification => Verification}/Done/done.js (100%) rename js/src/modals/{SMSVerification => Verification}/Done/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/gatherData.css (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/gatherData.js (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/QueryCode/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/QueryCode/queryCode.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/sendConfirmation.css (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/sendConfirmation.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/sendRequest.css (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/sendRequest.js (100%) rename js/src/modals/{SMSVerification => Verification}/index.js (94%) rename js/src/modals/{SMSVerification => Verification}/store.js (100%) rename js/src/modals/{SMSVerification/SMSVerification.js => Verification/verification.js} (97%) diff --git a/js/src/modals/SMSVerification/Done/done.css b/js/src/modals/Verification/Done/done.css similarity index 100% rename from js/src/modals/SMSVerification/Done/done.css rename to js/src/modals/Verification/Done/done.css diff --git a/js/src/modals/SMSVerification/Done/done.js b/js/src/modals/Verification/Done/done.js similarity index 100% rename from js/src/modals/SMSVerification/Done/done.js rename to js/src/modals/Verification/Done/done.js diff --git a/js/src/modals/SMSVerification/Done/index.js b/js/src/modals/Verification/Done/index.js similarity index 100% rename from js/src/modals/SMSVerification/Done/index.js rename to js/src/modals/Verification/Done/index.js diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.css b/js/src/modals/Verification/GatherData/gatherData.css similarity index 100% rename from js/src/modals/SMSVerification/GatherData/gatherData.css rename to js/src/modals/Verification/GatherData/gatherData.css diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js similarity index 100% rename from js/src/modals/SMSVerification/GatherData/gatherData.js rename to js/src/modals/Verification/GatherData/gatherData.js diff --git a/js/src/modals/SMSVerification/GatherData/index.js b/js/src/modals/Verification/GatherData/index.js similarity index 100% rename from js/src/modals/SMSVerification/GatherData/index.js rename to js/src/modals/Verification/GatherData/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/index.js b/js/src/modals/Verification/QueryCode/index.js similarity index 100% rename from js/src/modals/SMSVerification/QueryCode/index.js rename to js/src/modals/Verification/QueryCode/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/queryCode.js b/js/src/modals/Verification/QueryCode/queryCode.js similarity index 100% rename from js/src/modals/SMSVerification/QueryCode/queryCode.js rename to js/src/modals/Verification/QueryCode/queryCode.js diff --git a/js/src/modals/SMSVerification/SendConfirmation/index.js b/js/src/modals/Verification/SendConfirmation/index.js similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/index.js rename to js/src/modals/Verification/SendConfirmation/index.js diff --git a/js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.css b/js/src/modals/Verification/SendConfirmation/sendConfirmation.css similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.css rename to js/src/modals/Verification/SendConfirmation/sendConfirmation.css diff --git a/js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.js b/js/src/modals/Verification/SendConfirmation/sendConfirmation.js similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.js rename to js/src/modals/Verification/SendConfirmation/sendConfirmation.js diff --git a/js/src/modals/SMSVerification/SendRequest/index.js b/js/src/modals/Verification/SendRequest/index.js similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/index.js rename to js/src/modals/Verification/SendRequest/index.js diff --git a/js/src/modals/SMSVerification/SendRequest/sendRequest.css b/js/src/modals/Verification/SendRequest/sendRequest.css similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/sendRequest.css rename to js/src/modals/Verification/SendRequest/sendRequest.css diff --git a/js/src/modals/SMSVerification/SendRequest/sendRequest.js b/js/src/modals/Verification/SendRequest/sendRequest.js similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/sendRequest.js rename to js/src/modals/Verification/SendRequest/sendRequest.js diff --git a/js/src/modals/SMSVerification/index.js b/js/src/modals/Verification/index.js similarity index 94% rename from js/src/modals/SMSVerification/index.js rename to js/src/modals/Verification/index.js index d9b0990db84..9c29a716558 100644 --- a/js/src/modals/SMSVerification/index.js +++ b/js/src/modals/Verification/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './SMSVerification'; +export default from './verification'; diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/Verification/store.js similarity index 100% rename from js/src/modals/SMSVerification/store.js rename to js/src/modals/Verification/store.js diff --git a/js/src/modals/SMSVerification/SMSVerification.js b/js/src/modals/Verification/verification.js similarity index 97% rename from js/src/modals/SMSVerification/SMSVerification.js rename to js/src/modals/Verification/verification.js index 86f027a524a..65447c4f9fb 100644 --- a/js/src/modals/SMSVerification/SMSVerification.js +++ b/js/src/modals/Verification/verification.js @@ -37,7 +37,7 @@ import SendConfirmation from './SendConfirmation'; import Done from './Done'; @observer -export default class SMSVerification extends Component { +export default class Verification extends Component { static propTypes = { store: PropTypes.any.isRequired, account: PropTypes.string.isRequired, @@ -54,7 +54,7 @@ export default class SMSVerification extends Component { } render () { - const phase = SMSVerification.phases[this.props.store.step]; + const phase = Verification.phases[this.props.store.step]; const { error, isStepValid } = this.props.store; return ( diff --git a/js/src/modals/index.js b/js/src/modals/index.js index 0f0844e4022..1daee866360 100644 --- a/js/src/modals/index.js +++ b/js/src/modals/index.js @@ -24,7 +24,7 @@ import EditMeta from './EditMeta'; import ExecuteContract from './ExecuteContract'; import FirstRun from './FirstRun'; import Shapeshift from './Shapeshift'; -import SMSVerification from './SMSVerification'; +import Verification from './Verification'; import Transfer from './Transfer'; import PasswordManager from './PasswordManager'; import SaveContract from './SaveContract'; @@ -42,7 +42,7 @@ export { ExecuteContract, FirstRun, Shapeshift, - SMSVerification, + Verification, Transfer, PasswordManager, LoadContract, diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e975d..1fd654fdec3 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -23,7 +23,7 @@ import ContentSend from 'material-ui/svg-icons/content/send'; import LockIcon from 'material-ui/svg-icons/action/lock'; import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; -import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; +import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; @@ -32,7 +32,7 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/SMSVerification/store'; +import VerificationStore from '~/modals/Verification/store'; import styles from './account.css'; @@ -228,7 +228,7 @@ class Account extends Component { const { address } = this.props.params; return ( - From 1672cbad7be46f49af7e2b0b1eaf66b08f9a95a4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 16:09:02 +0100 Subject: [PATCH 180/382] factor out SMS-specific logic --- .../Verification/SendRequest/sendRequest.js | 6 +- js/src/modals/Verification/sms-store.js | 66 +++++++++++++++++++ js/src/modals/Verification/store.js | 55 +++------------- js/src/modals/Verification/verification.js | 8 +-- js/src/views/Account/account.js | 2 +- 5 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 js/src/modals/Verification/sms-store.js diff --git a/js/src/modals/Verification/SendRequest/sendRequest.js b/js/src/modals/Verification/SendRequest/sendRequest.js index 933de92651a..41dc7c06c16 100644 --- a/js/src/modals/Verification/SendRequest/sendRequest.js +++ b/js/src/modals/Verification/SendRequest/sendRequest.js @@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react'; import { nullableProptype } from '~/util/proptypes'; import TxHash from '~/ui/TxHash'; import { - POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS + POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE } from '../store'; import styles from './sendRequest.css'; @@ -45,9 +45,9 @@ export default class SendRequest extends Component { ); - case REQUESTING_SMS: + case REQUESTING_CODE: return ( -

Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.

+

Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.

); default: diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js new file mode 100644 index 00000000000..58b79ebfb71 --- /dev/null +++ b/js/src/modals/Verification/sms-store.js @@ -0,0 +1,66 @@ +// 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 . + +import { observable, computed, action } from 'mobx'; +import phone from 'phoneformat.js'; + +import VerificationStore, { + LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE +} from './store'; +import { postToServer } from '../../3rdparty/sms-verification'; + +export default class SMSVerificationStore extends VerificationStore { + @observable number = ''; + + @computed get isNumberValid () { + return phone.isValidNumber(this.number); + } + + @computed get isStepValid () { + if (this.step === DONE) { + return true; + } + if (this.error) { + return false; + } + + switch (this.step) { + case LOADING: + return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; + case QUERY_DATA: + return this.isNumberValid && this.consentGiven; + case QUERY_CODE: + return this.requestTx && this.isCodeValid === true; + case POSTED_CONFIRMATION: + return !!this.confirmationTx; + default: + return false; + } + } + + constructor (api, account, isTestnet) { + return super(api, account, isTestnet, 'smsverification'); + } + + @action setNumber = (number) => { + this.number = number; + } + + requestCode = () => { + const { number, account, isTestnet } = this; + return postToServer({ number, address: account }, isTestnet); + } +} diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index 279329ca5cc..f8542786b49 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { observable, computed, autorun, action } from 'mobx'; -import phone from 'phoneformat.js'; +import { observable, autorun, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; - import Contracts from '~/contracts'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; -import { postToServer } from '../../3rdparty/sms-verification'; import checkIfTxFailed from '../../util/check-if-tx-failed'; import waitForConfirmations from '../../util/wait-for-block-confirmations'; @@ -29,7 +26,7 @@ export const LOADING = 'fetching-contract'; export const QUERY_DATA = 'query-data'; export const POSTING_REQUEST = 'posting-request'; export const POSTED_REQUEST = 'posted-request'; -export const REQUESTING_SMS = 'requesting-sms'; +export const REQUESTING_CODE = 'requesting-code'; export const QUERY_CODE = 'query-code'; export const POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation'; @@ -44,45 +41,18 @@ export default class VerificationStore { @observable isVerified = null; @observable hasRequested = null; @observable consentGiven = false; - @observable number = ''; @observable requestTx = null; @observable code = ''; @observable isCodeValid = null; @observable confirmationTx = null; - @computed get isNumberValid () { - return phone.isValidNumber(this.number); - } - - @computed get isStepValid () { - if (this.step === DONE) { - return true; - } - if (this.error) { - return false; - } - - switch (this.step) { - case LOADING: - return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; - case QUERY_DATA: - return this.isNumberValid && this.consentGiven; - case QUERY_CODE: - return this.requestTx && this.isCodeValid === true; - case POSTED_CONFIRMATION: - return !!this.confirmationTx; - default: - return false; - } - } - - constructor (api, account, isTestnet) { + constructor (api, account, isTestnet, name) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; - Contracts.create(api).registry.getContract('smsverification') + Contracts.create(api).registry.getContract(name) .then((contract) => { this.contract = contract; this.load(); @@ -93,7 +63,7 @@ export default class VerificationStore { autorun(() => { if (this.error) { - console.error('sms verification: ' + this.error); + console.error('verification: ' + this.error); } }); } @@ -136,10 +106,6 @@ export default class VerificationStore { }); } - @action setNumber = (number) => { - this.number = number; - } - @action setConsentGiven = (consentGiven) => { this.consentGiven = consentGiven; } @@ -168,7 +134,7 @@ export default class VerificationStore { } @action sendRequest = () => { - const { api, account, contract, fee, number, hasRequested } = this; + const { api, account, contract, fee, hasRequested } = this; const request = contract.functions.find((fn) => fn.name === 'request'); const options = { from: account, value: fee.toString() }; @@ -201,18 +167,15 @@ export default class VerificationStore { chain .then(() => { - return api.parity.netChain(); - }) - .then((chain) => { - this.step = REQUESTING_SMS; - return postToServer({ number, address: account }, this.isTestnet); + this.step = REQUESTING_CODE; + return this.requestCode(); }) .then(() => awaitPuzzle(api, contract, account)) .then(() => { this.step = QUERY_CODE; }) .catch((err) => { - this.error = 'Failed to request a confirmation SMS: ' + err.message; + this.error = 'Failed to request a confirmation code: ' + err.message; }); } diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 65447c4f9fb..b05ed353ae8 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -25,7 +25,7 @@ import { LOADING, QUERY_DATA, POSTING_REQUEST, POSTED_REQUEST, - REQUESTING_SMS, QUERY_CODE, + REQUESTING_CODE, QUERY_CODE, POSTING_CONFIRMATION, POSTED_CONFIRMATION, DONE } from './store'; @@ -47,7 +47,7 @@ export default class Verification extends Component { static phases = { // mapping (store steps -> steps) [LOADING]: 0, [QUERY_DATA]: 1, - [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2, + [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, [QUERY_CODE]: 3, [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, [DONE]: 5 @@ -60,7 +60,7 @@ export default class Verification extends Component { return ( Loading SMS Verification.

+

Loading Verification.

); case 1: diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 1fd654fdec3..240ca2f9b7f 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -32,7 +32,7 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/Verification/store'; +import VerificationStore from '~/modals/Verification/sms-store'; import styles from './account.css'; From 1ac3421f3327778306c21997e9acdcecde32ad6d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 17:16:54 +0100 Subject: [PATCH 181/382] step to select verification method --- js/src/modals/Verification/verification.js | 109 ++++++++++++++++----- js/src/views/Account/account.js | 29 ++++-- 2 files changed, 105 insertions(+), 33 deletions(-) diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index b05ed353ae8..5982933a9ec 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -20,6 +20,13 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all'; import CancelIcon from 'material-ui/svg-icons/content/clear'; import { Button, IdentityIcon, Modal } from '~/ui'; +import RadioButtons from '~/ui/Form/RadioButtons'; +import { nullableProptype } from '~/util/proptypes'; + +const methods = { + sms: { label: 'SMS Verification', key: 0, value: 'sms' }, + email: { label: 'E-mail Verification', key: 1, value: 'email' } +}; import { LOADING, @@ -39,23 +46,34 @@ import Done from './Done'; @observer export default class Verification extends Component { static propTypes = { - store: PropTypes.any.isRequired, + store: nullableProptype(PropTypes.object).isRequired, account: PropTypes.string.isRequired, + onSelectMethod: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } static phases = { // mapping (store steps -> steps) - [LOADING]: 0, - [QUERY_DATA]: 1, - [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, - [QUERY_CODE]: 3, - [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, - [DONE]: 5 + [LOADING]: 1, + [QUERY_DATA]: 2, + [POSTING_REQUEST]: 3, [POSTED_REQUEST]: 3, [REQUESTING_CODE]: 3, + [QUERY_CODE]: 4, + [POSTING_CONFIRMATION]: 5, [POSTED_CONFIRMATION]: 5, + [DONE]: 6 } + state = { + method: 'sms' + }; + render () { - const phase = Verification.phases[this.props.store.step]; - const { error, isStepValid } = this.props.store; + const { store } = this.props; + let phase = 0; let error = false; let isStepValid = true; + + if (store) { + phase = Verification.phases[store.step]; + error = store.error; + isStepValid = store.isStepValid; + } return ( { this.renderStep(phase, error) } @@ -85,7 +103,7 @@ export default class Verification extends Component { return (
{ cancel }
); } - if (phase === 5) { + if (phase === 6) { return (
{ cancel } @@ -101,16 +119,23 @@ export default class Verification extends Component { let action = () => {}; switch (phase) { - case 1: - action = store.sendRequest; + case 0: + action = () => { + const { onSelectMethod } = this.props; + const { method } = this.state; + onSelectMethod(method); + }; break; case 2: - action = store.queryCode; + action = store.sendRequest; break; case 3: - action = store.sendConfirmation; + action = store.queryCode; break; case 4: + action = store.sendConfirmation; + break; + case 5: action = store.done; break; } @@ -133,6 +158,19 @@ export default class Verification extends Component { return (

{ error }

); } + if (phase === 0) { + const { method } = this.state; + const values = Object.values(methods); + const value = values.findIndex((v) => v.value === method); + return ( + + ); + } + const { step, fee, number, isNumberValid, isVerified, hasRequested, @@ -141,13 +179,34 @@ export default class Verification extends Component { } = this.props.store; switch (phase) { - case 0: + case 1: return (

Loading Verification.

); - case 1: - const { setNumber, setConsentGiven } = this.props.store; + case 2: + const { method } = this.state; + const { setConsentGiven } = this.props.store; + + const fields = [] + if (method === 'sms') { + fields.push({ + key: 'number', + label: 'phone number in international format', + hint: 'the SMS will be sent to this number', + error: this.props.store.isNumberValid ? null : 'invalid number', + onChange: this.props.store.setNumber + }); + } else if (method === 'email') { + fields.push({ + key: 'email', + label: 'email address', + hint: 'the code will be sent to this address', + error: this.props.store.isEmailValid ? null : 'invalid email', + onChange: this.props.store.setEmail + }); + } + return ( ); - case 2: + case 3: return ( ); - case 3: + case 4: return ( ); - case 4: + case 5: return ( ); - case 5: + case 6: return ( ); @@ -183,4 +242,8 @@ export default class Verification extends Component { return null; } } + + selectMethod = (choice, i) => { + this.setState({ method: choice.value }); + } } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 240ca2f9b7f..3f262bce416 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -32,7 +32,8 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/Verification/sms-store'; +import SMSVerificationStore from '~/modals/Verification/sms-store'; +import EmailVerificationStore from '~/modals/Verification/email-store'; import styles from './account.css'; @@ -72,15 +73,6 @@ class Account extends Component { if (prevAddress !== nextAddress) { this.setVisibleAccounts(nextProps); } - - const { isTestnet } = nextProps; - if (typeof isTestnet === 'boolean' && !this.state.verificationStore) { - const { api } = this.context; - const { address } = nextProps.params; - this.setState({ - verificationStore: new VerificationStore(api, address, isTestnet) - }); - } } componentWillUnmount () { @@ -230,6 +222,7 @@ class Account extends Component { return ( ); @@ -303,6 +296,22 @@ class Account extends Component { this.setState({ showVerificationDialog: true }); } + selectVerificationMethod = (name) => { + const { isTestnet } = this.props; + if (typeof isTestnet !== 'boolean' || this.state.verificationStore) return; + + const { api } = this.context; + const { address } = this.props.params; + + let verificationStore = null; + if (name === 'sms') { + verificationStore = new SMSVerificationStore(api, address, isTestnet); + } else if (name === 'email') { + verificationStore = new EmailVerificationStore(api, address, isTestnet); + } + this.setState({ verificationStore }); + } + onVerificationClose = () => { this.setState({ showVerificationDialog: false }); } From d3fd71d9534ac540d72f7d8263e13a5e8461fa53 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 17:45:34 +0100 Subject: [PATCH 182/382] add email-specific contract, helpers, store --- js/src/3rdparty/email-verification/index.js | 53 +++++++++++++++ js/src/3rdparty/email-verification/styles.css | 20 ++++++ js/src/3rdparty/sms-verification/index.js | 13 ++++ js/src/3rdparty/sms-verification/styles.css | 20 ++++++ js/src/contracts/abi/email-verification.json | 1 + js/src/contracts/abi/index.js | 2 + js/src/modals/Verification/email-store.js | 66 +++++++++++++++++++ 7 files changed, 175 insertions(+) create mode 100644 js/src/3rdparty/email-verification/index.js create mode 100644 js/src/3rdparty/email-verification/styles.css create mode 100644 js/src/3rdparty/sms-verification/styles.css create mode 100644 js/src/contracts/abi/email-verification.json create mode 100644 js/src/modals/Verification/email-store.js diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js new file mode 100644 index 00000000000..5f4885f3be4 --- /dev/null +++ b/js/src/3rdparty/email-verification/index.js @@ -0,0 +1,53 @@ +// 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 . + +import { stringify } from 'querystring'; +import React from 'react'; + +import styles from './styles.css'; + +export const howItWorks = ( +
+

The following steps will let you prove that you control both an account and an e-mail address.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via e-mail is the solution to this puzzle.
  6. +
+
+); + +export const termsOfService = ( +
    +
  • todo
  • +
+); + +export const postToServer = (query, isTestnet = false) => { + const port = isTestnet ? 28443 : 18443; + query = stringify(query); + return fetch(`https://email-verification.parity.io:${port}/?` + query, { + method: 'POST', mode: 'cors', cache: 'no-store' + }) + .then((res) => { + return res.json().then((data) => { + if (res.ok) { + return data.message; + } + throw new Error(data.message || 'unknown error'); + }); + }); +}; diff --git a/js/src/3rdparty/email-verification/styles.css b/js/src/3rdparty/email-verification/styles.css new file mode 100644 index 00000000000..daa4c605ce8 --- /dev/null +++ b/js/src/3rdparty/email-verification/styles.css @@ -0,0 +1,20 @@ +/* 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 . +*/ + +.list li { + padding: .1em 0; +} diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index c50b2331a80..46faf084cd9 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -17,6 +17,19 @@ import { stringify } from 'querystring'; import React from 'react'; +import styles from './styles.css'; + +export const howItWorks = ( +
+

The following steps will let you prove that you control both an account and a phone number.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via SMS is the solution to this puzzle.
  6. +
+
+); + export const termsOfService = (
-
+
-
+
{ this.renderSettings() }
@@ -102,6 +102,7 @@ export default class Status extends Component {
+ +
+
+ +
+
); } From a8f428ccca44c25687d80c5f4f6815372214edc5 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 14 Dec 2016 14:37:25 +0100 Subject: [PATCH 305/382] Display capability status on statusbar --- .../UpgradeParity/{modalStore.js => store.js} | 61 +++++++++--- js/src/modals/UpgradeParity/upgradeParity.js | 51 +++++----- js/src/modals/UpgradeParity/upgradeStore.js | 67 ------------- .../views/Application/Container/container.js | 11 ++- js/src/views/Application/Status/status.css | 63 ++++++------- js/src/views/Application/Status/status.js | 94 +++++++++++++------ js/src/views/Application/application.css | 2 +- js/src/views/Application/application.js | 30 +++--- 8 files changed, 199 insertions(+), 180 deletions(-) rename js/src/modals/UpgradeParity/{modalStore.js => store.js} (56%) delete mode 100644 js/src/modals/UpgradeParity/upgradeStore.js diff --git a/js/src/modals/UpgradeParity/modalStore.js b/js/src/modals/UpgradeParity/store.js similarity index 56% rename from js/src/modals/UpgradeParity/modalStore.js rename to js/src/modals/UpgradeParity/store.js index 994e42003d9..461231fd7ec 100644 --- a/js/src/modals/UpgradeParity/modalStore.js +++ b/js/src/modals/UpgradeParity/store.js @@ -14,41 +14,70 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { action, computed, observable, transaction } from 'mobx'; +import { action, observable, transaction } from 'mobx'; import store from 'store'; const LS_UPDATE = '_parity::update'; -const A_DAY = 24 * 60 * 60 * 1000; +const A_MINUTE = 60 * 1000; +const A_DAY = 24 * 60 * A_MINUTE; const STEP_INFO = 1; const STEP_UPDATING = 2; const STEP_COMPLETED = 3; const STEP_ERROR = 4; -export default class ModalStore { - @observable closed = false; +const CHECK_INTERVAL = 1 * A_MINUTE; + +export default class Store { + @observable available = null; + @observable consensusCapability = null; + @observable closed = true; @observable error = null; @observable step = 0; - @observable upgrade = null; + @observable upgrading = null; + @observable version = null; - constructor (upgradeStore) { - this.upgrade = upgradeStore; + constructor (api) { + this._api = api; this.loadStorage(); + this.checkUpgrade(); + + setInterval(this.checkUpgrade, CHECK_INTERVAL); } - @computed get showUpgrade () { - return !this.closed && Date.now() >= this.remindAt; + @action setUpgrading () { + transaction(() => { + this.upgrading = this.available; + this.setStep(STEP_UPDATING); + }); } - @action closeModal = () => { + @action setVersions (available, version, consensusCapability) { transaction(() => { - this.closed = true; - this.setStep(STEP_INFO); + this.available = available; + this.consensusCapability = consensusCapability; + this.version = version; }); } + checkUpgrade = () => { + Promise + .all([ + this._api.parity.upgradeReady(), + this._api.parity.consensusCapability(), + this._api.parity.versionInfo() + ]) + .then(([available, consensusCapability, version]) => { + console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability); + this.setVersions(available, version, consensusCapability); + }) + .catch((error) => { + console.warn('checkUpgrade', error); + }); + } + @action loadStorage = () => { const values = store.get(LS_UPDATE) || {}; @@ -57,6 +86,10 @@ export default class ModalStore { return values; } + @action openModal = () => { + this.closed = false; + } + @action setStep = (step, error = null) => { transaction(() => { this.error = error; @@ -70,9 +103,9 @@ export default class ModalStore { } @action upgradeNow = () => { - this.setStep(STEP_UPDATING); + this.setUpgrading(); - this.upgrade + return this._api.parity .executeUpgrade() .then((result) => { if (!result) { diff --git a/js/src/modals/UpgradeParity/upgradeParity.js b/js/src/modals/UpgradeParity/upgradeParity.js index c1ba9d39598..7f13d87ea87 100644 --- a/js/src/modals/UpgradeParity/upgradeParity.js +++ b/js/src/modals/UpgradeParity/upgradeParity.js @@ -22,8 +22,7 @@ import { Button } from '~/ui'; import { CancelIcon, DoneIcon, NextIcon, SnoozeIcon } from '~/ui/Icons'; import Modal, { Busy, Completed } from '~/ui/Modal'; -import ModalStore, { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './modalStore'; -import UpgradeStore from './upgradeStore'; +import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './store'; import styles from './upgradeParity.css'; @observer @@ -32,17 +31,21 @@ export default class UpgradeParity extends Component { api: PropTypes.object.isRequired }; - store = new ModalStore(new UpgradeStore(this.context.api)); + static propTypes = { + store: PropTypes.object.isRequired + } render () { - if (!this.store.upgrade.available || !this.store.showUpgrade) { + const { store } = this.props; + + if (!store.showUpgrade) { return null; } return ( , - this.store.step === STEP_ERROR + store.step === STEP_ERROR ? @@ -65,6 +68,8 @@ export default class UpgradeParity extends Component { } renderActions () { + const { store } = this.props; + const closeButton =
- { error.message } + { store.error.message }
); diff --git a/js/src/modals/UpgradeParity/upgradeStore.js b/js/src/modals/UpgradeParity/upgradeStore.js deleted file mode 100644 index 8ac2f24b0ae..00000000000 --- a/js/src/modals/UpgradeParity/upgradeStore.js +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2015, 2016 Parity Technologies (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 . - -import { action, observable, transaction } from 'mobx'; - -const CHECK_INTERVAL = 1 * 60 * 1000; - -export default class UpgradeStore { - @observable available = null; - @observable consensusCapability = null; - @observable upgrading = null; - @observable version = null; - - constructor (api) { - this._api = api; - - this.checkUpgrade(); - setInterval(this.checkUpgrade, CHECK_INTERVAL); - } - - @action setUpgrading () { - this.upgrading = this.available; - } - - @action setVersions (available, version, consensusCapability) { - transaction(() => { - this.available = available; - this.consensusCapability = consensusCapability; - this.version = version; - }); - } - - checkUpgrade = () => { - Promise - .all([ - this._api.parity.upgradeReady(), - this._api.parity.consensusCapability(), - this._api.parity.versionInfo() - ]) - .then(([available, consensusCapability, version]) => { - console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability); - this.setVersions(available, version, consensusCapability); - }) - .catch((error) => { - console.warn('checkUpgrade', error); - }); - } - - executeUpgrade = () => { - this.setUpgrading(); - - return this._api.parity.executeUpgrade(); - } -} diff --git a/js/src/views/Application/Container/container.js b/js/src/views/Application/Container/container.js index 275f8999c6c..421483e54ce 100644 --- a/js/src/views/Application/Container/container.js +++ b/js/src/views/Application/Container/container.js @@ -29,20 +29,23 @@ export default class Container extends Component { static propTypes = { children: PropTypes.node.isRequired, onCloseFirstRun: PropTypes.func, - showFirstRun: PropTypes.bool + showFirstRun: PropTypes.bool, + upgradeStore: PropTypes.object.isRequired }; render () { const { muiTheme } = this.context; - const { children, onCloseFirstRun, showFirstRun } = this.props; + const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props; return ( - + - + { children } diff --git a/js/src/views/Application/Status/status.css b/js/src/views/Application/Status/status.css index 8721bc4c2b8..4eeb4b918e3 100644 --- a/js/src/views/Application/Status/status.css +++ b/js/src/views/Application/Status/status.css @@ -14,55 +14,52 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .status { - position: fixed; + align-items: center; + background-color: rgba(0, 0, 0, 0.8); bottom: 0; + color: #ccc; + display: flex; + font-size: 0.75em; left: 0; + padding: .4em .5em; + position: fixed; right: 0; z-index: 1000; - display: flex; - align-items: center; - padding: .4em .5em; - font-size: x-small; - color: #ccc; - background-color: rgba(0, 0, 0, 0.8); -} - -.enode { - word-wrap: break-word; -} - -.enode > * { - display: inline-block; - margin: 0 .25em; - vertical-align: middle; -} -.enode > :last-child { - margin-right: 0; } .netinfo { - display: flex; - flex-grow: 1; - align-items: center; color: #ddd; -} + flex-grow: 1; + text-align: right; + vertical-align: middle; + text-align: right; -.netinfo > * { - margin-left: 1em; + div { + display: inline-block; + margin-left: 1em; + } } .network { - padding: 0.25em 0.5em; - border-radius: .4em; + border-radius: 0.4em; line-height: 1.2; + padding: 0.25em 0.5em; text-transform: uppercase; -} -.networklive { - background: rgb(0, 136, 0); + &.live { + background: rgb(0, 136, 0); + } + + &.test { + background: rgb(136, 0, 0); + } } -.networktest { - background: rgb(136, 0, 0); +.upgrade { + div { + display: inline-block; + margin-left: 1em; + } } diff --git a/js/src/views/Application/Status/status.js b/js/src/views/Application/Status/status.js index 8d772483880..1aded4b887e 100644 --- a/js/src/views/Application/Status/status.js +++ b/js/src/views/Application/Status/status.js @@ -15,78 +15,118 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { BlockStatus } from '~/ui'; -import CopyToClipboard from '~/ui/CopyToClipboard'; import styles from './status.css'; class Status extends Component { static propTypes = { - blockNumber: PropTypes.object.isRequired, clientVersion: PropTypes.string, - enode: PropTypes.string, - netPeers: PropTypes.object, + isTest: PropTypes.bool, netChain: PropTypes.string, - isTest: PropTypes.bool + netPeers: PropTypes.object, + upgradeStore: PropTypes.object.isRequired } render () { - const { blockNumber, clientVersion, netChain, netPeers, isTest } = this.props; - const netStyle = `${styles.network} ${styles[isTest ? 'networktest' : 'networklive']}`; - - if (!blockNumber) { - return null; - } + const { clientVersion, isTest, netChain, netPeers } = this.props; return (
{ clientVersion }
+
+ { this.renderConsensus() } + { this.renderUpgradeButton() } +
-
- { isTest ? 'test' : netChain } +
+ { netChain }
{ netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
- { this.renderEnode() }
); } - renderEnode () { - const { enode } = this.props; + renderConsensus () { + const { upgradeStore } = this.props; - if (!enode) { - return null; + if (upgradeStore.consensusCapability === 'capable') { + return ( +
+ +
+ ); + } else if (upgradeStore.consensusCapability.capableUntil) { + return ( +
+ +
+ ); + } else if (upgradeStore.consensusCapability.incapableSince) { + return ( +
+ +
+ ); } - const [protocol, rest] = enode.split('://'); - const [id, host] = rest.split('@'); - const abbreviated = `${protocol}://${id.slice(0, 3)}…${id.slice(-3)}@${host}`; + return ( +
+ +
+ ); + } + + renderUpgradeButton () { + const { upgradeStore } = this.props; + + if (!upgradeStore.available) { + return null; + } return ( -
- -
{ abbreviated }
+
); } } function mapStateToProps (state) { - const { blockNumber, clientVersion, enode, netPeers, netChain, isTest } = state.nodeStatus; + const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus; return { - blockNumber, clientVersion, - enode, netPeers, netChain, isTest diff --git a/js/src/views/Application/application.css b/js/src/views/Application/application.css index e6f97f10526..9d957d6c913 100644 --- a/js/src/views/Application/application.css +++ b/js/src/views/Application/application.css @@ -22,5 +22,5 @@ } .content { - padding-bottom: 1em; + padding-bottom: 1.25em; } diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index c536f10dfe4..49d299661ee 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -19,6 +19,8 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { observer } from 'mobx-react'; +import UpgradeStore from '~/modals/UpgradeParity/store'; + import Connection from '../Connection'; import ParityBar from '../ParityBar'; @@ -42,14 +44,15 @@ class Application extends Component { } static propTypes = { + blockNumber: PropTypes.object, children: PropTypes.node, - netChain: PropTypes.string, isTest: PropTypes.bool, - pending: PropTypes.array, - blockNumber: PropTypes.object + netChain: PropTypes.string, + pending: PropTypes.array } store = new Store(this.context.api); + upgradeStore = new UpgradeStore(this.context.api); render () { const [root] = (window.location.hash || '').replace('#/', '').split('/'); @@ -71,12 +74,13 @@ class Application extends Component { } renderApp () { - const { children, pending, netChain, isTest, blockNumber } = this.props; + const { blockNumber, children, pending, netChain, isTest } = this.props; return ( + upgradeStore={ this.upgradeStore } + onCloseFirstRun={ this.store.closeFirstrun } + showFirstRun={ this.store.firstrunVisible }> { children }
- { blockNumber ? () : null } + { + blockNumber + ? + : null + } ); @@ -102,16 +110,16 @@ class Application extends Component { } function mapStateToProps (state) { - const { netChain, isTest, blockNumber } = state.nodeStatus; + const { blockNumber, netChain, isTest } = state.nodeStatus; const { hasAccounts } = state.personal; const { pending } = state.signer; return { + blockNumber, hasAccounts, - netChain, isTest, - pending, - blockNumber + netChain, + pending }; } From 366f9a8cd73dc864d197e7d95d984ff5bdfd2f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Dec 2016 14:18:48 +0100 Subject: [PATCH 306/382] Fixing tests --- Cargo.lock | 20 ++- signer/src/tests/mod.rs | 370 +++++++++++++++++++++------------------- 2 files changed, 207 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b85ac56461..ad23083aea4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,7 +733,7 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1310,7 +1310,7 @@ dependencies = [ "ethcore-rpc 1.5.0", "ethcore-signer 1.5.0", "ethcore-util 1.5.0", - "futures 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1347,6 +1347,17 @@ dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.3.5" @@ -1609,7 +1620,7 @@ dependencies = [ "ethcore-bigint 0.1.2", "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", - "futures 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc-client 1.4.0", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2121,7 +2132,7 @@ dependencies = [ "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" -"checksum futures 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd89497091f8c5d3a65c6b4baf6d2f0731937a7c9217d2f89141b21437a9d96" +"checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897" "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" @@ -2182,6 +2193,7 @@ dependencies = [ "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "" +"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" diff --git a/signer/src/tests/mod.rs b/signer/src/tests/mod.rs index 767f84a62c2..ab46a0e6f9c 100644 --- a/signer/src/tests/mod.rs +++ b/signer/src/tests/mod.rs @@ -17,7 +17,6 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; -#[cfg(test)] use devtools::http_client; use devtools::RandomTempPath; @@ -61,189 +60,202 @@ pub fn serve() -> (Server, usize, GuardedAuthCodes) { }) } -#[test] -fn should_reject_invalid_host() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET / HTTP/1.1\r\n\ - Host: test:8180\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - assert!(response.body.contains("URL Blocked")); - http_client::assert_security_headers_present(&response.headers, None); +/// Test a single request to running server +pub fn request(server: Server, request: &str) -> http_client::Response { + http_client::request(server.addr(), request) } -#[test] -fn should_allow_home_parity_host() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET http://home.parity/ HTTP/1.1\r\n\ - Host: home.parity\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - http_client::assert_security_headers_present(&response.headers, None); -} +#[cfg(test)] +mod testing { + use std::time; + use util::Hashable; + use devtools::http_client; + use super::{serve, request}; + + #[test] + fn should_reject_invalid_host() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: test:8180\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + assert!(response.body.contains("URL Blocked")); + http_client::assert_security_headers_present(&response.headers, None); + } -#[test] -fn should_serve_styles_even_on_disallowed_domain() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - GET /styles.css HTTP/1.1\r\n\ - Host: test:8180\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - http_client::assert_security_headers_present(&response.headers, None); -} + #[test] + fn should_allow_home_parity_host() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + GET http://home.parity/ HTTP/1.1\r\n\ + Host: home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + http_client::assert_security_headers_present(&response.headers, None); + } -#[test] -fn should_return_200_ok_for_connect_requests() { - // given - let server = serve().0; - - // when - let response = request(server, - "\ - CONNECT home.parity:8080 HTTP/1.1\r\n\ - Host: home.parity\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); -} + #[test] + fn should_serve_styles_even_on_disallowed_domain() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + GET /styles.css HTTP/1.1\r\n\ + Host: test:8180\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + http_client::assert_security_headers_present(&response.headers, None); + } -#[test] -fn should_block_if_authorization_is_incorrect() { - // given - let (server, port, _) = serve(); - - // when - let response = request(server, - &format!("\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:{}\r\n\ - Connection: Upgrade\r\n\ - Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ - Sec-WebSocket-Protocol: wrong\r\n\ - Sec-WebSocket-Version: 13\r\n\ - \r\n\ - {{}} - ", port) - ); - - // then - assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - http_client::assert_security_headers_present(&response.headers, None); -} + #[test] + fn should_return_200_ok_for_connect_requests() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + CONNECT home.parity:8080 HTTP/1.1\r\n\ + Host: home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + } -#[test] -fn should_allow_if_authorization_is_correct() { - // given - let (server, port, mut authcodes) = serve(); - let code = authcodes.generate_new().unwrap().replace("-", ""); - authcodes.to_file(&authcodes.path).unwrap(); - let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - - // when - let response = request(server, - &format!("\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:{}\r\n\ - Connection: Close\r\n\ - Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ - Sec-WebSocket-Protocol: {:?}_{}\r\n\ - Sec-WebSocket-Version: 13\r\n\ - \r\n\ - {{}} - ", - port, - format!("{}:{}", code, timestamp).sha3(), - timestamp, - ) - ); - - // then - assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned()); -} + #[test] + fn should_block_if_authorization_is_incorrect() { + // given + let (server, port, _) = serve(); + + // when + let response = request(server, + &format!("\ + GET / HTTP/1.1\r\n\ + Host: 127.0.0.1:{}\r\n\ + Connection: Upgrade\r\n\ + Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ + Sec-WebSocket-Protocol: wrong\r\n\ + Sec-WebSocket-Version: 13\r\n\ + \r\n\ + {{}} + ", port) + ); + + // then + assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + http_client::assert_security_headers_present(&response.headers, None); + } -#[test] -fn should_allow_initial_connection_but_only_once() { - // given - let (server, port, authcodes) = serve(); - let code = "initial"; - let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - assert!(authcodes.is_empty()); - - // when - let response1 = http_client::request(server.addr(), - &format!("\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:{}\r\n\ - Connection: Close\r\n\ - Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ - Sec-WebSocket-Protocol:{:?}_{}\r\n\ - Sec-WebSocket-Version: 13\r\n\ - \r\n\ - {{}} - ", - port, - format!("{}:{}", code, timestamp).sha3(), - timestamp, - ) - ); - let response2 = http_client::request(server.addr(), - &format!("\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:{}\r\n\ - Connection: Close\r\n\ - Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ - Sec-WebSocket-Protocol:{:?}_{}\r\n\ - Sec-WebSocket-Version: 13\r\n\ - \r\n\ - {{}} - ", - port, - format!("{}:{}", code, timestamp).sha3(), - timestamp, - ) - ); - - - // then - assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned()); - assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - http_client::assert_security_headers_present(&response2.headers, None); + #[test] + fn should_allow_if_authorization_is_correct() { + // given + let (server, port, mut authcodes) = serve(); + let code = authcodes.generate_new().unwrap().replace("-", ""); + authcodes.to_file(&authcodes.path).unwrap(); + let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs(); + + // when + let response = request(server, + &format!("\ + GET / HTTP/1.1\r\n\ + Host: 127.0.0.1:{}\r\n\ + Connection: Close\r\n\ + Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ + Sec-WebSocket-Protocol: {:?}_{}\r\n\ + Sec-WebSocket-Version: 13\r\n\ + \r\n\ + {{}} + ", + port, + format!("{}:{}", code, timestamp).sha3(), + timestamp, + ) + ); + + // then + assert_eq!(response.status, "HTTP/1.1 101 Switching Protocols".to_owned()); + } + + #[test] + fn should_allow_initial_connection_but_only_once() { + // given + let (server, port, authcodes) = serve(); + let code = "initial"; + let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_secs(); + assert!(authcodes.is_empty()); + + // when + let response1 = http_client::request(server.addr(), + &format!("\ + GET / HTTP/1.1\r\n\ + Host: 127.0.0.1:{}\r\n\ + Connection: Close\r\n\ + Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ + Sec-WebSocket-Protocol:{:?}_{}\r\n\ + Sec-WebSocket-Version: 13\r\n\ + \r\n\ + {{}} + ", + port, + format!("{}:{}", code, timestamp).sha3(), + timestamp, + ) + ); + let response2 = http_client::request(server.addr(), + &format!("\ + GET / HTTP/1.1\r\n\ + Host: 127.0.0.1:{}\r\n\ + Connection: Close\r\n\ + Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\ + Sec-WebSocket-Protocol:{:?}_{}\r\n\ + Sec-WebSocket-Version: 13\r\n\ + \r\n\ + {{}} + ", + port, + format!("{}:{}", code, timestamp).sha3(), + timestamp, + ) + ); + + + // then + assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned()); + assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + http_client::assert_security_headers_present(&response2.headers, None); + } } From 339b889ac4477221e89cda12a0eb7aab7caa07a5 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Wed, 14 Dec 2016 22:15:19 +0800 Subject: [PATCH 307/382] spelling (#3839) --- parity/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/run.rs b/parity/run.rs index e59df7039ef..34656784250 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -175,7 +175,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // display info about used pruning algorithm info!("Starting {}", Colour::White.bold().paint(version())); - info!("State DB configuation: {}{}{}", + info!("State DB configuration: {}{}{}", Colour::White.bold().paint(algorithm.as_str()), match fat_db { true => Colour::White.bold().paint(" +Fat").to_string(), From 7330612bfbccd2880dc41503d17807587d2040b3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 14 Dec 2016 15:56:01 +0100 Subject: [PATCH 308/382] Toggle upgrade modal via upgrade link --- js/src/modals/UpgradeParity/store.js | 80 ++++++----- js/src/modals/UpgradeParity/store.spec.js | 58 ++++++++ js/src/modals/UpgradeParity/upgradeParity.css | 10 +- js/src/modals/UpgradeParity/upgradeParity.js | 131 ++++++++++-------- js/src/ui/Modal/Title/title.js | 12 +- js/src/ui/Modal/modal.js | 4 +- 6 files changed, 199 insertions(+), 96 deletions(-) create mode 100644 js/src/modals/UpgradeParity/store.spec.js diff --git a/js/src/modals/UpgradeParity/store.js b/js/src/modals/UpgradeParity/store.js index 461231fd7ec..466a588e6e1 100644 --- a/js/src/modals/UpgradeParity/store.js +++ b/js/src/modals/UpgradeParity/store.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { action, observable, transaction } from 'mobx'; +import { action, computed, observable, transaction } from 'mobx'; import store from 'store'; const LS_UPDATE = '_parity::update'; @@ -22,10 +22,10 @@ const LS_UPDATE = '_parity::update'; const A_MINUTE = 60 * 1000; const A_DAY = 24 * 60 * A_MINUTE; -const STEP_INFO = 1; -const STEP_UPDATING = 2; -const STEP_COMPLETED = 3; -const STEP_ERROR = 4; +const STEP_INFO = 0; +const STEP_UPDATING = 1; +const STEP_COMPLETED = 2; +const STEP_ERROR = 3; const CHECK_INTERVAL = 1 * A_MINUTE; @@ -34,6 +34,7 @@ export default class Store { @observable consensusCapability = null; @observable closed = true; @observable error = null; + @observable remindAt = 0; @observable step = 0; @observable upgrading = null; @observable version = null; @@ -47,37 +48,17 @@ export default class Store { setInterval(this.checkUpgrade, CHECK_INTERVAL); } - @action setUpgrading () { - transaction(() => { - this.upgrading = this.available; - this.setStep(STEP_UPDATING); - }); + @computed get isVisible () { + return !this.closed && Date.now() >= this.remindAt; } - @action setVersions (available, version, consensusCapability) { + @action closeModal = () => { transaction(() => { - this.available = available; - this.consensusCapability = consensusCapability; - this.version = version; + this.closed = true; + this.setStep(0, null); }); } - checkUpgrade = () => { - Promise - .all([ - this._api.parity.upgradeReady(), - this._api.parity.consensusCapability(), - this._api.parity.versionInfo() - ]) - .then(([available, consensusCapability, version]) => { - console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability); - this.setVersions(available, version, consensusCapability); - }) - .catch((error) => { - console.warn('checkUpgrade', error); - }); - } - @action loadStorage = () => { const values = store.get(LS_UPDATE) || {}; @@ -93,7 +74,22 @@ export default class Store { @action setStep = (step, error = null) => { transaction(() => { this.error = error; - this.setp = step; + this.step = step; + }); + } + + @action setUpgrading () { + transaction(() => { + this.upgrading = this.available; + this.setStep(STEP_UPDATING, null); + }); + } + + @action setVersions (available, version, consensusCapability) { + transaction(() => { + this.available = available; + this.consensusCapability = consensusCapability; + this.version = version; }); } @@ -112,7 +108,7 @@ export default class Store { throw new Error('Unable to complete update'); } - this.setStep(STEP_COMPLETED); + this.setStep(STEP_COMPLETED, null); }) .catch((error) => { console.error('upgradeNow', error); @@ -120,6 +116,26 @@ export default class Store { this.setStep(STEP_ERROR, error); }); } + + checkUpgrade = () => { + if (!this._api) { + return; + } + + Promise + .all([ + this._api.parity.upgradeReady(), + this._api.parity.consensusCapability(), + this._api.parity.versionInfo() + ]) + .then(([available, consensusCapability, version]) => { + console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability); + this.setVersions(available, version, consensusCapability); + }) + .catch((error) => { + console.warn('checkUpgrade', error); + }); + } } export { diff --git a/js/src/modals/UpgradeParity/store.spec.js b/js/src/modals/UpgradeParity/store.spec.js new file mode 100644 index 00000000000..1e0111284b9 --- /dev/null +++ b/js/src/modals/UpgradeParity/store.spec.js @@ -0,0 +1,58 @@ +// Copyright 2015, 2016 Parity Technologies (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 . + +import Store from './store'; + +let store; + +describe('modals/UpgradeParity/store', () => { + describe('@actions', () => { + beforeEach(() => { + store = new Store(); + }); + + describe('openModal & closeModal', () => { + it('toggles between the closed states', () => { + expect(store.closed).to.be.true; + store.openModal(); + expect(store.closed).to.be.false; + store.closeModal(); + expect(store.closed).to.be.true; + }); + + it('resets the step state upon closing', () => { + store.setStep(5, 'soem error'); + store.closeModal(); + expect(store.step).to.equal(0); + expect(store.error).to.be.null; + }); + }); + + describe('setStep', () => { + it('sets the step as provided', () => { + expect(store.step).to.equal(0); + store.setStep(3); + expect(store.step).to.equal(3); + }); + + it('sets the error when provided', () => { + expect(store.error).to.be.null; + store.setStep(3, new Error('some error')); + expect(store.error).to.match(/some error/); + }); + }); + }); +}); diff --git a/js/src/modals/UpgradeParity/upgradeParity.css b/js/src/modals/UpgradeParity/upgradeParity.css index 962bbc58cb1..bca0fe47d46 100644 --- a/js/src/modals/UpgradeParity/upgradeParity.css +++ b/js/src/modals/UpgradeParity/upgradeParity.css @@ -16,11 +16,17 @@ */ .error { - padding-top: 1.5em; + padding-top: 1.25em; } .infoStep { div+div { - padding-top: 1.5em; + padding-top: 1.25em; } } + +.version { + display: inline; + opacity: 0.5; + white-space: normal; +} diff --git a/js/src/modals/UpgradeParity/upgradeParity.js b/js/src/modals/UpgradeParity/upgradeParity.js index 7f13d87ea87..3e67c42f29a 100644 --- a/js/src/modals/UpgradeParity/upgradeParity.js +++ b/js/src/modals/UpgradeParity/upgradeParity.js @@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { Button } from '~/ui'; -import { CancelIcon, DoneIcon, NextIcon, SnoozeIcon } from '~/ui/Icons'; +import { CancelIcon, DoneIcon, NextIcon } from '~/ui/Icons'; import Modal, { Busy, Completed } from '~/ui/Modal'; import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './store'; @@ -38,7 +38,7 @@ export default class UpgradeParity extends Component { render () { const { store } = this.props; - if (!store.showUpgrade) { + if (!store.isVisible) { return null; } @@ -55,11 +55,11 @@ export default class UpgradeParity extends Component { defaultMessage='upgrading parity' />, store.step === STEP_ERROR ? - : + : ] } visible> { this.renderStep() } @@ -73,6 +73,7 @@ export default class UpgradeParity extends Component { const closeButton =
- ); - } else if (store.consensusCapability.capableUntil) { - consensusInfo = ( -
- -
- ); - } else if (store.consensusCapability.incapableSince) { - consensusInfo = ( -
- -
- ); - } - return (
@@ -174,11 +136,11 @@ export default class UpgradeParity extends Component { id='upgradeParity.info.upgrade' defaultMessage='A new version of Parity, version {newversion} is available as an upgrade from your current version {currentversion}' values={ { - currentversion, - newversion + currentversion:
{ currentversion }
, + newversion:
{ newversion }
} } />
- { consensusInfo } + { this.renderConsensusInfo() }
); @@ -190,7 +152,7 @@ export default class UpgradeParity extends Component { id='upgradeParity.busy' defaultMessage='Your upgrade to Parity {newversion} is currently in progress' values={ { - newversion + newversion:
{ newversion }
} } /> } /> ); @@ -202,7 +164,7 @@ export default class UpgradeParity extends Component { id='upgradeParity.completed' defaultMessage='Your upgrade to Parity {newversion} has been successfully completed.' values={ { - newversion + newversion:
{ newversion }
} } /> ); @@ -215,7 +177,7 @@ export default class UpgradeParity extends Component { id='upgradeParity.failed' defaultMessage='Your upgrade to Parity {newversion} has failed with an error.' values={ { - newversion + newversion:
{ newversion }
} } />
@@ -226,8 +188,63 @@ export default class UpgradeParity extends Component { } } - renderVersion (versionInfo) { - const { track, version } = versionInfo; + renderConsensusInfo () { + const { store } = this.props; + const { consensusCapability } = store; + + if (consensusCapability) { + if (consensusCapability === 'capable') { + return ( +
+ +
+ ); + } else if (consensusCapability.capableUntil) { + return ( +
+ +
+ ); + } else if (consensusCapability.incapableSince) { + return ( +
+ +
+ ); + } + } + + return ( +
+ +
+ ); + } + + formatVersion (struct) { + if (!struct || !struct.version) { + return ( + + ); + } + + const { track, version } = struct.version; return `${version.major}.${version.minor}.${version.patch}-${track}`; } diff --git a/js/src/ui/Modal/Title/title.js b/js/src/ui/Modal/Title/title.js index 14308ee0b4a..920d1b13c50 100644 --- a/js/src/ui/Modal/Title/title.js +++ b/js/src/ui/Modal/Title/title.js @@ -36,7 +36,13 @@ export default class Title extends Component { return (
-

{ steps ? steps[current] : title }

+

+ { + steps + ? steps[current] + : title + } +

{ this.renderSteps() } { this.renderWaiting() }
@@ -63,10 +69,10 @@ export default class Title extends Component { renderTimeline () { const { steps } = this.props; - return steps.map((label) => { + return steps.map((label, index) => { return ( + key={ index }> { label } diff --git a/js/src/ui/Modal/modal.js b/js/src/ui/Modal/modal.js index f72c3ffb1b5..0b56692a8a1 100644 --- a/js/src/ui/Modal/modal.js +++ b/js/src/ui/Modal/modal.js @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { Dialog } from 'material-ui'; import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { Dialog } from 'material-ui'; import { nodeOrStringProptype } from '~/util/proptypes'; @@ -51,7 +51,7 @@ class Modal extends Component { render () { const { muiTheme } = this.context; - const { actions, busy, className, current, children, compact, steps, waiting, title, visible, settings } = this.props; + const { actions, busy, children, className, current, compact, settings, steps, title, visible, waiting } = this.props; const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed); const header = ( Date: Wed, 14 Dec 2016 16:42:26 +0100 Subject: [PATCH 309/382] Add lint:css --- js/.stylelintrc.json | 8 ++++++++ js/package.json | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 js/.stylelintrc.json diff --git a/js/.stylelintrc.json b/js/.stylelintrc.json new file mode 100644 index 00000000000..9248483c6ed --- /dev/null +++ b/js/.stylelintrc.json @@ -0,0 +1,8 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "selector-pseudo-class-no-unknown": [ + true, { "ignorePseudoClasses": ["global"] } + ] + } +} diff --git a/js/package.json b/js/package.json index 8581c9cb692..8c28b8c5667 100644 --- a/js/package.json +++ b/js/package.json @@ -38,8 +38,10 @@ "start:app": "node webpack/dev.server", "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", - "lint": "eslint --ignore-path .gitignore ./src/", - "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", + "lint": "npm run lint:css && npm run lint:js", + "lint:css": "stylelint ./src/**/*.css", + "lint:js": "eslint --ignore-path .gitignore ./src/", + "lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/", "test": "NODE_ENV=test mocha 'src/**/*.spec.js'", "test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'", "test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'", @@ -118,6 +120,8 @@ "sinon-as-promised": "4.0.2", "sinon-chai": "2.8.0", "style-loader": "0.13.1", + "stylelint": "7.6.0", + "stylelint-config-standard": "15.0.0", "url-loader": "0.5.7", "webpack": "2.1.0-beta.27", "webpack-dev-middleware": "1.8.4", From 6e51b23e30029b6df5f4b1c9190fbffb8f2c1efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Wed, 14 Dec 2016 20:07:33 +0100 Subject: [PATCH 310/382] Adding traces tests --- ethcore/src/client/test_client.rs | 11 +- ethcore/src/types/trace_types/localized.rs | 2 +- rpc/src/v1/tests/mocked/mod.rs | 7 +- rpc/src/v1/tests/mocked/traces.rs | 145 +++++++++++++++++++++ 4 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 rpc/src/v1/tests/mocked/traces.rs diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 44954f99daa..cab70347176 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -90,6 +90,8 @@ pub struct TestBlockChainClient { pub ancient_block: RwLock<Option<(H256, u64)>>, /// First block info. pub first_block: RwLock<Option<(H256, u64)>>, + /// Traces to return + pub traces: RwLock<Option<Vec<LocalizedTrace>>>, } /// Used for generating test client blocks. @@ -151,6 +153,7 @@ impl TestBlockChainClient { latest_block_timestamp: RwLock::new(10_000_000), ancient_block: RwLock::new(None), first_block: RwLock::new(None), + traces: RwLock::new(None), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().clone(); @@ -642,19 +645,19 @@ impl BlockChainClient for TestBlockChainClient { } fn filter_traces(&self, _filter: TraceFilter) -> Option<Vec<LocalizedTrace>> { - unimplemented!(); + self.traces.read().clone() } fn trace(&self, _trace: TraceId) -> Option<LocalizedTrace> { - unimplemented!(); + self.traces.read().clone().and_then(|vec| vec.into_iter().next()) } fn transaction_traces(&self, _trace: TransactionId) -> Option<Vec<LocalizedTrace>> { - unimplemented!(); + self.traces.read().clone() } fn block_traces(&self, _trace: BlockId) -> Option<Vec<LocalizedTrace>> { - unimplemented!(); + self.traces.read().clone() } fn queue_transactions(&self, transactions: Vec<Bytes>, _peer_id: usize) { diff --git a/ethcore/src/types/trace_types/localized.rs b/ethcore/src/types/trace_types/localized.rs index 57abea362d9..f65c4741529 100644 --- a/ethcore/src/types/trace_types/localized.rs +++ b/ethcore/src/types/trace_types/localized.rs @@ -21,7 +21,7 @@ use super::trace::{Action, Res}; use header::BlockNumber; /// Localized trace. -#[derive(Debug, PartialEq, Binary)] +#[derive(Debug, PartialEq, Clone, Binary)] pub struct LocalizedTrace { /// Type of action performed by a transaction. pub action: Action, diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index 0ba0fc95c98..0dbaa38f67a 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -18,13 +18,14 @@ //! method calls properly. mod eth; +mod manage_network; mod net; -mod web3; -mod personal; mod parity; mod parity_accounts; mod parity_set; +mod personal; mod rpc; mod signer; mod signing; -mod manage_network; +mod traces; +mod web3; diff --git a/rpc/src/v1/tests/mocked/traces.rs b/rpc/src/v1/tests/mocked/traces.rs new file mode 100644 index 00000000000..f9a9baa0090 --- /dev/null +++ b/rpc/src/v1/tests/mocked/traces.rs @@ -0,0 +1,145 @@ +// Copyright 2015, 2016 Parity Technologies (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 <http://www.gnu.org/licenses/>. + +use std::sync::Arc; + +use ethcore::executed::{CallType, Executed}; +use ethcore::trace::trace::{Action, Res, Call}; +use ethcore::trace::LocalizedTrace; +use ethcore::client::{TestBlockChainClient}; + +use jsonrpc_core::{IoHandler, GenericIoHandler}; +use v1::tests::helpers::{TestMinerService}; +use v1::{Traces, TracesClient}; + +struct Tester { + _client: Arc<TestBlockChainClient>, + _miner: Arc<TestMinerService>, + io: IoHandler, +} + +fn io() -> Tester { + let client = Arc::new(TestBlockChainClient::new()); + *client.traces.write() = Some(vec![LocalizedTrace { + action: Action::Call(Call { + from: 0xf.into(), + to: 0x10.into(), + value: 0x1.into(), + gas: 0x100.into(), + input: vec![1, 2, 3], + call_type: CallType::Call, + }), + result: Res::None, + subtraces: 0, + trace_address: vec![0], + transaction_number: 0, + transaction_hash: 5.into(), + block_number: 10, + block_hash: 10.into(), + }]); + *client.execution_result.write() = Some(Ok(Executed { + gas: 20_000.into(), + gas_used: 10_000.into(), + refunded: 0.into(), + cumulative_gas_used: 10_000.into(), + logs: vec![], + contracts_created: vec![], + output: vec![1, 2, 3], + trace: vec![], + vm_trace: None, + state_diff: None, + })); + let miner = Arc::new(TestMinerService::default()); + let traces = TracesClient::new(&client, &miner); + let io = IoHandler::new(); + io.add_delegate(traces.to_delegate()); + + Tester { + _client: client, + _miner: miner, + io: io, + } +} + +#[test] +fn rpc_trace_filter() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_filter","params": [{}],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"action":{"callType":"call","from":"0x000000000000000000000000000000000000000f","gas":"0x100","input":"0x010203","to":"0x0000000000000000000000000000000000000010","value":"0x1"},"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000a","blockNumber":10,"result":null,"subtraces":0,"traceAddress":[0],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionPosition":0,"type":"call"}],"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_block() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_block","params": ["0x10"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"action":{"callType":"call","from":"0x000000000000000000000000000000000000000f","gas":"0x100","input":"0x010203","to":"0x0000000000000000000000000000000000000010","value":"0x1"},"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000a","blockNumber":10,"result":null,"subtraces":0,"traceAddress":[0],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionPosition":0,"type":"call"}],"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_transaction() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_transaction","params":["0x0000000000000000000000000000000000000000000000000000000000000005"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"action":{"callType":"call","from":"0x000000000000000000000000000000000000000f","gas":"0x100","input":"0x010203","to":"0x0000000000000000000000000000000000000010","value":"0x1"},"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000a","blockNumber":10,"result":null,"subtraces":0,"traceAddress":[0],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionPosition":0,"type":"call"}],"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_get() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_get","params":["0x0000000000000000000000000000000000000000000000000000000000000005", ["0","0","0"]],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"action":{"callType":"call","from":"0x000000000000000000000000000000000000000f","gas":"0x100","input":"0x010203","to":"0x0000000000000000000000000000000000000010","value":"0x1"},"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000a","blockNumber":10,"result":null,"subtraces":0,"traceAddress":[0],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionPosition":0,"type":"call"},"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_call() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_call","params":[{}, ["stateDiff", "vmTrace", "trace"]],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"output":"0x010203","stateDiff":null,"trace":[],"vmTrace":null},"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_raw_transaction() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_rawTransaction","params":["0xf869018609184e72a0008276c094d46e8dd67c5d32be8058bb8eb970870f07244567849184e72a801ba0617f39c1a107b63302449c476d96a6cb17a5842fc98ff0c5bcf4d5c4d8166b95a009fdb6097c6196b9bbafc3a59f02f38d91baeef23d0c60a8e4f23c7714cea3a9", ["stateDiff", "vmTrace", "trace"]],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"output":"0x010203","stateDiff":null,"trace":[],"vmTrace":null},"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} + +#[test] +fn rpc_trace_replay_transaction() { + let tester = io(); + + let request = r#"{"jsonrpc":"2.0","method":"trace_replayTransaction","params":["0x0000000000000000000000000000000000000000000000000000000000000005", ["trace", "stateDiff", "vmTrace"]],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"output":"0x010203","stateDiff":null,"trace":[],"vmTrace":null},"id":1}"#; + + assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); +} From d2b9bc5bd8fca75bfcdbad8640ddd47e654b291c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Wed, 14 Dec 2016 20:09:14 +0100 Subject: [PATCH 311/382] Traces to AutoArgs --- rpc/src/v1/impls/traces.rs | 139 ++++++++++++++---------------------- rpc/src/v1/traits/traces.rs | 72 +++++++++---------- 2 files changed, 89 insertions(+), 122 deletions(-) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 6b37e40cc23..5f42c0cf3fa 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -17,14 +17,14 @@ //! Traces api implementation. use std::sync::{Weak, Arc}; -use jsonrpc_core::*; -use serde; use rlp::{UntrustedRlp, View}; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; +use jsonrpc_core::Error; +use jsonrpc_macros::Trailing; use v1::traits::Traces; use v1::helpers::{errors, CallRequest as CRequest}; use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; @@ -37,22 +37,6 @@ fn to_call_analytics(flags: Vec<String>) -> CallAnalytics { } } -/// Returns number of different parameters in given `Params` object. -fn params_len(params: &Params) -> usize { - match params { - &Params::Array(ref vec) => vec.len(), - _ => 0, - } -} - -/// Deserialize request parameters with optional third parameter `BlockNumber` defaulting to `BlockNumber::Latest`. -fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize { - match params_len(¶ms) { - 2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)), - _ => from_params::<(F1, F2, BlockNumber)>(params) - } -} - /// Traces api implementation. pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService { client: Weak<C>, @@ -91,90 +75,77 @@ impl<C, M> TracesClient<C, M> where C: BlockChainClient, M: MinerService { } impl<C, M> Traces for TracesClient<C, M> where C: BlockChainClient + 'static, M: MinerService + 'static { - fn filter(&self, params: Params) -> Result<Value, Error> { + fn filter(&self, filter: TraceFilter) -> Result<Vec<LocalizedTrace>, Error> { try!(self.active()); - from_params::<(TraceFilter,)>(params) - .and_then(|(filter, )| { - let client = take_weak!(self.client); - let traces = client.filter_traces(filter.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); - Ok(to_value(&traces)) - }) + + let client = take_weak!(self.client); + let traces = client.filter_traces(filter.into()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); + Ok(traces) } - fn block_traces(&self, params: Params) -> Result<Value, Error> { + fn block_traces(&self, block_number: BlockNumber) -> Result<Vec<LocalizedTrace>, Error> { try!(self.active()); - from_params::<(BlockNumber,)>(params) - .and_then(|(block_number,)| { - let client = take_weak!(self.client); - let traces = client.block_traces(block_number.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); - Ok(to_value(&traces)) - }) + let client = take_weak!(self.client); + let traces = client.block_traces(block_number.into()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); + Ok(traces) } - fn transaction_traces(&self, params: Params) -> Result<Value, Error> { + fn transaction_traces(&self, transaction_hash: H256) -> Result<Vec<LocalizedTrace>, Error> { try!(self.active()); - from_params::<(H256,)>(params) - .and_then(|(transaction_hash,)| { - let client = take_weak!(self.client); - let traces = client.transaction_traces(TransactionId::Hash(transaction_hash.into())); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); - Ok(to_value(&traces)) - }) + + let client = take_weak!(self.client); + let traces = client.transaction_traces(TransactionId::Hash(transaction_hash.into())); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); + Ok(traces) } - fn trace(&self, params: Params) -> Result<Value, Error> { + fn trace(&self, transaction_hash: H256, address: Vec<Index>) -> Result<Option<LocalizedTrace>, Error> { try!(self.active()); - from_params::<(H256, Vec<Index>)>(params) - .and_then(|(transaction_hash, address)| { - let client = take_weak!(self.client); - let id = TraceId { - transaction: TransactionId::Hash(transaction_hash.into()), - address: address.into_iter().map(|i| i.value()).collect() - }; - let trace = client.trace(id); - let trace = trace.map(LocalizedTrace::from); - Ok(to_value(&trace)) - }) + let client = take_weak!(self.client); + let id = TraceId { + transaction: TransactionId::Hash(transaction_hash.into()), + address: address.into_iter().map(|i| i.value()).collect() + }; + let trace = client.trace(id); + let trace = trace.map(LocalizedTrace::from); + + Ok(trace) } - fn call(&self, params: Params) -> Result<Value, Error> { + fn call(&self, request: CallRequest, flags: Vec<String>, block: Trailing<BlockNumber>) -> Result<Option<TraceResults>, Error> { try!(self.active()); - from_params_default_third(params) - .and_then(|(request, flags, block)| { - let request = CallRequest::into(request); - let signed = try!(self.sign_call(request)); - match take_weak!(self.client).call(&signed, block.into(), to_call_analytics(flags)) { - Ok(e) => Ok(to_value(&TraceResults::from(e))), - _ => Ok(Value::Null), - } - }) + let block = block.0; + + let request = CallRequest::into(request); + let signed = try!(self.sign_call(request)); + Ok(match take_weak!(self.client).call(&signed, block.into(), to_call_analytics(flags)) { + Ok(e) => Some(TraceResults::from(e)), + _ => None, + }) } - fn raw_transaction(&self, params: Params) -> Result<Value, Error> { + fn raw_transaction(&self, raw_transaction: Bytes, flags: Vec<String>, block: Trailing<BlockNumber>) -> Result<Option<TraceResults>, Error> { try!(self.active()); - from_params_default_third(params) - .and_then(|(raw_transaction, flags, block)| { - let raw_transaction = Bytes::to_vec(raw_transaction); - match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed) => match take_weak!(self.client).call(&signed, block.into(), to_call_analytics(flags)) { - Ok(e) => Ok(to_value(&TraceResults::from(e))), - _ => Ok(Value::Null), - }, - Err(e) => Err(errors::invalid_params("Transaction is not valid RLP", e)), - } - }) + let block = block.0; + + let raw_transaction = Bytes::to_vec(raw_transaction); + match UntrustedRlp::new(&raw_transaction).as_val() { + Ok(signed) => Ok(match take_weak!(self.client).call(&signed, block.into(), to_call_analytics(flags)) { + Ok(e) => Some(TraceResults::from(e)), + _ => None, + }), + Err(e) => Err(errors::invalid_params("Transaction is not valid RLP", e)), + } } - fn replay_transaction(&self, params: Params) -> Result<Value, Error> { + fn replay_transaction(&self, transaction_hash: H256, flags: Vec<String>) -> Result<Option<TraceResults>, Error> { try!(self.active()); - from_params::<(H256, _)>(params) - .and_then(|(transaction_hash, flags)| { - match take_weak!(self.client).replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags)) { - Ok(e) => Ok(to_value(&TraceResults::from(e))), - _ => Ok(Value::Null), - } - }) + + Ok(match take_weak!(self.client).replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags)) { + Ok(e) => Some(TraceResults::from(e)), + _ => None, + }) } } diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 36561216f36..1d5fef5ec2b 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -16,43 +16,39 @@ //! Traces specific rpc interface. -use std::sync::Arc; -use jsonrpc_core::{Params, Value, Error, IoDelegate}; - -/// Traces specific rpc interface. -pub trait Traces: Sized + Send + Sync + 'static { - /// Returns traces matching given filter. - fn filter(&self, _: Params) -> Result<Value, Error>; - - /// Returns transaction trace at given index. - fn trace(&self, _: Params) -> Result<Value, Error>; - - /// Returns all traces of given transaction. - fn transaction_traces(&self, _: Params) -> Result<Value, Error>; - - /// Returns all traces produced at given block. - fn block_traces(&self, _: Params) -> Result<Value, Error>; - - /// Executes the given call and returns a number of possible traces for it. - fn call(&self, _: Params) -> Result<Value, Error>; - - /// Executes the given raw transaction and returns a number of possible traces for it. - fn raw_transaction(&self, _: Params) -> Result<Value, Error>; - - /// Executes the transaction with the given hash and returns a number of possible traces for it. - fn replay_transaction(&self, _: Params) -> Result<Value, Error>; - - /// Should be used to convert object to io delegate. - fn to_delegate(self) -> IoDelegate<Self> { - let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("trace_filter", Traces::filter); - delegate.add_method("trace_get", Traces::trace); - delegate.add_method("trace_transaction", Traces::transaction_traces); - delegate.add_method("trace_block", Traces::block_traces); - delegate.add_method("trace_call", Traces::call); - delegate.add_method("trace_rawTransaction", Traces::raw_transaction); - delegate.add_method("trace_replayTransaction", Traces::replay_transaction); - - delegate +use jsonrpc_core::Error; +use jsonrpc_macros::Trailing; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; + +build_rpc_trait! { + /// Traces specific rpc interface. + pub trait Traces { + /// Returns traces matching given filter. + #[rpc(name = "trace_filter")] + fn filter(&self, TraceFilter) -> Result<Vec<LocalizedTrace>, Error>; + + /// Returns transaction trace at given index. + #[rpc(name = "trace_get")] + fn trace(&self, H256, Vec<Index>) -> Result<Option<LocalizedTrace>, Error>; + + /// Returns all traces of given transaction. + #[rpc(name = "trace_transaction")] + fn transaction_traces(&self, H256) -> Result<Vec<LocalizedTrace>, Error>; + + /// Returns all traces produced at given block. + #[rpc(name = "trace_block")] + fn block_traces(&self, BlockNumber) -> Result<Vec<LocalizedTrace>, Error>; + + /// Executes the given call and returns a number of possible traces for it. + #[rpc(name = "trace_call")] + fn call(&self, CallRequest, Vec<String>, Trailing<BlockNumber>) -> Result<Option<TraceResults>, Error>; + + /// Executes the given raw transaction and returns a number of possible traces for it. + #[rpc(name = "trace_rawTransaction")] + fn raw_transaction(&self, Bytes, Vec<String>, Trailing<BlockNumber>) -> Result<Option<TraceResults>, Error>; + + /// Executes the transaction with the given hash and returns a number of possible traces for it. + #[rpc(name = "trace_replayTransaction")] + fn replay_transaction(&self, H256, Vec<String>) -> Result<Option<TraceResults>, Error>; } } From 244761782eb9b0b63c90a51facb19bdae09ff06f Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" <general.beck@gmail.com> Date: Thu, 15 Dec 2016 02:14:15 +0700 Subject: [PATCH 312/382] Update gitlab-ci Remove armv6 from `nightly` remove `-j 8` key from windows builds Remove `test-windows` from `nightly` --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84d10e1bcec..359a03ecad1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -234,7 +234,7 @@ linux-armv6: # - beta # - tags # - stable - - triggers +# - triggers script: - export CC=arm-linux-gnueabi-gcc - export CXX=arm-linux-gnueabi-g++ @@ -350,7 +350,7 @@ windows: - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - - cargo build -j 8 --release #%CARGOFLAGS% + - cargo build --release #%CARGOFLAGS% - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe @@ -408,7 +408,7 @@ test-darwin: test-windows: stage: test only: - - triggers +# - triggers before_script: - git submodule update --init --recursive script: From 245c8581bb19d36fa30ab3c9d75808bda7e0ce8b Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" <general.beck@gmail.com> Date: Thu, 15 Dec 2016 02:17:59 +0700 Subject: [PATCH 313/382] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 359a03ecad1..e540ca02378 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -231,7 +231,7 @@ linux-armv6: stage: build image: ethcore/rust-armv6:latest only: -# - beta + - beta # - tags # - stable # - triggers From bf7fb849374436af53cc7ede1a0c1bc45dd651a5 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Wed, 14 Dec 2016 21:59:00 +0100 Subject: [PATCH 314/382] Don't put sha3s. --- .gitlab-ci.yml | 4 ---- ethcore/res/ethereum/tests | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index def1c0466cb..81eb9f7b219 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,7 +36,6 @@ linux-stable: - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5 - - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.sha3 --body parity.sha3 - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5" - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu @@ -143,7 +142,6 @@ linux-i686: - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5 - - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.sha3 --body parity.sha3 - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5" - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io/push-build/$CI_BUILD_REF_NAME/$PLATFORM @@ -340,7 +338,6 @@ darwin: - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5 - - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.sha3 --body parity.sha3 - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io/push-build/$CI_BUILD_REF_NAME/$PLATFORM @@ -397,7 +394,6 @@ windows: - aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5 - - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.sha3 --body target\release\parity.exe.sha3 - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5 - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be From 15050da1cd3ce6785c957847adcde7c56dbf281b Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Thu, 15 Dec 2016 00:07:49 +0100 Subject: [PATCH 315/382] Let Webpack talk again --- js/webpack/dev.server.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/js/webpack/dev.server.js b/js/webpack/dev.server.js index 9e8bd1524a4..fc107488a53 100644 --- a/js/webpack/dev.server.js +++ b/js/webpack/dev.server.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. const webpack = require('webpack'); +const WebpackStats = require('webpack/lib/Stats'); const webpackDevMiddleware = require('webpack-dev-middleware'); const webpackHotMiddleware = require('webpack-hot-middleware'); @@ -59,12 +60,24 @@ app.use(webpackHotMiddleware(compiler, { })); app.use(webpackDevMiddleware(compiler, { - noInfo: false, - quiet: true, + noInfo: true, + quiet: false, progress: true, publicPath: webpackConfig.output.publicPath, stats: { colors: true + }, + reporter: function (data) { + // @see https://github.com/webpack/webpack/blob/324d309107f00cfc38ec727521563d309339b2ec/lib/Stats.js#L790 + // Accepted values: none, errors-only, minimal, normal, verbose + const options = WebpackStats.presetToOptions('minimal'); + options.timings = true; + + const output = data.stats.toString(options); + + process.stdout.write('\n'); + process.stdout.write(output); + process.stdout.write('\n\n'); } })); From 03db4d0373d48cb36d3ad5fe3ec8575951a99562 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" <general.beck@gmail.com> Date: Thu, 15 Dec 2016 14:15:27 +0700 Subject: [PATCH 316/382] Update gitlab-ci add `cargo clean` in darwin build --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e540ca02378..c31a033904d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -312,6 +312,7 @@ darwin: - stable - triggers script: + - cargo clean - cargo build -j 8 --release -p ethstore #$CARGOFLAGS - cargo build -j 8 --release #$CARGOFLAGS - rm -rf parity.md5 From 9fc3c47bcb86ab34da0a7f561306a1e1e4b41dd1 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 10:05:52 +0100 Subject: [PATCH 317/382] inject exports both partiy & web3 --- js/src/inject.js | 18 ++++++++++++++++++ js/webpack/libraries.js | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 js/src/inject.js diff --git a/js/src/inject.js b/js/src/inject.js new file mode 100644 index 00000000000..56095809e04 --- /dev/null +++ b/js/src/inject.js @@ -0,0 +1,18 @@ +// Copyright 2015, 2016 Parity Technologies (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 <http://www.gnu.org/licenses/>. + +import './parity'; +import './web3'; diff --git a/js/webpack/libraries.js b/js/webpack/libraries.js index 923d799dccb..a4e57d7d1e5 100644 --- a/js/webpack/libraries.js +++ b/js/webpack/libraries.js @@ -28,7 +28,7 @@ module.exports = { context: path.join(__dirname, '../src'), entry: { // library - 'inject': ['./web3.js'], + 'inject': ['./inject.js'], 'web3': ['./web3.js'], 'parity': ['./parity.js'] }, From 88a26ec53f7db3813a6ddb770866a99777f3be8b Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 10:08:57 +0100 Subject: [PATCH 318/382] Support lint:cached --- js/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/js/package.json b/js/package.json index 8c28b8c5667..615562adc1f 100644 --- a/js/package.json +++ b/js/package.json @@ -39,6 +39,7 @@ "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "npm run lint:css && npm run lint:js", + "lint:cached": "npm run lint:css && npm run lint:js:cached", "lint:css": "stylelint ./src/**/*.css", "lint:js": "eslint --ignore-path .gitignore ./src/", "lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/", From 701399fc77b2d692b8f7c8698b9bc170da9f6894 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 10:23:01 +0100 Subject: [PATCH 319/382] Don't log auth token --- js/src/secureApi.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/secureApi.js b/js/src/secureApi.js index 80e76720b32..d91a974d771 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -38,7 +38,7 @@ export default class SecureApi extends Api { setToken = () => { window.localStorage.setItem('sysuiToken', this._transport.token); - console.log('SecureApi:setToken', this._transport.token); + // DEBUG: console.log('SecureApi:setToken', this._transport.token); } _checkNodeUp () { @@ -149,14 +149,14 @@ export default class SecureApi extends Api { this._signerPort = signerPort.toNumber(); }); - console.log('SecureApi:connectSuccess', this._transport.token); + // DEBUG: console.log('SecureApi:connectSuccess', this._transport.token); } updateToken (token, connectState = 0) { this._connectState = connectState; this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, '')); this._followConnection(); - console.log('SecureApi:updateToken', this._transport.token, connectState); + // DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState); } get dappsPort () { From c6912c8e0a48a01bce08c96658921f20be9cb7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Thu, 15 Dec 2016 11:38:05 +0100 Subject: [PATCH 320/382] Removing on_done --- dapps/src/apps/fetcher.rs | 177 ++++++++++++++++++------------------ dapps/src/handlers/fetch.rs | 18 +--- 2 files changed, 94 insertions(+), 101 deletions(-) diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 2430af03561..e1fa4c066c2 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -120,7 +120,7 @@ impl<R: URLHint> ContentFetcher<R> { // Content is already being fetched Some(&mut ContentStatus::Fetching(ref fetch_control)) => { trace!(target: "dapps", "Content fetching in progress. Waiting..."); - (None, fetch_control.to_handler(control)) + (None, fetch_control.to_async_handler(path, control)) }, // We need to start fetching the content None => { @@ -129,11 +129,12 @@ impl<R: URLHint> ContentFetcher<R> { let content = self.resolver.resolve(content_hex); let cache = self.cache.clone(); - let on_done = move |id: String, result: Option<LocalPageEndpoint>| { + let id = content_id.clone(); + let on_done = move |result: Option<LocalPageEndpoint>| { let mut cache = cache.lock(); match result { Some(endpoint) => { - cache.insert(id, ContentStatus::Ready(endpoint)); + cache.insert(id.clone(), ContentStatus::Ready(endpoint)); }, // In case of error None => { @@ -248,43 +249,45 @@ struct ContentInstaller { id: String, mime: String, content_path: PathBuf, - on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>, + on_done: Box<Fn(Option<LocalPageEndpoint>) + Send>, } impl ContentValidator for ContentInstaller { type Error = ValidationError; fn validate_and_install(&self, path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> { - // Create dir - try!(fs::create_dir_all(&self.content_path)); - - // Validate hash - let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); - let hash = try!(sha3(&mut file_reader)); - let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); - if id != hash { - return Err(ValidationError::HashMismatch { - expected: id, - got: hash, - }); - } + let validate = || { + // Create dir + try!(fs::create_dir_all(&self.content_path)); + + // Validate hash + let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); + let hash = try!(sha3(&mut file_reader)); + let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); + if id != hash { + return Err(ValidationError::HashMismatch { + expected: id, + got: hash, + }); + } - // And prepare path for a file - let filename = path.file_name().expect("We always fetch a file."); - let mut content_path = self.content_path.clone(); - content_path.push(&filename); + // And prepare path for a file + let filename = path.file_name().expect("We always fetch a file."); + let mut content_path = self.content_path.clone(); + content_path.push(&filename); - if content_path.exists() { - try!(fs::remove_dir_all(&content_path)) - } - - try!(fs::copy(&path, &content_path)); + if content_path.exists() { + try!(fs::remove_dir_all(&content_path)) + } - Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))) - } + try!(fs::copy(&path, &content_path)); + Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)) + }; - fn done(&self, endpoint: Option<LocalPageEndpoint>) { - (self.on_done)(self.id.clone(), endpoint) + // Make sure to always call on_done (even in case of errors)! + let result = validate(); + (self.on_done)(result.as_ref().ok().cloned()); + result.map(|endpoint| (self.id.clone(), endpoint)) } } @@ -292,7 +295,7 @@ impl ContentValidator for ContentInstaller { struct DappInstaller { id: String, dapps_path: PathBuf, - on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>, + on_done: Box<Fn(Option<LocalPageEndpoint>) + Send>, embeddable_on: Option<(String, u16)>, } @@ -333,67 +336,67 @@ impl ContentValidator for DappInstaller { fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> { trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path); - let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path))); - let hash = try!(sha3(&mut file_reader)); - let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); - if id != hash { - return Err(ValidationError::HashMismatch { - expected: id, - got: hash, - }); - } - let file = file_reader.into_inner(); - // Unpack archive - let mut zip = try!(zip::ZipArchive::new(file)); - // First find manifest file - let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip)); - // Overwrite id to match hash - manifest.id = self.id.clone(); - - let target = self.dapp_target_path(&manifest); - - // Remove old directory - if target.exists() { - warn!(target: "dapps", "Overwriting existing dapp: {}", manifest.id); - try!(fs::remove_dir_all(target.clone())); - } + let validate = || { + let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path))); + let hash = try!(sha3(&mut file_reader)); + let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); + if id != hash { + return Err(ValidationError::HashMismatch { + expected: id, + got: hash, + }); + } + let file = file_reader.into_inner(); + // Unpack archive + let mut zip = try!(zip::ZipArchive::new(file)); + // First find manifest file + let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip)); + // Overwrite id to match hash + manifest.id = self.id.clone(); + + let target = self.dapp_target_path(&manifest); + + // Remove old directory + if target.exists() { + warn!(target: "dapps", "Overwriting existing dapp: {}", manifest.id); + try!(fs::remove_dir_all(target.clone())); + } - // Unpack zip - for i in 0..zip.len() { - let mut file = try!(zip.by_index(i)); - // TODO [todr] Check if it's consistent on windows. - let is_dir = file.name().chars().rev().next() == Some('/'); - - let file_path = PathBuf::from(file.name()); - let location_in_manifest_base = file_path.strip_prefix(&manifest_dir); - // Create files that are inside manifest directory - if let Ok(location_in_manifest_base) = location_in_manifest_base { - let p = target.join(location_in_manifest_base); - // Check if it's a directory - if is_dir { - try!(fs::create_dir_all(p)); - } else { - let mut target = try!(fs::File::create(p)); - try!(io::copy(&mut file, &mut target)); + // Unpack zip + for i in 0..zip.len() { + let mut file = try!(zip.by_index(i)); + // TODO [todr] Check if it's consistent on windows. + let is_dir = file.name().chars().rev().next() == Some('/'); + + let file_path = PathBuf::from(file.name()); + let location_in_manifest_base = file_path.strip_prefix(&manifest_dir); + // Create files that are inside manifest directory + if let Ok(location_in_manifest_base) = location_in_manifest_base { + let p = target.join(location_in_manifest_base); + // Check if it's a directory + if is_dir { + try!(fs::create_dir_all(p)); + } else { + let mut target = try!(fs::File::create(p)); + try!(io::copy(&mut file, &mut target)); + } } } - } - // Write manifest - let manifest_str = try!(serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)); - let manifest_path = target.join(MANIFEST_FILENAME); - let mut manifest_file = try!(fs::File::create(manifest_path)); - try!(manifest_file.write_all(manifest_str.as_bytes())); - - // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); - - // Return modified app manifest - Ok((manifest.id.clone(), app)) - } + // Write manifest + let manifest_str = try!(serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)); + let manifest_path = target.join(MANIFEST_FILENAME); + let mut manifest_file = try!(fs::File::create(manifest_path)); + try!(manifest_file.write_all(manifest_str.as_bytes())); + // Create endpoint + let endpoint = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); + // Return modified app manifest + Ok(endpoint) + }; - fn done(&self, endpoint: Option<LocalPageEndpoint>) { - (self.on_done)(self.id.clone(), endpoint) + let result = validate(); + (self.on_done)(result.as_ref().ok().cloned()); + result.map(|endpoint| (self.id.clone(), endpoint)) } } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 6fb524293f8..8e5f250ce73 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -29,6 +29,7 @@ use hyper::{server, Decoder, Encoder, Next, Method, Control}; use hyper::net::HttpStream; use hyper::status::StatusCode; +use endpoint::EndpointPath; use handlers::{ContentHandler, Redirection, extract_url}; use page::LocalPageEndpoint; @@ -45,7 +46,6 @@ pub trait ContentValidator { type Error: fmt::Debug + fmt::Display; fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>; - fn done(&self, Option<LocalPageEndpoint>); } pub struct FetchControl { @@ -88,7 +88,9 @@ impl FetchControl { self.abort.store(true, Ordering::SeqCst); } - pub fn to_handler(&self, control: Control) -> Box<server::Handler<HttpStream> + Send> { + pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> { + // TODO [ToDr] We should be able to pass EndpointPath to handler as well + // (request may be coming from different domain, etc) let (tx, rx) = mpsc::channel(); self.listeners.lock().push((control, tx)); @@ -141,25 +143,13 @@ pub struct ContentFetcherHandler<H: ContentValidator> { embeddable_on: Option<(String, u16)>, } -impl<H: ContentValidator> Drop for ContentFetcherHandler<H> { - fn drop(&mut self) { - let result = match self.status { - FetchState::Done(_, ref result, _) => Some(result.clone()), - _ => None, - }; - self.installer.done(result); - } -} - impl<H: ContentValidator> ContentFetcherHandler<H> { - pub fn new( url: String, control: Control, handler: H, embeddable_on: Option<(String, u16)>, ) -> (Self, Arc<FetchControl>) { - let fetch_control = Arc::new(FetchControl::default()); let client = Client::default(); let handler = ContentFetcherHandler { From 6d41168bbed6173e2aef5a77b9647d2e8641806a Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 12:07:09 +0100 Subject: [PATCH 321/382] Change build order --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c31a033904d..28604d0cd5c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -312,9 +312,8 @@ darwin: - stable - triggers script: - - cargo clean - - cargo build -j 8 --release -p ethstore #$CARGOFLAGS - cargo build -j 8 --release #$CARGOFLAGS + - cargo build -j 8 --release -p ethstore #$CARGOFLAGS - rm -rf parity.md5 - md5sum target/release/parity > parity.md5 - packagesbuild -v mac/Parity.pkgproj From 69eec105a74872f532d5e5acfa6720ae8fffc2ac Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 13:40:44 +0100 Subject: [PATCH 322/382] Change default back to permissive for now. --- ethcore/res/ethereum/tests | 2 +- ethcore/src/account_provider/mod.rs | 3 ++- ethcore/src/account_provider/stores.rs | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 649cb316286..dab19dbc04c 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -478,7 +478,8 @@ mod tests { let ap = AccountProvider::transient_provider(); let address = ap.new_account("test").unwrap(); - // Default policy should be to return nothing + // When returning nothing + ap.set_new_dapps_whitelist(Some(vec![])).unwrap(); assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); // change to all diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index 722370426b3..d4f2093eec3 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -175,7 +175,7 @@ impl DappsSettingsStore { /// Returns current new dapps policy pub fn policy(&self) -> NewDappsPolicy { - self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::Whitelist(vec![])) + self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts) } /// Returns recent dapps (in order of last request) @@ -353,8 +353,9 @@ mod tests { let temp = RandomTempPath::create_dir(); let path = temp.as_str().to_owned(); let mut store = DappsSettingsStore::new(path.clone()); + // Test default policy - assert_eq!(store.policy(), NewDappsPolicy::Whitelist(vec![])); + assert_eq!(store.policy(), NewDappsPolicy::AllAccounts); // when store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])); From 943efc21bbf3f01d822a92d168b52f2d6024d6e0 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 13:41:04 +0100 Subject: [PATCH 323/382] add tendermint seal --- ethcore/res/tendermint.json | 8 ++++++-- ethcore/src/spec/seal.rs | 26 ++++++++++++++++++++++++++ json/src/spec/mod.rs | 2 +- json/src/spec/seal.rs | 22 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 38e5334a3e4..77875710714 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -19,8 +19,12 @@ }, "genesis": { "seal": { - "generic": { - "rlp": "f88980b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "tendermint": { + "round": "0x0", + "proposal": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "precommits": [ + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] } }, "difficulty": "0x20000", diff --git a/ethcore/src/spec/seal.rs b/ethcore/src/spec/seal.rs index d5257ab793b..7c0735483e5 100644 --- a/ethcore/src/spec/seal.rs +++ b/ethcore/src/spec/seal.rs @@ -44,6 +44,16 @@ pub struct AuthorityRound { pub signature: H520, } +/// Tendermint seal. +pub struct AuthorityRound { + /// Seal round. + pub round: usize, + /// Proposal seal signature. + pub proposal: H520, + /// Precommit seal signatures. + pub precommits: Vec<H520>, +} + impl Into<Generic> for AuthorityRound { fn into(self) -> Generic { let mut s = RlpStream::new_list(2); @@ -52,6 +62,14 @@ impl Into<Generic> for AuthorityRound { } } +impl Into<Generic> for AuthorityRound { + fn into(self) -> Generic { + let mut s = RlpStream::new_list(3); + s.append(&self.round).append(&self.proposal).append(&self.precommits); + Generic(s.out()) + } +} + pub struct Generic(pub Vec<u8>); /// Genesis seal type. @@ -60,6 +78,8 @@ pub enum Seal { Ethereum(Ethereum), /// AuthorityRound seal. AuthorityRound(AuthorityRound), + /// Tendermint seal. + Tendermint(Tendermint), /// Generic RLP seal. Generic(Generic), } @@ -75,6 +95,11 @@ impl From<ethjson::spec::Seal> for Seal { step: ar.step.into(), signature: ar.signature.into() }), + ethjson::spec::Seal::Tendermint(tender) => Seal::Tendermint(Tendermint { + round: tender.round.into(), + proposal: tender.proposal.into(), + precommits: tender.precommits.into_iter().map(Into::into).collect() + }), ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic(g.into())), } } @@ -86,6 +111,7 @@ impl Into<Generic> for Seal { Seal::Generic(generic) => generic, Seal::Ethereum(eth) => eth.into(), Seal::AuthorityRound(ar) => ar.into(), + Seal::Tendermint(tender) => tender.into(), } } } diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index f27b13b9239..d923be0695c 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -34,7 +34,7 @@ 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, AuthorityRoundSeal}; +pub use self::seal::{Seal, Ethereum, AuthorityRoundSeal, TendermintSeal}; pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; diff --git a/json/src/spec/seal.rs b/json/src/spec/seal.rs index d586ed501b1..1cf1e86e692 100644 --- a/json/src/spec/seal.rs +++ b/json/src/spec/seal.rs @@ -39,6 +39,17 @@ pub struct AuthorityRoundSeal { pub signature: H520, } +/// Tendermint seal. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TendermintSeal { + /// Seal round. + pub round: Uint, + /// Proposal seal signature. + pub proposal: H520, + /// Proposal seal signature. + pub precommits: Vec<H520>, +} + /// Seal variants. #[derive(Debug, PartialEq, Deserialize)] pub enum Seal { @@ -48,6 +59,9 @@ pub enum Seal { /// AuthorityRound seal. #[serde(rename="authority_round")] AuthorityRound(AuthorityRoundSeal), + /// Tendermint seal. + #[serde(rename="tendermint")] + Tendermint(TendermintSeal), /// Generic seal. #[serde(rename="generic")] Generic(Bytes), @@ -72,6 +86,14 @@ mod tests { "step": "0x0", "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } + },{ + "tendermint": { + "round": "0x0", + "proposal": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "precommits": [ + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + } }]"#; let _deserialized: Vec<Seal> = serde_json::from_str(s).unwrap(); // TODO: validate all fields From 0b9ab0797ff481052862c10035da34615bbfacf6 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 13:45:22 +0100 Subject: [PATCH 324/382] fix build --- ethcore/src/spec/seal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/spec/seal.rs b/ethcore/src/spec/seal.rs index 7c0735483e5..967ffc22be4 100644 --- a/ethcore/src/spec/seal.rs +++ b/ethcore/src/spec/seal.rs @@ -45,7 +45,7 @@ pub struct AuthorityRound { } /// Tendermint seal. -pub struct AuthorityRound { +pub struct Tendermint { /// Seal round. pub round: usize, /// Proposal seal signature. @@ -62,7 +62,7 @@ impl Into<Generic> for AuthorityRound { } } -impl Into<Generic> for AuthorityRound { +impl Into<Generic> for Tendermint { fn into(self) -> Generic { let mut s = RlpStream::new_list(3); s.append(&self.round).append(&self.proposal).append(&self.precommits); From f59f7c57730c1280f499e4c6dbe46a4c3d5e221c Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Thu, 15 Dec 2016 13:48:24 +0100 Subject: [PATCH 325/382] address style grumbles :lipstick: --- js/src/contracts/badgereg.js | 11 +++- .../providers/certifications/middleware.js | 62 +++++++++++-------- .../redux/providers/certifications/reducer.js | 4 +- js/src/ui/Certifications/certifications.js | 1 + 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 10943c43e11..8075f456e13 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -31,7 +31,7 @@ export default class BadgeReg { this.contracts = {}; // by name } - nrOfCertifiers () { + certifierCount () { return this._registry.getContract('badgereg') .then((badgeReg) => { return badgeReg.instance.badgeCount.call({}, []) @@ -48,9 +48,14 @@ export default class BadgeReg { return badgeReg.instance.badge.call({}, [ id ]); }) .then(([ address, name ]) => { - if (address === ZERO20) throw new Error(`Certifier ${id} does not exist.`); + if (address === ZERO20) { + throw new Error(`Certifier ${id} does not exist.`); + } + name = bytesToHex(name); - name = name === ZERO32 ? null : hex2Ascii(name); + name = name === ZERO32 + ? null + : hex2Ascii(name); return this.fetchMeta(id) .then(({ title, icon }) => { const data = { address, id, name, title, icon }; diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6cbfffa728d..6e4b898d043 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -44,8 +44,11 @@ export default class CertificationsMiddleware { .then((logs) => { logs.forEach((log) => { const certifier = certifiers.find((c) => c.address === log.address); - if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); + if (!certifier) { + throw new Error(`Could not find certifier at ${log.address}.`); + } const { id, name, title, icon } = certifier; + if (log.event === 'Revoked') { dispatch(removeCertification(log.params.who.value, id)); } else { @@ -59,33 +62,42 @@ export default class CertificationsMiddleware { }; return (store) => (next) => (action) => { - if (action.type === 'fetchCertifiers') { - badgeReg.nrOfCertifiers().then((count) => { - new Array(+count).fill(null).forEach((_, id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - if (!certifiers.some((c) => c.id === cert.id)) { - certifiers = certifiers.concat(cert); - fetchConfirmedEvents(store.dispatch); - } - }) - .catch((err) => { - console.warn(`Could not fetch certifier ${id}:`, err); - }); + switch (action.type) { + case 'fetchCertifiers': + badgeReg.certifierCount().then((count) => { + new Array(+count).fill(null).forEach((_, id) => { + badgeReg.fetchCertifier(id) + .then((cert) => { + if (!certifiers.some((c) => c.id === cert.id)) { + certifiers = certifiers.concat(cert); + fetchConfirmedEvents(store.dispatch); + } + }) + .catch((err) => { + console.warn(`Could not fetch certifier ${id}:`, err); + }); + }); }); - }); - } else if (action.type === 'fetchCertifications') { - const { address } = action; - if (!accounts.includes(address)) { - accounts = accounts.concat(address); + break; + case 'fetchCertifications': + const { address } = action; + + if (!accounts.includes(address)) { + accounts = accounts.concat(address); + fetchConfirmedEvents(store.dispatch); + } + + break; + case 'setVisibleAccounts': + const { addresses } = action; + accounts = uniq(accounts.concat(addresses)); fetchConfirmedEvents(store.dispatch); - } - } else if (action.type === 'setVisibleAccounts') { - const { addresses } = action; - accounts = uniq(accounts.concat(addresses)); - fetchConfirmedEvents(store.dispatch); - } else return next(action); + + break; + default: + next(action); + } }; } } diff --git a/js/src/redux/providers/certifications/reducer.js b/js/src/redux/providers/certifications/reducer.js index d96ed1d65b1..bb2681cf6b8 100644 --- a/js/src/redux/providers/certifications/reducer.js +++ b/js/src/redux/providers/certifications/reducer.js @@ -24,8 +24,10 @@ export default (state = initialState, action) => { if (certifications.some((c) => c.id === id)) { return state; } - const newCertifications = certifications.concat({ id, name, icon, title }); + const newCertifications = certifications.concat({ + id, name, icon, title + }); return { ...state, [address]: newCertifications }; } diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 6c9b2fe2cbe..bafd06f35bb 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -65,6 +65,7 @@ function mapStateToProps (_, initProps) { return (state) => { const certifications = state.certifications[account] || []; const dappsUrl = state.api.dappsUrl; + return { certifications, dappsUrl }; }; } From 4c42dedcad11969a58cf381cc0328da7db770817 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Thu, 15 Dec 2016 13:51:44 +0100 Subject: [PATCH 326/382] sort props :lipstick: --- js/src/views/Accounts/List/list.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 44a8ff88823..d5bdd966266 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -27,18 +27,18 @@ import styles from './list.css'; class List extends Component { static propTypes = { accounts: PropTypes.object, - walletsOwners: PropTypes.object, balances: PropTypes.object, - link: PropTypes.string, - search: PropTypes.array, + certifications: PropTypes.object.isRequired, empty: PropTypes.bool, + link: PropTypes.string, order: PropTypes.string, orderFallback: PropTypes.string, - certifications: PropTypes.object.isRequired, + search: PropTypes.array, + walletsOwners: PropTypes.object, - handleAddSearchToken: PropTypes.func, fetchCertifiers: PropTypes.func.isRequired, - fetchCertifications: PropTypes.func.isRequired + fetchCertifications: PropTypes.func.isRequired, + handleAddSearchToken: PropTypes.func }; render () { @@ -50,7 +50,7 @@ class List extends Component { } componentWillMount () { - const { fetchCertifiers, accounts, fetchCertifications } = this.props; + const { accounts, fetchCertifiers, fetchCertifications } = this.props; fetchCertifiers(); for (let address in accounts) { fetchCertifications(address); @@ -58,7 +58,7 @@ class List extends Component { } renderAccounts () { - const { accounts, balances, link, empty, handleAddSearchToken, walletsOwners } = this.props; + const { accounts, balances, empty, link, walletsOwners, handleAddSearchToken } = this.props; if (empty) { return ( From 6dfcbd41560213e70a2fb7b93990081e8123be24 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Thu, 15 Dec 2016 13:06:03 +0000 Subject: [PATCH 327/382] [ci skip] js-precompiled 20161215-130206 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e76a6749bfc..7c2ad2c77f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#175003ae159b126302fd1a90dd875dc86d7adba0" +source = "git+https://github.com/ethcore/js-precompiled.git#a80a0c512cf569985ec1fffc5ea5ae70eb6c1e1f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 615562adc1f..3282c38e47e 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.122", + "version": "0.2.123", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From ca63bc2f4430f516e4a7f95a7c31d6a32b8b9714 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 14:46:31 +0100 Subject: [PATCH 328/382] Update parking_lot dep --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 54fbfdb0863..e06f56aa2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,7 +886,7 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", From 7e76d6494c4f9ac422f4a51957ce4c65a1bab99e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Thu, 15 Dec 2016 13:52:59 +0000 Subject: [PATCH 329/382] [ci skip] js-precompiled 20161215-134858 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f4d8ff117a..511218335b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a80a0c512cf569985ec1fffc5ea5ae70eb6c1e1f" +source = "git+https://github.com/ethcore/js-precompiled.git#762c6d10f8640a6c4b875d776490282680bfe3e2" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 3282c38e47e..83d7994e751 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.123", + "version": "0.2.124", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 8f5e0cf7d9468b6dc24704f92a8a8b44694ee8d9 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 14:54:02 +0100 Subject: [PATCH 330/382] Fix build --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e06f56aa2cd..cf557d48897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1620,7 +1620,6 @@ dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] From d1040e4127072956b79642800c174eefa96adb05 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 15:03:34 +0100 Subject: [PATCH 331/382] Whitespace --- rpc_client/src/client.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rpc_client/src/client.rs b/rpc_client/src/client.rs index bc4c7aa8b04..9ee9f5c9d4d 100644 --- a/rpc_client/src/client.rs +++ b/rpc_client/src/client.rs @@ -65,7 +65,7 @@ impl RpcHandler { } impl Handler for RpcHandler { - fn build_request(&mut self, url: &Url) -> WsResult<Request> { + fn build_request(&mut self, url: &Url) -> WsResult<Request> { match Request::from_url(url) { Ok(mut r) => { let timestamp = try!(time::UNIX_EPOCH.elapsed().map_err(|err| { @@ -80,14 +80,14 @@ impl Handler for RpcHandler { Err(e) => Err(WsError::new(WsErrorKind::Internal, format!("{}", e))), } - } - fn on_error(&mut self, err: WsError) { + } + fn on_error(&mut self, err: WsError) { match self.complete.take() { Some(c) => c.complete(Err(RpcError::WsError(err))), None => println!("unexpected error: {}", err), } - } - fn on_open(&mut self, _: Handshake) -> WsResult<()> { + } + fn on_open(&mut self, _: Handshake) -> WsResult<()> { match (self.complete.take(), self.out.take()) { (Some(c), Some(out)) => { c.complete(Ok(Rpc { @@ -103,7 +103,7 @@ impl Handler for RpcHandler { } } } - fn on_message(&mut self, msg: Message) -> WsResult<()> { + fn on_message(&mut self, msg: Message) -> WsResult<()> { let ret: Result<JsonValue, JsonRpcError>; let response_id; let string = &msg.to_string(); @@ -147,7 +147,7 @@ impl Handler for RpcHandler { ), } Ok(()) - } + } } /// Keeping track of issued requests to be matched up with responses From dedf9d6dcee2acd694723073c6ddd6f8355e4eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Thu, 15 Dec 2016 14:46:10 +0100 Subject: [PATCH 332/382] Getting rid of redirection --- dapps/src/apps/fetcher.rs | 15 +++--- dapps/src/handlers/fetch.rs | 99 ++++++++++++++++++++++++------------- dapps/src/page/handler.rs | 33 +++++++++---- dapps/src/page/local.rs | 50 ++++++++++++------- dapps/src/page/mod.rs | 2 +- 5 files changed, 129 insertions(+), 70 deletions(-) diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index e1fa4c066c2..006858e731c 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -151,6 +151,7 @@ impl<R: URLHint> ContentFetcher<R> { Some(URLHintResult::Dapp(dapp)) => { let (handler, fetch_control) = ContentFetcherHandler::new( dapp.url(), + path, control, DappInstaller { id: content_id.clone(), @@ -166,6 +167,7 @@ impl<R: URLHint> ContentFetcher<R> { Some(URLHintResult::Content(content)) => { let (handler, fetch_control) = ContentFetcherHandler::new( content.url, + path, control, ContentInstaller { id: content_id.clone(), @@ -255,7 +257,7 @@ struct ContentInstaller { impl ContentValidator for ContentInstaller { type Error = ValidationError; - fn validate_and_install(&self, path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> { + fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, ValidationError> { let validate = || { // Create dir try!(fs::create_dir_all(&self.content_path)); @@ -287,7 +289,7 @@ impl ContentValidator for ContentInstaller { // Make sure to always call on_done (even in case of errors)! let result = validate(); (self.on_done)(result.as_ref().ok().cloned()); - result.map(|endpoint| (self.id.clone(), endpoint)) + result } } @@ -334,10 +336,10 @@ impl DappInstaller { impl ContentValidator for DappInstaller { type Error = ValidationError; - fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> { - trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path); + fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, ValidationError> { + trace!(target: "dapps", "Opening dapp bundle at {:?}", path); let validate = || { - let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path))); + let mut file_reader = io::BufReader::new(try!(fs::File::open(path))); let hash = try!(sha3(&mut file_reader)); let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); if id != hash { @@ -390,13 +392,12 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint let endpoint = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); - // Return modified app manifest Ok(endpoint) }; let result = validate(); (self.on_done)(result.as_ref().ok().cloned()); - result.map(|endpoint| (self.id.clone(), endpoint)) + result } } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 8e5f250ce73..d62b425d9eb 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -22,35 +22,41 @@ use std::sync::{mpsc, Arc}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Instant, Duration}; use util::Mutex; -use url::Url; use fetch::{Client, Fetch, FetchResult}; use hyper::{server, Decoder, Encoder, Next, Method, Control}; use hyper::net::HttpStream; +use hyper::uri::RequestUri; use hyper::status::StatusCode; use endpoint::EndpointPath; -use handlers::{ContentHandler, Redirection, extract_url}; -use page::LocalPageEndpoint; +use handlers::ContentHandler; +use page::{LocalPageEndpoint, PageHandlerWaiting}; const FETCH_TIMEOUT: u64 = 30; enum FetchState { + Waiting, NotStarted(String), Error(ContentHandler), InProgress(mpsc::Receiver<FetchResult>), - Done(String, LocalPageEndpoint, Redirection), + Done(LocalPageEndpoint, Box<PageHandlerWaiting>), +} + +enum WaitResult { + Error(ContentHandler), + Done(LocalPageEndpoint), } pub trait ContentValidator { type Error: fmt::Debug + fmt::Display; - fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>; + fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, Self::Error>; } pub struct FetchControl { abort: Arc<AtomicBool>, - listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>, + listeners: Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>, deadline: Instant, } @@ -65,9 +71,10 @@ impl Default for FetchControl { } impl FetchControl { - fn notify<F: Fn() -> FetchState>(&self, status: F) { + fn notify<F: Fn() -> WaitResult>(&self, status: F) { let mut listeners = self.listeners.lock(); for (control, sender) in listeners.drain(..) { + trace!(target: "dapps", "Resuming request waiting for content..."); if let Err(e) = sender.send(status()) { trace!(target: "dapps", "Waiting listener notification failed: {:?}", e); } else { @@ -78,9 +85,9 @@ impl FetchControl { fn set_status(&self, status: &FetchState) { match *status { - FetchState::Error(ref handler) => self.notify(|| FetchState::Error(handler.clone())), - FetchState::Done(ref id, ref endpoint, ref handler) => self.notify(|| FetchState::Done(id.clone(), endpoint.clone(), handler.clone())), - FetchState::NotStarted(_) | FetchState::InProgress(_) => {}, + FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())), + FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())), + FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {}, } } @@ -89,45 +96,65 @@ impl FetchControl { } pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> { - // TODO [ToDr] We should be able to pass EndpointPath to handler as well - // (request may be coming from different domain, etc) let (tx, rx) = mpsc::channel(); self.listeners.lock().push((control, tx)); Box::new(WaitingHandler { receiver: rx, - state: None, + state: FetchState::Waiting, + uri: RequestUri::default(), + path: path, }) } } pub struct WaitingHandler { - receiver: mpsc::Receiver<FetchState>, - state: Option<FetchState>, + receiver: mpsc::Receiver<WaitResult>, + state: FetchState, + uri: RequestUri, + path: EndpointPath, } impl server::Handler<HttpStream> for WaitingHandler { - fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next { + fn on_request(&mut self, request: server::Request<HttpStream>) -> Next { + self.uri = request.uri().clone(); Next::wait() } - fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next { - self.state = self.receiver.try_recv().ok(); - Next::write() + fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next { + let result = self.receiver.try_recv().ok(); + self.state = match result { + Some(WaitResult::Error(handler)) => FetchState::Error(handler), + Some(WaitResult::Done(endpoint)) => { + let mut page_handler = endpoint.to_page_handler(self.path.clone()); + page_handler.set_uri(&self.uri); + FetchState::Done(endpoint, page_handler) + }, + None => { + warn!("A result for waiting request was not received."); + FetchState::Waiting + }, + }; + + match self.state { + FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder), + FetchState::Error(ref mut handler) => handler.on_request_readable(decoder), + _ => Next::write(), + } } fn on_response(&mut self, res: &mut server::Response) -> Next { match self.state { - Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response(res), - Some(FetchState::Error(ref mut handler)) => handler.on_response(res), + FetchState::Done(_, ref mut handler) => handler.on_response(res), + FetchState::Error(ref mut handler) => handler.on_response(res), _ => Next::end(), } } fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next { match self.state { - Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response_writable(encoder), - Some(FetchState::Error(ref mut handler)) => handler.on_response_writable(encoder), + FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder), + FetchState::Error(ref mut handler) => handler.on_response_writable(encoder), _ => Next::end(), } } @@ -139,13 +166,15 @@ pub struct ContentFetcherHandler<H: ContentValidator> { status: FetchState, client: Option<Client>, installer: H, - request_url: Option<Url>, + path: EndpointPath, + uri: RequestUri, embeddable_on: Option<(String, u16)>, } impl<H: ContentValidator> ContentFetcherHandler<H> { pub fn new( url: String, + path: EndpointPath, control: Control, handler: H, embeddable_on: Option<(String, u16)>, @@ -158,7 +187,8 @@ impl<H: ContentValidator> ContentFetcherHandler<H> { client: Some(client), status: FetchState::NotStarted(url), installer: handler, - request_url: None, + path: path, + uri: RequestUri::default(), embeddable_on: embeddable_on, }; @@ -182,7 +212,6 @@ impl<H: ContentValidator> ContentFetcherHandler<H> { impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<H> { fn on_request(&mut self, request: server::Request<HttpStream>) -> Next { - self.request_url = extract_url(&request); let status = if let FetchState::NotStarted(ref url) = self.status { Some(match *request.method() { // Start fetching content @@ -195,8 +224,8 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler< Ok(receiver) => FetchState::InProgress(receiver), Err(e) => FetchState::Error(ContentHandler::error( StatusCode::BadGateway, - "Unable To Start Dapp Download", - "Could not initialize download of the dapp. It might be a problem with the remote server.", + "Unable To Start Content Download", + "Could not initialize download of the content. It might be a problem with the remote server.", Some(&format!("{}", e)), self.embeddable_on.clone(), )), @@ -217,6 +246,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler< self.fetch_control.set_status(&status); self.status = status; } + self.uri = request.uri().clone(); Next::read() } @@ -256,11 +286,10 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler< self.embeddable_on.clone(), )) }, - Ok((id, result)) => { - let url: String = self.request_url.take() - .map(|url| url.raw.into_string()) - .expect("Request URL always read in on_request; qed"); - FetchState::Done(id, result, Redirection::new(&url)) + Ok(endpoint) => { + let mut handler = endpoint.to_page_handler(self.path.clone()); + handler.set_uri(&self.uri); + FetchState::Done(endpoint, handler) }, }; // Remove temporary zip file @@ -296,7 +325,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler< fn on_response(&mut self, res: &mut server::Response) -> Next { match self.status { - FetchState::Done(_, _, ref mut handler) => handler.on_response(res), + FetchState::Done(_, ref mut handler) => handler.on_response(res), FetchState::Error(ref mut handler) => handler.on_response(res), _ => Next::end(), } @@ -304,7 +333,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler< fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next { match self.status { - FetchState::Done(_, _, ref mut handler) => handler.on_response_writable(encoder), + FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder), FetchState::Error(ref mut handler) => handler.on_response_writable(encoder), _ => Next::end(), } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index 382dfa5d1a7..ba7a7ee04e5 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -83,13 +83,19 @@ impl Default for PageCache { } } +/// A generic type for `PageHandler` allowing to set the URL. +/// Used by dapps fetching to set the URL after the content was downloaded. +pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send { + fn set_uri(&mut self, uri: &RequestUri); +} + /// A handler for a single webapp. /// Resolves correct paths and serves as a plumbing code between /// hyper server and dapp. pub struct PageHandler<T: Dapp> { /// A Dapp. pub app: T, - /// File currently being served (or `None` if file does not exist). + /// File currently being served pub file: ServedFile<T>, /// Optional prefix to strip from path. pub prefix: Option<String>, @@ -101,6 +107,21 @@ pub struct PageHandler<T: Dapp> { pub cache: PageCache, } +impl<T: Dapp> PageHandlerWaiting for PageHandler<T> { + fn set_uri(&mut self, uri: &RequestUri) { + trace!(target: "dapps", "Setting URI: {:?}", uri); + self.file = match *uri { + RequestUri::AbsolutePath { ref path, .. } => { + self.app.file(&self.extract_path(path)) + }, + RequestUri::AbsoluteUri(ref url) => { + self.app.file(&self.extract_path(url.path())) + }, + _ => None, + }.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f)); + } +} + impl<T: Dapp> PageHandler<T> { fn extract_path(&self, path: &str) -> String { let app_id = &self.path.app_id; @@ -124,15 +145,7 @@ impl<T: Dapp> PageHandler<T> { impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> { fn on_request(&mut self, req: server::Request<HttpStream>) -> Next { - self.file = match *req.uri() { - RequestUri::AbsolutePath { ref path, .. } => { - self.app.file(&self.extract_path(path)) - }, - RequestUri::AbsoluteUri(ref url) => { - self.app.file(&self.extract_path(url.path())) - }, - _ => None, - }.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f)); + self.set_uri(req.uri()); Next::write() } diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index 77c91019db7..e8ab9ce1433 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -18,7 +18,7 @@ use mime_guess; use std::io::{Seek, Read, SeekFrom}; use std::fs; use std::path::{Path, PathBuf}; -use page::handler::{self, PageCache}; +use page::handler::{self, PageCache, PageHandlerWaiting}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; #[derive(Debug, Clone)] @@ -54,6 +54,36 @@ impl LocalPageEndpoint { pub fn path(&self) -> PathBuf { self.path.clone() } + + fn page_handler_with_mime(&self, path: EndpointPath, mime: &str) -> handler::PageHandler<LocalSingleFile> { + handler::PageHandler { + app: LocalSingleFile { path: self.path.clone(), mime: mime.into() }, + prefix: None, + path: path, + file: handler::ServedFile::new(None), + safe_to_embed_on: self.embeddable_on.clone(), + cache: self.cache, + } + } + + fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> { + handler::PageHandler { + app: LocalDapp { path: self.path.clone() }, + prefix: None, + path: path, + file: handler::ServedFile::new(None), + safe_to_embed_on: self.embeddable_on.clone(), + cache: self.cache, + } + } + + pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> { + if let Some(ref mime) = self.mime { + Box::new(self.page_handler_with_mime(path, mime)) + } else { + Box::new(self.page_handler(path)) + } + } } impl Endpoint for LocalPageEndpoint { @@ -63,23 +93,9 @@ impl Endpoint for LocalPageEndpoint { fn to_handler(&self, path: EndpointPath) -> Box<Handler> { if let Some(ref mime) = self.mime { - Box::new(handler::PageHandler { - app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() }, - prefix: None, - path: path, - file: handler::ServedFile::new(None), - safe_to_embed_on: self.embeddable_on.clone(), - cache: self.cache, - }) + Box::new(self.page_handler_with_mime(path, mime)) } else { - Box::new(handler::PageHandler { - app: LocalDapp { path: self.path.clone() }, - prefix: None, - path: path, - file: handler::ServedFile::new(None), - safe_to_embed_on: self.embeddable_on.clone(), - cache: self.cache, - }) + Box::new(self.page_handler(path)) } } } diff --git a/dapps/src/page/mod.rs b/dapps/src/page/mod.rs index 9619f1b10d9..5c2b008f8ca 100644 --- a/dapps/src/page/mod.rs +++ b/dapps/src/page/mod.rs @@ -21,5 +21,5 @@ mod handler; pub use self::local::LocalPageEndpoint; pub use self::builtin::PageEndpoint; -pub use self::handler::PageCache; +pub use self::handler::{PageCache, PageHandlerWaiting}; From 5e59e0b9d30ec3cac34b1eb9572a44327d6f15c2 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 15:35:46 +0100 Subject: [PATCH 333/382] Fix build. --- updater/src/updater.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 75637f1392c..aed62873da1 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -289,7 +289,7 @@ impl Updater { } impl ChainNotify for Updater { - fn new_blocks(&self, _imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, _duration: u64) { + fn new_blocks(&self, _imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, _proposed: Vec<Bytes>, _duration: u64) { // TODO: something like this // if !self.client.upgrade().map_or(true, |c| c.is_major_syncing()) { self.poll(); From 9dd1268b69c7d924ba14751a2effca397bb34e79 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 15:56:50 +0100 Subject: [PATCH 334/382] Add keys for steps (future-proof) --- js/src/modals/UpgradeParity/store.js | 2 +- js/src/modals/UpgradeParity/upgradeParity.js | 40 +++++++++++--------- js/src/ui/Modal/Title/title.js | 2 +- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/js/src/modals/UpgradeParity/store.js b/js/src/modals/UpgradeParity/store.js index 466a588e6e1..d7a4efd3e69 100644 --- a/js/src/modals/UpgradeParity/store.js +++ b/js/src/modals/UpgradeParity/store.js @@ -25,7 +25,7 @@ const A_DAY = 24 * 60 * A_MINUTE; const STEP_INFO = 0; const STEP_UPDATING = 1; const STEP_COMPLETED = 2; -const STEP_ERROR = 3; +const STEP_ERROR = 2; const CHECK_INTERVAL = 1 * A_MINUTE; diff --git a/js/src/modals/UpgradeParity/upgradeParity.js b/js/src/modals/UpgradeParity/upgradeParity.js index 3e67c42f29a..a11e1a1fe96 100644 --- a/js/src/modals/UpgradeParity/upgradeParity.js +++ b/js/src/modals/UpgradeParity/upgradeParity.js @@ -49,16 +49,20 @@ export default class UpgradeParity extends Component { steps={ [ <FormattedMessage id='upgradeParity.step.info' + key='info' defaultMessage='upgrade available' />, <FormattedMessage + key='updating' id='upgradeParity.step.updating' defaultMessage='upgrading parity' />, store.step === STEP_ERROR ? <FormattedMessage id='upgradeParity.step.error' + key='error' defaultMessage='error' /> : <FormattedMessage id='upgradeParity.step.completed' + key='completed' defaultMessage='upgrade completed' /> ] } visible> @@ -158,6 +162,25 @@ export default class UpgradeParity extends Component { ); case STEP_COMPLETED: + case STEP_ERROR: + if (store.error) { + return ( + <Completed> + <div> + <FormattedMessage + id='upgradeParity.failed' + defaultMessage='Your upgrade to Parity {newversion} has failed with an error.' + values={ { + newversion: <div className={ styles.version }>{ newversion }</div> + } } /> + </div> + <div className={ styles.error }> + { store.error.message } + </div> + </Completed> + ); + } + return ( <Completed> <FormattedMessage @@ -168,23 +191,6 @@ export default class UpgradeParity extends Component { } } /> </Completed> ); - - case STEP_ERROR: - return ( - <Completed> - <div> - <FormattedMessage - id='upgradeParity.failed' - defaultMessage='Your upgrade to Parity {newversion} has failed with an error.' - values={ { - newversion: <div className={ styles.version }>{ newversion }</div> - } } /> - </div> - <div className={ styles.error }> - { store.error.message } - </div> - </Completed> - ); } } diff --git a/js/src/ui/Modal/Title/title.js b/js/src/ui/Modal/Title/title.js index 920d1b13c50..27197be283a 100644 --- a/js/src/ui/Modal/Title/title.js +++ b/js/src/ui/Modal/Title/title.js @@ -72,7 +72,7 @@ export default class Title extends Component { return steps.map((label, index) => { return ( <Step - key={ index }> + key={ label.key || index }> <StepLabel> { label } </StepLabel> From 2be6223eb32ab941e09ba82105cbcafd01718c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Thu, 15 Dec 2016 15:57:21 +0100 Subject: [PATCH 335/382] Updating submodules to latest --- ethcore/res/ethereum/tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be From 9bfa69c6bd831d4e4ca2a38ec1de12f4bdd7d066 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 16:52:39 +0100 Subject: [PATCH 336/382] fix channel --- ethcore/src/engines/tendermint/mod.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index de89658ab82..2a9925e8932 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -661,10 +661,10 @@ mod tests { use block::*; use error::{Error, BlockError}; use header::Header; + use io::{IoService, IoChannel}; use env_info::EnvInfo; use tests::helpers::*; use account_provider::AccountProvider; - use io::IoService; use service::ClientIoMessage; use spec::Spec; use engines::{Engine, EngineError, Seal}; @@ -904,19 +904,15 @@ mod tests { let proposal = Some(b.header().bare_hash()); // Register IoHandler remembers messages. - let io_service = IoService::<ClientIoMessage>::start().unwrap(); let test_io = TestIo::new(); - io_service.register_handler(test_io.clone()).unwrap(); - engine.register_message_channel(io_service.channel()); + let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>))); + engine.register_message_channel(channel); let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); - - // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(500)); // Relays all valid present and future messages. assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current))); @@ -941,9 +937,8 @@ mod tests { // Register IoHandler remembers messages. let test_io = TestIo::new(); - let io_service = IoService::<ClientIoMessage>::start().unwrap(); - io_service.register_handler(test_io.clone()).unwrap(); - engine.register_message_channel(io_service.channel()); + let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>))); + engine.register_message_channel(channel); // Propose let (b, mut seal) = propose_default(&spec, v1.clone()); @@ -956,11 +951,12 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(500)); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); + let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); + + assert!(first ^ second); engine.stop(); } } From ee653bc262a02a55d4e075f049656b97238d77a4 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 16:53:28 +0100 Subject: [PATCH 337/382] unused import --- ethcore/src/engines/tendermint/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2a9925e8932..bb6d54ca55c 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -661,7 +661,7 @@ mod tests { use block::*; use error::{Error, BlockError}; use header::Header; - use io::{IoService, IoChannel}; + use io::IoChannel; use env_info::EnvInfo; use tests::helpers::*; use account_provider::AccountProvider; From 17781a5ac4083126cc0343a3393b252d37bf1ad1 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 17:52:28 +0100 Subject: [PATCH 338/382] Fix typo in method call --- js/src/api/contract/contract.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 95dcf2e72cc..2a090e8c6f7 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -458,7 +458,7 @@ export default class Contract { } try { - this.sendData(subscriptionId, null, this.parseEventLogs(logs)); + this._sendData(subscriptionId, null, this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } From 87f1486907c2b047d0d70b2d48be2af3efa1cd28 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Thu, 15 Dec 2016 17:03:27 +0000 Subject: [PATCH 339/382] [ci skip] js-precompiled 20161215-170041 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 511218335b8..2626a8c96c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#762c6d10f8640a6c4b875d776490282680bfe3e2" +source = "git+https://github.com/ethcore/js-precompiled.git#ad6617a73dbb17c53dddc0fc567e70ea5b8e882f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 83d7994e751..66a60385b74 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.124", + "version": "0.2.125", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 0e963da27b3389d1d3b27d5daf47cefb4cb1a584 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Thu, 15 Dec 2016 18:09:52 +0100 Subject: [PATCH 340/382] fix verification stores :bug: --- js/src/modals/Verification/email-store.js | 2 +- js/src/modals/Verification/sms-store.js | 2 +- js/src/modals/Verification/store.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index 3d7faa9e163..7855732c7e5 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -54,7 +54,7 @@ export default class EmailVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, EmailVerificationABI, 'emailverification3', account, isTestnet); + super(api, EmailVerificationABI, 4, account, isTestnet); } requestValues = () => [ sha3(this.email) ] diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js index 44c5aa39cc4..84a44545211 100644 --- a/js/src/modals/Verification/sms-store.js +++ b/js/src/modals/Verification/sms-store.js @@ -53,7 +53,7 @@ export default class SMSVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, SMSVerificationABI, 'smsverification', account, isTestnet); + super(api, SMSVerificationABI, 0, account, isTestnet); } @action setNumber = (number) => { diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index dcbfbf698e0..692fc58a35e 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -46,13 +46,13 @@ export default class VerificationStore { @observable isCodeValid = null; @observable confirmationTx = null; - constructor (api, abi, name, account, isTestnet) { + constructor (api, abi, certifierId, account, isTestnet) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; - Contracts.get().badgeReg.fetchCertifier(name) + Contracts.get().badgeReg.fetchCertifier(certifierId) .then(({ address }) => { this.contract = new Contract(api, abi).at(address); this.load(); From 1f1dc8b89d0f15ab7c6bdec186c257a71fb64a10 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 15 Dec 2016 18:09:59 +0100 Subject: [PATCH 341/382] Store subscriptionId, use it to send changes --- js/src/api/contract/contract.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 2a090e8c6f7..68c0371a116 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -342,7 +342,8 @@ export default class Contract { options: _options, autoRemove, callback, - filterId + filterId, + id: subscriptionId }; if (skipInitFetch) { @@ -452,13 +453,13 @@ export default class Contract { }) ) .then((logsArray) => { - logsArray.forEach((logs, subscriptionId) => { + logsArray.forEach((logs, index) => { if (!logs || !logs.length) { return; } try { - this._sendData(subscriptionId, null, this.parseEventLogs(logs)); + this._sendData(subscriptions[index].id, null, this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } From 2952ea1b854d0e5f8d3f69ed14320014887bad59 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 18:19:19 +0100 Subject: [PATCH 342/382] Delayed transactions --- ethcore/light/src/client.rs | 6 +- ethcore/light/src/net/tests/mod.rs | 4 +- ethcore/light/src/provider.rs | 8 +- ethcore/res/ethereum/tests | 2 +- ethcore/src/client/client.rs | 4 +- ethcore/src/client/test_client.rs | 4 +- ethcore/src/client/traits.rs | 4 +- ethcore/src/miner/banning_queue.rs | 4 +- ethcore/src/miner/miner.rs | 57 ++-- ethcore/src/miner/mod.rs | 13 +- ethcore/src/miner/transaction_queue.rs | 341 +++++++++++++--------- ethcore/src/types/transaction.rs | 28 ++ rpc/src/v1/helpers/dispatch.rs | 11 +- rpc/src/v1/helpers/requests.rs | 5 + rpc/src/v1/helpers/signing_queue.rs | 1 + rpc/src/v1/impls/eth.rs | 4 +- rpc/src/v1/impls/parity.rs | 6 + rpc/src/v1/impls/signer.rs | 8 +- rpc/src/v1/tests/helpers/miner_service.rs | 20 +- rpc/src/v1/tests/mocked/eth.rs | 3 +- rpc/src/v1/tests/mocked/signer.rs | 8 +- rpc/src/v1/tests/mocked/signing.rs | 1 + rpc/src/v1/traits/parity.rs | 4 + rpc/src/v1/types/block.rs | 2 +- rpc/src/v1/types/confirmations.rs | 15 +- rpc/src/v1/types/transaction.rs | 17 +- rpc/src/v1/types/transaction_request.rs | 15 +- sync/src/chain.rs | 10 +- sync/src/tests/consensus.rs | 4 +- 29 files changed, 387 insertions(+), 222 deletions(-) diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 73e85b31c20..80100f17c02 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -24,7 +24,7 @@ use ethcore::service::ClientIoMessage; use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; use ethcore::verification::queue::{HeaderQueue, QueueInfo}; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::{SignedTransaction, PendingTransaction}; use ethcore::blockchain_info::BlockChainInfo; use io::IoChannel; @@ -114,7 +114,7 @@ impl Provider for Client { Vec::new() } - fn pending_transactions(&self) -> Vec<SignedTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { Vec::new() } -} \ No newline at end of file +} diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 99f695b8108..f9551fc9a1b 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -20,7 +20,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; use ethcore::ids::BlockId; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::PendingTransaction; use network::{PeerId, NodeId}; use net::buffer_flow::FlowParams; @@ -169,7 +169,7 @@ impl Provider for TestProvider { req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() } - fn pending_transactions(&self) -> Vec<SignedTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { self.0.client.pending_transactions() } } diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index d820ee6d0c4..ce80929c2d9 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -19,7 +19,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; use util::{Bytes, H256}; @@ -79,7 +79,7 @@ pub trait Provider: Send + Sync { fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>; /// Provide pending transactions. - fn pending_transactions(&self) -> Vec<SignedTransaction>; + fn pending_transactions(&self) -> Vec<PendingTransaction>; } // Implementation of a light client data provider for a client. @@ -178,7 +178,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() } - fn pending_transactions(&self) -> Vec<SignedTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { BlockChainClient::pending_transactions(self) } -} \ No newline at end of file +} diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index dce59461733..39f7420f8e4 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -42,7 +42,7 @@ use env_info::LastHashes; use verification; use verification::{PreverifiedBlock, Verifier}; use block::*; -use transaction::{LocalizedTransaction, SignedTransaction, Action}; +use transaction::{LocalizedTransaction, SignedTransaction, PendingTransaction, Action}; use blockchain::extras::TransactionAddress; use types::filter::Filter; use types::mode::Mode as IpcMode; @@ -1286,7 +1286,7 @@ impl BlockChainClient for Client { } } - fn pending_transactions(&self) -> Vec<SignedTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { self.miner.pending_transactions(self.chain.read().best_block_number()) } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 44954f99daa..f730c7d9c4d 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -21,7 +21,7 @@ use util::*; use rlp::*; use ethkey::{Generator, Random}; use devtools::*; -use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; +use transaction::{Transaction, LocalizedTransaction, SignedTransaction, PendingTransaction, Action}; use blockchain::TreeRoute; use client::{ BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, @@ -663,7 +663,7 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } - fn pending_transactions(&self) -> Vec<SignedTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { self.miner.pending_transactions(self.chain_info().best_block_number) } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index f44a4496f72..7afe96f57ff 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -21,7 +21,7 @@ use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use header::{BlockNumber}; -use transaction::{LocalizedTransaction, SignedTransaction}; +use transaction::{LocalizedTransaction, SignedTransaction, PendingTransaction}; use log_entry::LocalizedLogEntry; use filter::Filter; use views::{BlockView}; @@ -203,7 +203,7 @@ pub trait BlockChainClient : Sync + Send { fn queue_transactions(&self, transactions: Vec<Bytes>, peer_id: usize); /// list all transactions - fn pending_transactions(&self) -> Vec<SignedTransaction>; + fn pending_transactions(&self) -> Vec<PendingTransaction>; /// Sorted list of transaction gas prices from at least last sample_size blocks. fn gas_price_corpus(&self, sample_size: usize) -> Vec<U256> { diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs index d8b038c1561..1fe345614c4 100644 --- a/ethcore/src/miner/banning_queue.rs +++ b/ethcore/src/miner/banning_queue.rs @@ -115,7 +115,7 @@ impl BanningTransactionQueue { } } } - self.queue.add(transaction, TransactionOrigin::External, account_details, gas_estimator) + self.queue.add(transaction, TransactionOrigin::External, None, account_details, gas_estimator) } /// Ban transaction with given hash. @@ -263,7 +263,7 @@ mod tests { let mut txq = queue(); // when - txq.queue().add(tx, TransactionOrigin::External, &default_account_details, &gas_required).unwrap(); + txq.queue().add(tx, TransactionOrigin::External, None, &default_account_details, &gas_required).unwrap(); // then // should also deref to queue diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index a6a63ccaf0b..a01f1652321 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -28,7 +28,7 @@ use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; use error::*; -use transaction::{Action, SignedTransaction}; +use transaction::{Action, SignedTransaction, PendingTransaction}; use receipt::{Receipt, RichReceipt}; use spec::Spec; use engines::Engine; @@ -320,11 +320,12 @@ impl Miner { } let _timer = PerfTimer::new("prepare_block"); + let chain_info = chain.chain_info(); let (transactions, mut open_block, original_work_hash) = { - let transactions = {self.transaction_queue.lock().top_transactions()}; + let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number)}; let mut sealing_work = self.sealing_work.lock(); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); - let best_hash = chain.best_block_header().sha3(); + let best_hash = chain_info.best_block_hash; /* // check to see if last ClosedBlock in would_seals is actually same parent block. // if so @@ -568,8 +569,14 @@ impl Miner { prepare_new } - fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec<SignedTransaction>, default_origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> - Vec<Result<TransactionImportResult, Error>> { + fn add_transactions_to_queue( + &self, + chain: &MiningBlockChainClient, + transactions: Vec<SignedTransaction>, + default_origin: TransactionOrigin, + min_block: Option<BlockNumber>, + transaction_queue: &mut BanningTransactionQueue) + -> Vec<Result<TransactionImportResult, Error>> { let fetch_account = |a: &Address| AccountDetails { nonce: chain.latest_nonce(a), @@ -604,7 +611,7 @@ impl Miner { match origin { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - transaction_queue.add(tx, origin, &fetch_account, &gas_required) + transaction_queue.add(tx, origin, min_block, &fetch_account, &gas_required) }, TransactionOrigin::External => { transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) @@ -830,7 +837,7 @@ impl MinerService for Miner { let results = { let mut transaction_queue = self.transaction_queue.lock(); self.add_transactions_to_queue( - chain, transactions, TransactionOrigin::External, &mut transaction_queue + chain, transactions, TransactionOrigin::External, None, &mut transaction_queue ) }; @@ -848,17 +855,17 @@ impl MinerService for Miner { fn import_own_transaction( &self, chain: &MiningBlockChainClient, - transaction: SignedTransaction, + pending: PendingTransaction, ) -> Result<TransactionImportResult, Error> { - let hash = transaction.hash(); - trace!(target: "own_tx", "Importing transaction: {:?}", transaction); + let hash = pending.transaction.hash(); + trace!(target: "own_tx", "Importing transaction: {:?}", pending); let imported = { // Be sure to release the lock before we call prepare_work_sealing let mut transaction_queue = self.transaction_queue.lock(); let import = self.add_transactions_to_queue( - chain, vec![transaction], TransactionOrigin::Local, &mut transaction_queue + chain, vec![pending.transaction], TransactionOrigin::Local, pending.min_block, &mut transaction_queue ).pop().expect("one result returned per added transaction; one added => one result; qed"); match import { @@ -893,9 +900,9 @@ impl MinerService for Miner { imported } - fn all_transactions(&self) -> Vec<SignedTransaction> { + fn all_transactions(&self) -> Vec<PendingTransaction> { let queue = self.transaction_queue.lock(); - queue.top_transactions() + queue.pending_transactions(BlockNumber::max_value()) } fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> { @@ -906,22 +913,26 @@ impl MinerService for Miner { .collect() } - fn pending_transactions(&self, best_block: BlockNumber) -> Vec<SignedTransaction> { + fn future_transactions(&self) -> Vec<PendingTransaction> { + self.transaction_queue.lock().future_transactions() + } + + fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { let queue = self.transaction_queue.lock(); match self.options.pending_set { - PendingSet::AlwaysQueue => queue.top_transactions(), + PendingSet::AlwaysQueue => queue.pending_transactions(best_block), PendingSet::SealingOrElseQueue => { self.from_pending_block( best_block, - || queue.top_transactions(), - |sealing| sealing.transactions().to_owned() + || queue.pending_transactions(best_block), + |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() ) }, PendingSet::AlwaysSealing => { self.from_pending_block( best_block, || vec![], - |sealing| sealing.transactions().to_owned() + |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() ) }, } @@ -1116,7 +1127,7 @@ impl MinerService for Miner { }).for_each(|txs| { let mut transaction_queue = self.transaction_queue.lock(); let _ = self.add_transactions_to_queue( - chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue + chain, txs, TransactionOrigin::RetractedBlock, None, &mut transaction_queue ); }); } @@ -1149,7 +1160,7 @@ mod tests { use ethkey::{Generator, Random}; use client::{BlockChainClient, TestBlockChainClient, EachBlockWith, TransactionImportResult}; use header::BlockNumber; - use types::transaction::{Transaction, SignedTransaction, Action}; + use types::transaction::{Transaction, SignedTransaction, PendingTransaction, Action}; use spec::Spec; use tests::helpers::{generate_dummy_client}; @@ -1228,7 +1239,7 @@ mod tests { let transaction = transaction(); let best_block = 0; // when - let res = miner.import_own_transaction(&client, transaction); + let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1248,7 +1259,7 @@ mod tests { let transaction = transaction(); let best_block = 10; // when - let res = miner.import_own_transaction(&client, transaction); + let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1305,7 +1316,7 @@ mod tests { assert!(miner.pending_block().is_none()); assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber); - assert_eq!(miner.import_own_transaction(client, transaction()).unwrap(), TransactionImportResult::Current); + assert_eq!(miner.import_own_transaction(client, PendingTransaction::new(transaction(), None)).unwrap(), TransactionImportResult::Current); miner.update_sealing(client); client.flush_queue(); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 814953c81a1..fa8e7df0ab3 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -62,7 +62,7 @@ use block::ClosedBlock; use header::BlockNumber; use receipt::{RichReceipt, Receipt}; use error::{Error, CallError}; -use transaction::SignedTransaction; +use transaction::{SignedTransaction, PendingTransaction}; /// Miner client API pub trait MinerService : Send + Sync { @@ -118,7 +118,7 @@ pub trait MinerService : Send + Sync { Vec<Result<TransactionImportResult, Error>>; /// Imports own (node owner) transaction to queue. - fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction) -> + fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: PendingTransaction) -> Result<TransactionImportResult, Error>; /// Returns hashes of transactions currently in pending @@ -144,11 +144,14 @@ pub trait MinerService : Send + Sync { /// Query pending transactions for hash. fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<SignedTransaction>; - /// Get a list of all transactions. - fn all_transactions(&self) -> Vec<SignedTransaction>; + /// Get a list of all ready transactions in the queue. + fn all_transactions(&self) -> Vec<PendingTransaction>; /// Get a list of all pending transactions. - fn pending_transactions(&self, best_block: BlockNumber) -> Vec<SignedTransaction>; + fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>; + + /// Get a list of all future transactions. + fn future_transactions(&self) -> Vec<PendingTransaction>; /// Get a list of local transactions with statuses. fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus>; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 28b39c7e6c2..a13fc01c4f3 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -51,8 +51,8 @@ //! let gas_estimator = |_tx: &SignedTransaction| 2.into(); //! //! let mut txq = TransactionQueue::default(); -//! txq.add(st2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); -//! txq.add(st1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); +//! txq.add(st2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); +//! txq.add(st1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); //! //! // Check status //! assert_eq!(txq.status().pending, 2); @@ -94,6 +94,7 @@ use util::table::Table; use transaction::*; use error::{Error, TransactionError}; use client::TransactionImportResult; +use header::BlockNumber; use miner::local_transactions::{LocalTransactionsList, Status as LocalTransactionStatus}; /// Transaction origin @@ -253,18 +254,21 @@ impl Ord for TransactionOrder { /// Verified transaction (with sender) #[derive(Debug)] struct VerifiedTransaction { - /// Transaction + /// Transaction. transaction: SignedTransaction, - /// transaction origin + /// Transaction origin. origin: TransactionOrigin, + /// Delay until specifid block. + min_block: Option<BlockNumber>, } impl VerifiedTransaction { - fn new(transaction: SignedTransaction, origin: TransactionOrigin) -> Result<Self, Error> { + fn new(transaction: SignedTransaction, origin: TransactionOrigin, min_block: Option<BlockNumber>) -> Result<Self, Error> { try!(transaction.sender()); Ok(VerifiedTransaction { transaction: transaction, origin: origin, + min_block: min_block, }) } @@ -620,6 +624,7 @@ impl TransactionQueue { &mut self, tx: SignedTransaction, origin: TransactionOrigin, + min_block: Option<BlockNumber>, fetch_account: &F, gas_estimator: &G, ) -> Result<TransactionImportResult, Error> where @@ -630,7 +635,7 @@ impl TransactionQueue { let hash = tx.hash(); let cloned_tx = tx.clone(); - let result = self.add_internal(tx, origin, fetch_account, gas_estimator); + let result = self.add_internal(tx, origin, min_block, fetch_account, gas_estimator); match result { Ok(TransactionImportResult::Current) => { self.local_transactions.mark_pending(hash); @@ -651,7 +656,7 @@ impl TransactionQueue { } result } else { - self.add_internal(tx, origin, fetch_account, gas_estimator) + self.add_internal(tx, origin, min_block, fetch_account, gas_estimator) } } @@ -660,6 +665,7 @@ impl TransactionQueue { &mut self, tx: SignedTransaction, origin: TransactionOrigin, + min_block: Option<BlockNumber>, fetch_account: &F, gas_estimator: &G, ) -> Result<TransactionImportResult, Error> where @@ -728,7 +734,7 @@ impl TransactionQueue { // Verify signature try!(tx.check_low_s()); - let vtx = try!(VerifiedTransaction::new(tx, origin)); + let vtx = try!(VerifiedTransaction::new(tx, origin, min_block)); let client_account = fetch_account(&vtx.sender()); let cost = vtx.transaction.value + vtx.transaction.gas_price * vtx.transaction.gas; @@ -968,27 +974,56 @@ impl TransactionQueue { /// Returns top transactions from the queue ordered by priority. pub fn top_transactions(&self) -> Vec<SignedTransaction> { - self.current.by_priority - .iter() - .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) - .map(|t| t.transaction.clone()) - .collect() + self.top_transactions_at(BlockNumber::max_value()) + } - /// Returns local transactions (some of them might not be part of the queue anymore). - pub fn local_transactions(&self) -> &LinkedHashMap<H256, LocalTransactionStatus> { - self.local_transactions.all_transactions() + fn collect_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F) + where F: FnMut(&VerifiedTransaction) { + + let mut delayed = HashSet::new(); + for t in self.current.by_priority.iter() { + let tx = self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`"); + let sender = tx.transaction.sender().expect("Queue only contains transactions with valid sender"); + if delayed.contains(&sender) { + continue; + } + if tx.min_block.unwrap_or(0) > best_block { + delayed.insert(sender); + continue; + } + f(&tx); + } + } + + /// Returns top transactions from the queue ordered by priority. + pub fn top_transactions_at(&self, best_block: BlockNumber) -> Vec<SignedTransaction> { + let mut r = Vec::new(); + self.collect_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); + r } - #[cfg(test)] - fn future_transactions(&self) -> Vec<SignedTransaction> { + /// Return all ready transactions. + pub fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { + let mut r = Vec::new(); + self.collect_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); + r + } + + /// Return all ready transactions. + pub fn future_transactions(&self) -> Vec<PendingTransaction> { self.future.by_priority .iter() .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) - .map(|t| t.transaction.clone()) + .map(|t| PendingTransaction { transaction: t.transaction.clone(), min_block: t.min_block }) .collect() } + /// Returns local transactions (some of them might not be part of the queue anymore). + pub fn local_transactions(&self) -> &LinkedHashMap<H256, LocalTransactionStatus> { + self.local_transactions.all_transactions() + } + /// Returns hashes of all transactions from current, ordered by priority. pub fn pending_hashes(&self) -> Vec<H256> { self.current.by_priority @@ -1353,14 +1388,14 @@ mod test { let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let sender = tx1.sender().unwrap(); let nonce = tx1.nonce; - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); // when let tx = new_tx(123.into(), 1.into()); - let res = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then // No longer the case as we don't even consider a transaction that isn't above a full @@ -1392,12 +1427,12 @@ mod test { gas_limit: !U256::zero(), }; let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External).unwrap(); - let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None).unwrap(); + let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None).unwrap(); let mut by_hash = { let mut x = HashMap::new(); - let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External).unwrap(); - let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None).unwrap(); + let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None).unwrap(); x.insert(tx1.hash(), tx1); x.insert(tx2.hash(), tx2); x @@ -1435,12 +1470,12 @@ mod test { // Create two transactions with same nonce // (same hash) let (tx1, tx2) = new_tx_pair_default(0.into(), 0.into()); - let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External).unwrap(); - let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None).unwrap(); + let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None).unwrap(); let by_hash = { let mut x = HashMap::new(); - let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External).unwrap(); - let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None).unwrap(); + let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None).unwrap(); x.insert(tx1.hash(), tx1); x.insert(tx2.hash(), tx2); x @@ -1482,10 +1517,10 @@ mod test { gas_limit: !U256::zero(), }; let tx = new_tx_default(); - let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None).unwrap(); let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none()); - let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External).unwrap(); + let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External, None).unwrap(); let order2 = TransactionOrder::for_transaction(&tx2, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some()); } @@ -1502,7 +1537,7 @@ mod test { assert_eq!(set.gas_price_entry_limit(), 0.into()); let tx = new_tx_default(); - let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); + let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None).unwrap(); let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none()); assert_eq!(set.gas_price_entry_limit(), 2.into()); @@ -1517,12 +1552,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx, TransactionOrigin::External, &prev_nonce, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator); // and then there should be only one transaction in current (the one with higher gas_price) assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1542,12 +1577,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator); + let res = txq.add(tx.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1566,7 +1601,7 @@ mod test { let tx = new_tx_default(); // when - let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1585,10 +1620,10 @@ mod test { txq.set_minimal_gas_price(15.into()); // when - let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res1 = txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1619,10 +1654,10 @@ mod test { txq.set_minimal_gas_price(15.into()); // when - let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); - let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res1 = txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1665,7 +1700,7 @@ mod test { txq.set_gas_limit(limit); // when - let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::GasLimitExceeded { @@ -1689,7 +1724,7 @@ mod test { }; // when - let res = txq.add(tx, TransactionOrigin::External, &account, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &account, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientBalance { @@ -1709,7 +1744,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientGasPrice { @@ -1729,7 +1764,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, TransactionOrigin::Local, &default_account_details, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::Local, None, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1759,7 +1794,7 @@ mod test { rlp::decode(s.as_raw()) }; // when - let res = txq.add(stx, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(stx, TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then assert!(res.is_err()); @@ -1773,8 +1808,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1793,9 +1828,9 @@ mod test { // when // first insert the one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but local - txq.add(tx.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1812,15 +1847,15 @@ mod test { // the second one has same nonce but higher `gas_price` let (_, tx0) = new_similar_tx_pair(); - txq.add(tx0.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx0.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // the one with higher gas price is first assert_eq!(txq.top_transactions()[0], tx0); assert_eq!(txq.top_transactions()[1], tx1); // when // insert second as local - txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); // then // the order should be updated @@ -1839,9 +1874,9 @@ mod test { // when // first insert local one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but from retracted block - txq.add(tx.clone(), TransactionOrigin::RetractedBlock, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::RetractedBlock, None, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1857,8 +1892,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1877,10 +1912,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 4); @@ -1889,10 +1924,10 @@ mod test { // then let top = txq.future_transactions(); - assert_eq!(top[0], txa); - assert_eq!(top[1], txb); - assert_eq!(top[2], tx1); - assert_eq!(top[3], tx2); + assert_eq!(top[0].transaction, txa); + assert_eq!(top[1].transaction, txb); + assert_eq!(top[2].transaction, tx1); + assert_eq!(top[3].transaction, tx2); assert_eq!(top.len(), 4); } @@ -1905,10 +1940,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(txb.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(txa.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); let top = txq.top_transactions(); assert_eq!(top[0], tx1); @@ -1938,10 +1973,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); let top = txq.top_transactions(); assert_eq!(top[0], tx1); @@ -1970,8 +2005,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.pending_hashes(); @@ -1988,8 +2023,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - let res2 = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + let res1 = txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(res1, TransactionImportResult::Current); @@ -2002,6 +2037,26 @@ mod test { assert_eq!(top[0], tx); } + #[test] + fn should_handle_min_block() { + // given + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // when + let res1 = txq.add(tx.clone(), TransactionOrigin::External, Some(1), &default_account_details, &gas_estimator).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + + // then + assert_eq!(res1, TransactionImportResult::Current); + assert_eq!(res2, TransactionImportResult::Current); + let top = txq.top_transactions_at(0); + assert_eq!(top.len(), 0); + let top = txq.top_transactions_at(1); + assert_eq!(top.len(), 2); + } + #[test] fn should_correctly_update_futures_when_removing() { // given @@ -2012,8 +2067,8 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -2035,13 +2090,13 @@ mod test { let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None); let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None); - txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); - txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); // when - txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2057,8 +2112,8 @@ mod test { // given let mut txq2 = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); - txq2.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq2.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq2.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq2.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq2.status().pending, 1); assert_eq!(txq2.status().future, 1); @@ -2079,10 +2134,10 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -2101,8 +2156,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // add - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); let stats = txq.status(); assert_eq!(stats.pending, 2); @@ -2121,11 +2176,11 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); + let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator); // then let t = txq.top_transactions(); @@ -2142,14 +2197,14 @@ mod test { txq.current.set_limit(10); let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(txq.status().future, 1); @@ -2160,11 +2215,11 @@ mod test { let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // limited by gas - txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); + txq.add(tx4.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap_err(); assert_eq!(txq.status().pending, 2); } @@ -2174,12 +2229,12 @@ mod test { let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit - txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); - txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx5.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap_err(); + txq.add(tx3.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 4); } @@ -2191,7 +2246,7 @@ mod test { let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() }; // when - let res = txq.add(tx, TransactionOrigin::External, &fetch_last_nonce, &gas_estimator); + let res = txq.add(tx, TransactionOrigin::External, None, &fetch_last_nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::Old); @@ -2207,12 +2262,12 @@ mod test { balance: !U256::zero() }; let mut txq = TransactionQueue::default(); let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); assert_eq!(txq.status().pending, 0); // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, &nonce, &gas_estimator); + let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::AlreadyImported); @@ -2226,15 +2281,15 @@ mod test { // given let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when txq.remove_invalid(&tx1.hash(), &default_account_details); assert_eq!(txq.status().pending, 0); assert_eq!(txq.status().future, 1); - txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2248,10 +2303,10 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -2278,8 +2333,8 @@ mod test { }; // when - txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2306,10 +2361,10 @@ mod test { }; // when - txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx0, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx0, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2327,8 +2382,8 @@ mod test { !U256::zero() }; let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); - txq.add(tx2, TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, None, &previous_nonce, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, None, &previous_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -2359,7 +2414,7 @@ mod test { let details = |_a: &Address| AccountDetails { nonce: nonce, balance: !U256::zero() }; // when - txq.add(tx, TransactionOrigin::External, &details, &gas_estimator).unwrap(); + txq.add(tx, TransactionOrigin::External, None, &details, &gas_estimator).unwrap(); // then assert_eq!(txq.last_nonce(&from), Some(nonce)); @@ -2374,7 +2429,7 @@ mod test { let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() }; // Insert first transaction - txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(); + txq.add(tx1, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap(); // when txq.remove_all(tx2.sender().unwrap(), nonce2 + U256::one()); @@ -2394,9 +2449,9 @@ mod test { // when // Insert first transaction - assert_eq!(txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current); // Second should go to future - assert_eq!(txq.add(tx2, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future); // Now block is imported txq.remove_all(sender, nonce2 - U256::from(1)); // tx2 should be not be promoted to current @@ -2415,9 +2470,9 @@ mod test { assert_eq!(txq.has_local_pending_transactions(), false); // when - assert_eq!(txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); assert_eq!(txq.has_local_pending_transactions(), false); - assert_eq!(txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); // then assert_eq!(txq.has_local_pending_transactions(), true); @@ -2432,8 +2487,8 @@ mod test { default_account_details(a).balance }; // when - assert_eq!(txq.add(tx2, TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); - assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); // then assert_eq!(txq.future.by_priority.len(), 1); @@ -2458,14 +2513,14 @@ mod test { (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) }; let sender = tx1.sender().unwrap(); - txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx3, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.future.by_priority.len(), 0); assert_eq!(txq.current.by_priority.len(), 3); // when - let res = txq.add(tx2_2, TransactionOrigin::Local, &default_account_details, &gas_estimator); + let res = txq.add(tx2_2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator); // then assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); @@ -2481,8 +2536,8 @@ mod test { let high_gas = |_: &SignedTransaction| 100_001.into(); // when - let res1 = txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator); - let res2 = txq.add(tx2, TransactionOrigin::Local, &default_account_details, &high_gas); + let res1 = txq.add(tx1, TransactionOrigin::Local, None, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &high_gas); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -2502,10 +2557,10 @@ mod test { let nonce1 = tx1.nonce; // Insert all transactions - txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); - txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.top_transactions().len(), 4); // when diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 6388120c3f9..681e38fcce9 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -390,6 +390,34 @@ impl Deref for LocalizedTransaction { } } +/// Queued information with additional information. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct PendingTransaction { + /// Signed transaction data. + pub transaction: SignedTransaction, + /// Gas price. + pub min_block: Option<BlockNumber>, +} + +impl PendingTransaction { + /// Create a new pending transaction from signed transaction. + pub fn new(signed: SignedTransaction, min_block: Option<BlockNumber>) -> Self { + PendingTransaction { + transaction: signed, + min_block: min_block, + } + } +} + +impl From<SignedTransaction> for PendingTransaction { + fn from(t: SignedTransaction) -> Self { + PendingTransaction { + transaction: t, + min_block: None, + } + } +} + #[test] fn sender_test() { let t: SignedTransaction = decode(&::rustc_serialize::hex::FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap()); diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 93e99646be4..79727f50e1d 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -21,7 +21,7 @@ use util::bytes::ToPretty; use ethkey::Signature; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; -use ethcore::transaction::{Action, SignedTransaction, Transaction}; +use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction}; use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; @@ -79,9 +79,9 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: O }) } -pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<H256, Error> +pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: PendingTransaction) -> Result<H256, Error> where C: MiningBlockChainClient, M: MinerService { - let hash = signed_transaction.hash(); + let hash = signed_transaction.transaction.hash(); miner.import_own_transaction(client, signed_transaction) .map_err(errors::from_transaction_error) @@ -120,10 +120,12 @@ pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider { let network_id = client.signing_network_id(); + let min_block = filled.min_block.clone(); let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password)); trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); - dispatch_transaction(&*client, &*miner, signed_transaction) + let pending_transaction = PendingTransaction::new(signed_transaction, min_block); + dispatch_transaction(&*client, &*miner, pending_transaction) } pub fn fill_optional_fields<C, M>(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest @@ -137,6 +139,7 @@ pub fn fill_optional_fields<C, M>(request: TransactionRequest, client: &C, miner gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), value: request.value.unwrap_or_else(|| 0.into()), data: request.data.unwrap_or_else(Vec::new), + min_block: request.min_block, } } diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 7249e4c4a98..2c14a4b99c4 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -33,6 +33,8 @@ pub struct TransactionRequest { pub data: Option<Bytes>, /// Transaction's nonce pub nonce: Option<U256>, + /// Delay until this block if specified. + pub min_block: Option<u64>, } /// Transaction request coming from RPC with default values filled in. @@ -52,6 +54,8 @@ pub struct FilledTransactionRequest { pub data: Bytes, /// Transaction's nonce pub nonce: Option<U256>, + /// Delay until this block if specified. + pub min_block: Option<u64>, } impl From<FilledTransactionRequest> for TransactionRequest { @@ -64,6 +68,7 @@ impl From<FilledTransactionRequest> for TransactionRequest { value: Some(r.value), data: Some(r.data), nonce: r.nonce, + min_block: r.min_block, } } } diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 03489bad73d..7f5d3a6d1d1 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -328,6 +328,7 @@ mod test { value: 10_000_000.into(), data: vec![], nonce: None, + min_block: None, }) } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 1364af03316..4bb5458cb28 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -38,7 +38,7 @@ use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; -use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; +use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, PendingTransaction, Action}; use ethcore::log_entry::LogEntry; use ethcore::filter::Filter as EthcoreFilter; use ethcore::snapshot::SnapshotService; @@ -613,7 +613,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where let raw_transaction = raw.to_vec(); match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction).map(Into::into), + Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), PendingTransaction::new(signed_transaction, None)).map(Into::into), Err(e) => Err(errors::from_rlp_error(e)), } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 2c6a498a1f1..a2781b1c660 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -265,6 +265,12 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where Ok(take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::<Vec<_>>()) } + fn future_transactions(&self) -> Result<Vec<Transaction>, Error> { + try!(self.active()); + + Ok(take_weak!(self.miner).future_transactions().into_iter().map(Into::into).collect::<Vec<_>>()) + } + fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error> { try!(self.active()); diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 6e09a5ec819..5a81b820557 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -21,7 +21,7 @@ use std::sync::{Arc, Weak}; use rlp::{UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::{SignedTransaction, PendingTransaction}; use ethcore::miner::MinerService; use jsonrpc_core::Error; @@ -96,6 +96,9 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC if let Some(gas) = modification.gas { request.gas = gas.into(); } + if let Some(min_block) = modification.min_block { + request.min_block = min_block; + } } // Execute let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); @@ -135,7 +138,8 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC // Dispatch if everything is ok if sender_matches && data_matches && value_matches && nonce_matches { - dispatch_transaction(&*client, &*miner, signed_transaction) + let pending_transaction = PendingTransaction::new(signed_transaction, request.min_block); + dispatch_transaction(&*client, &*miner, pending_transaction) .map(Into::into) .map(ConfirmationResponse::SendTransaction) } else { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 132e2a5e063..0ef0ee0a972 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -22,7 +22,7 @@ use ethcore::error::{Error, CallError}; use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics}; use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::header::BlockNumber; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::{SignedTransaction, PendingTransaction}; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; use ethcore::account_provider::Error as AccountError; @@ -160,17 +160,17 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction) -> + fn import_own_transaction(&self, chain: &MiningBlockChainClient, pending: PendingTransaction) -> Result<TransactionImportResult, Error> { // keep the pending nonces up to date - if let Ok(ref sender) = transaction.sender() { + if let Ok(ref sender) = pending.transaction.sender() { let nonce = self.last_nonce(sender).unwrap_or(chain.latest_nonce(sender)); self.last_nonces.write().insert(sender.clone(), nonce + U256::from(1)); } // lets assume that all txs are valid - self.imported_transactions.lock().push(transaction); + self.imported_transactions.lock().push(pending.transaction); Ok(TransactionImportResult::Current) } @@ -204,16 +204,20 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().get(hash).cloned() } - fn all_transactions(&self) -> Vec<SignedTransaction> { - self.pending_transactions.lock().values().cloned().collect() + fn all_transactions(&self) -> Vec<PendingTransaction> { + self.pending_transactions.lock().values().cloned().map(Into::into).collect() } fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> { self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() } - fn pending_transactions(&self, _best_block: BlockNumber) -> Vec<SignedTransaction> { - self.pending_transactions.lock().values().cloned().collect() + fn pending_transactions(&self, _best_block: BlockNumber) -> Vec<PendingTransaction> { + self.pending_transactions.lock().values().cloned().map(Into::into).collect() + } + + fn future_transactions(&self) -> Vec<PendingTransaction> { + vec![] } fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index ea8409ef203..b8707a1595e 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -494,7 +494,7 @@ fn rpc_eth_pending_transaction_by_hash() { tester.miner.pending_transactions.lock().insert(H256::zero(), tx); } - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","minBlock":null,"networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; let request = r#"{ "jsonrpc": "2.0", "method": "eth_getTransactionByHash", @@ -810,6 +810,7 @@ fn rpc_eth_sign_transaction() { r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + &format!("\"hash\":\"0x{:?}\",", t.hash()) + r#""input":"0x","# + + r#""minBlock":null,"# + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""nonce":"0x1","# + &format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) + diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 3537717d4a9..7ec023a968c 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -82,6 +82,7 @@ fn should_return_list_of_items_to_confirm() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), 5.into())).unwrap(); @@ -89,7 +90,7 @@ fn should_return_list_of_items_to_confirm() { let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, - r#"{"id":"0x1","payload":{"sendTransaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, + r#"{"id":"0x1","payload":{"sendTransaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","minBlock":null,"nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#, r#"],"id":1}"# ); @@ -111,6 +112,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); assert_eq!(tester.signer.requests().len(), 1); @@ -136,6 +138,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); assert_eq!(tester.signer.requests().len(), 1); @@ -178,6 +181,7 @@ fn should_confirm_transaction_and_dispatch() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); let t = Transaction { @@ -223,6 +227,7 @@ fn should_confirm_transaction_with_rlp() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); let t = Transaction { @@ -270,6 +275,7 @@ fn should_return_error_when_sender_does_not_match() { value: U256::from(1), data: vec![], nonce: None, + min_block: None, })).unwrap(); let t = Transaction { diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 5217ad402bf..4040b57b1a7 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -285,6 +285,7 @@ fn should_add_sign_transaction_to_the_queue() { r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + &format!("\"hash\":\"0x{:?}\",", t.hash()) + r#""input":"0x","# + + r#""minBlock":null,"# + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""nonce":"0x1","# + &format!("\"publicKey\":\"0x{:?}\",", t.public_key().unwrap()) + diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index fecc05667af..da7125f16d0 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -122,6 +122,10 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactions")] fn pending_transactions(&self) -> Result<Vec<Transaction>, Error>; + /// Returns all future transactions from transaction queue. + #[rpc(name = "parity_futureTransactions")] + fn future_transactions(&self) -> Result<Vec<Transaction>, Error>; + /// Returns propagation statistics on transactions pending in the queue. #[rpc(name = "parity_pendingTransactionsStats")] fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error>; diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 6dd441ee825..680cbd56266 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -139,7 +139,7 @@ mod tests { fn test_serialize_block_transactions() { let t = BlockTransactions::Full(vec![Transaction::default()]); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0"}]"#); + assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}]"#); let t = BlockTransactions::Hashes(vec![H256::default().into()]); let serialized = serde_json::to_string(&t).unwrap(); diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index fd81bf6e726..92567575eec 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -144,6 +144,9 @@ pub struct TransactionModification { pub gas_price: Option<U256>, /// Modified gas pub gas: Option<U256>, + /// Modified min block + #[serde(rename="minBlock")] + pub min_block: Option<Option<u64>>, } /// Represents two possible return values. @@ -218,12 +221,13 @@ mod tests { value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), + min_block: None, }), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1"}}}"#; + let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -242,12 +246,13 @@ mod tests { value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), + min_block: None, }), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1"}}}"#; + let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -275,7 +280,8 @@ mod tests { fn should_deserialize_modification() { // given let s1 = r#"{ - "gasPrice":"0xba43b7400" + "gasPrice":"0xba43b7400", + "minBlock":42 }"#; let s2 = r#"{"gas": "0x1233"}"#; let s3 = r#"{}"#; @@ -289,14 +295,17 @@ mod tests { assert_eq!(res1, TransactionModification { gas_price: Some(U256::from_str("0ba43b7400").unwrap()), gas: None, + min_block: Some(Some(42)), }); assert_eq!(res2, TransactionModification { gas_price: None, gas: Some(U256::from_str("1233").unwrap()), + min_block: None, }); assert_eq!(res3, TransactionModification { gas_price: None, gas: None, + min_block: None, }); } } diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 31374e91203..04275323d68 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -17,7 +17,7 @@ use serde::{Serialize, Serializer}; use ethcore::miner; use ethcore::contract_address; -use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; +use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use v1::helpers::errors; use v1::types::{Bytes, H160, H256, U256, H512}; @@ -69,6 +69,9 @@ pub struct Transaction { pub r: U256, /// The S field of the signature. pub s: U256, + /// Transaction activates at specified block. + #[serde(rename="minBlock")] + pub min_block: Option<u64>, } /// Local Transaction Status @@ -187,6 +190,7 @@ impl From<LocalizedTransaction> for Transaction { v: t.original_v().into(), r: signature.r().into(), s: signature.s().into(), + min_block: None, } } } @@ -220,10 +224,19 @@ impl From<SignedTransaction> for Transaction { v: t.original_v().into(), r: signature.r().into(), s: signature.s().into(), + min_block: None, } } } +impl From<PendingTransaction> for Transaction { + fn from(t: PendingTransaction) -> Transaction { + let mut r = Transaction::from(t.transaction); + r.min_block = t.min_block; + r + } +} + impl From<miner::LocalTransactionStatus> for LocalTransactionStatus { fn from(s: miner::LocalTransactionStatus) -> Self { use ethcore::miner::LocalTransactionStatus::*; @@ -248,7 +261,7 @@ mod tests { fn test_transaction_serialize() { let t = Transaction::default(); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0"}"#); + assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}"#); } #[test] diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 258346d5626..389efa46785 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -38,6 +38,9 @@ pub struct TransactionRequest { pub data: Option<Bytes>, /// Transaction's nonce pub nonce: Option<U256>, + /// Delay until this block if specified. + #[serde(rename="minBlock")] + pub min_block: Option<u64>, } impl From<helpers::TransactionRequest> for TransactionRequest { @@ -50,6 +53,7 @@ impl From<helpers::TransactionRequest> for TransactionRequest { value: r.value.map(Into::into), data: r.data.map(Into::into), nonce: r.nonce.map(Into::into), + min_block: r.min_block, } } } @@ -64,6 +68,7 @@ impl From<helpers::FilledTransactionRequest> for TransactionRequest { value: Some(r.value.into()), data: Some(r.data.into()), nonce: r.nonce.map(Into::into), + min_block: r.min_block, } } } @@ -78,6 +83,7 @@ impl Into<helpers::TransactionRequest> for TransactionRequest { value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), + min_block: self.min_block, } } } @@ -100,7 +106,8 @@ mod tests { "gas":"0x2", "value":"0x3", "data":"0x123456", - "nonce":"0x4" + "nonce":"0x4", + "minBlock":13 }"#; let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); @@ -112,6 +119,7 @@ mod tests { value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), + min_block: Some(13), }); } @@ -134,7 +142,8 @@ mod tests { gas: Some(U256::from_str("76c0").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), - nonce: None + nonce: None, + min_block: None, }); } @@ -151,6 +160,7 @@ mod tests { value: None, data: None, nonce: None, + min_block: None, }); } @@ -174,6 +184,7 @@ mod tests { value: None, data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), nonce: None, + min_block: None, }); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 350a42d0e1b..67d65ad8430 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1901,10 +1901,10 @@ impl ChainSync { return 0; } - let all_transactions_hashes = transactions.iter().map(|tx| tx.hash()).collect::<HashSet<H256>>(); + let all_transactions_hashes = transactions.iter().map(|tx| tx.transaction.hash()).collect::<HashSet<H256>>(); let all_transactions_rlp = { let mut packet = RlpStream::new_list(transactions.len()); - for tx in &transactions { packet.append(tx); } + for tx in &transactions { packet.append(&tx.transaction); } packet.out() }; @@ -1942,11 +1942,11 @@ impl ChainSync { // Construct RLP let mut packet = RlpStream::new_list(to_send.len()); for tx in &transactions { - if to_send.contains(&tx.hash()) { - packet.append(tx); + if to_send.contains(&tx.transaction.hash()) { + packet.append(&tx.transaction); // update stats let id = io.peer_session_info(*peer_id).and_then(|info| info.id); - stats.propagated(tx.hash(), id, block_number); + stats.propagated(tx.transaction.hash(), id, block_number); } } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index b96997d1e4e..78da9258b70 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -55,7 +55,7 @@ fn test_authority_round() { }.sign(s1.secret(), None); // exhange statuses net.sync_steps(5); - net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, tx1).unwrap(); + net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, PendingTransaction::new(tx1, None)).unwrap(); net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); @@ -68,7 +68,7 @@ fn test_authority_round() { value: 0.into(), data: Vec::new(), }.sign(s2.secret(), None); - net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, tx2).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, PendingTransaction::new(tx2, None)).unwrap(); net.peer(1).chain.engine().step(); net.peer(1).chain.miner().update_sealing(&net.peer(1).chain); net.sync(); From 867fdb9eac86f1d782624831e64a5523309258c4 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Thu, 15 Dec 2016 17:23:53 +0000 Subject: [PATCH 343/382] [ci skip] js-precompiled 20161215-172100 --- Cargo.lock | 17 +++++++++++++++-- js/package.json | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86a49298b5d..9a6ed9be6cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,7 +886,7 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1363,11 +1363,22 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#ad6617a73dbb17c53dddc0fc567e70ea5b8e882f" +source = "git+https://github.com/ethcore/js-precompiled.git#2d07c405453bcf1e603c3965387b7f920565b6d8" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.3.6" @@ -1620,6 +1631,7 @@ dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2218,6 +2230,7 @@ dependencies = [ "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>" +"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" diff --git a/js/package.json b/js/package.json index 66a60385b74..d247714ab69 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.125", + "version": "0.2.126", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From fa319ff64cbe432864c1f60b0dd4c3214fa3b0d8 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 18:24:18 +0100 Subject: [PATCH 344/382] Remove duplicate line. --- Cargo.lock | 6 +++--- Cargo.toml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83b7e78dcca..7ad67559f7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1329,7 +1329,6 @@ dependencies = [ ] [[package]] -<<<<<<< HEAD name = "parity-hash-fetch" version = "1.5.0" dependencies = [ @@ -1339,7 +1338,9 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", -======= +] + +[[package]] name = "parity-rpc-client" version = "1.4.0" dependencies = [ @@ -1357,7 +1358,6 @@ dependencies = [ "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)", ->>>>>>> origin/master ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8c621b3873e..632d8d2a7b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,6 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } -clippy = { version = "0.0.103", optional = true} rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } ethcore-light = { path = "ethcore/light" } From 4a2b418fc28c998e670033e0489c3ebba4057bbe Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 18:51:59 +0100 Subject: [PATCH 345/382] Configurable update tracks to help testing. --- Cargo.lock | 17 +++++++++-- ipc-common-types/src/types/release_track.rs | 10 ++++-- parity/cli/config.full.toml | 1 + parity/cli/mod.rs | 4 +++ parity/cli/usage.txt | 13 ++++++-- parity/configuration.rs | 10 +++++- rpc/src/v1/types/consensus_status.rs | 4 +++ updater/src/updater.rs | 34 ++++++++++----------- 8 files changed, 67 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ad67559f7a..847e36134e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,8 +33,8 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.5.0", - "parity-updater 1.5.0", "parity-rpc-client 1.4.0", + "parity-updater 1.5.0", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -890,7 +890,7 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1398,6 +1398,17 @@ dependencies = [ "parity-hash-fetch 1.5.0", ] +[[package]] +name = "parking_lot" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.3.6" @@ -1650,6 +1661,7 @@ dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2265,6 +2277,7 @@ dependencies = [ "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>" +"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" diff --git a/ipc-common-types/src/types/release_track.rs b/ipc-common-types/src/types/release_track.rs index 9b9e7d1e671..3fcc0bb6497 100644 --- a/ipc-common-types/src/types/release_track.rs +++ b/ipc-common-types/src/types/release_track.rs @@ -27,7 +27,9 @@ pub enum ReleaseTrack { Beta, /// Nightly track. Nightly, - /// No known track. + /// Testing track. + Testing, + /// No known track, also "current executable's track" when it's not yet known. Unknown, } @@ -37,7 +39,8 @@ impl fmt::Display for ReleaseTrack { ReleaseTrack::Stable => "stable", ReleaseTrack::Beta => "beta", ReleaseTrack::Nightly => "nightly", - ReleaseTrack::Unknown => "unknown", + ReleaseTrack::Testing => "testing", + ReleaseTrack::Unknown => "unknown", }) } } @@ -48,6 +51,7 @@ impl<'a> From<&'a str> for ReleaseTrack { "stable" => ReleaseTrack::Stable, "beta" => ReleaseTrack::Beta, "nightly" => ReleaseTrack::Nightly, + "testing" => ReleaseTrack::Testing, _ => ReleaseTrack::Unknown, } } @@ -59,6 +63,7 @@ impl From<u8> for ReleaseTrack { 1 => ReleaseTrack::Stable, 2 => ReleaseTrack::Beta, 3 => ReleaseTrack::Nightly, + 4 => ReleaseTrack::Testing, _ => ReleaseTrack::Unknown, } } @@ -70,6 +75,7 @@ impl Into<u8> for ReleaseTrack { ReleaseTrack::Stable => 1, ReleaseTrack::Beta => 2, ReleaseTrack::Nightly => 3, + ReleaseTrack::Testing => 4, ReleaseTrack::Unknown => 0, } } diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 2f420e0a7f9..ed204e0f31e 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -3,6 +3,7 @@ mode = "last" mode_timeout = 300 mode_alarm = 3600 auto_update = "critical" +releases_track = "current" no_download = false no_consensus = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 91f407e1e6e..3ae20be03a8 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -86,6 +86,7 @@ usage! { flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), flag_auto_update: String = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(), + flag_releases_track: String = "current", or |c: &Config| otry!(c.parity).releases_track.clone(), flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), @@ -313,6 +314,7 @@ struct Operating { mode_timeout: Option<u64>, mode_alarm: Option<u64>, auto_update: Option<String>, + releases_track: Option<String>, no_download: Option<bool>, no_consensus: Option<bool>, chain: Option<String>, @@ -540,6 +542,7 @@ mod tests { flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, flag_auto_update: "critical".into(), + flag_releases_track: "current".into(), flag_no_download: false, flag_no_consensus: false, flag_chain: "xyz".into(), @@ -717,6 +720,7 @@ mod tests { mode_timeout: Some(15u64), mode_alarm: Some(10u64), auto_update: None, + releases_track: None, no_download: None, no_consensus: None, chain: Some("./chain.json".into()), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index e05d94751a9..b2f033bfc6f 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -34,12 +34,19 @@ Operating Options: --mode-alarm SECS Specify the number of seconds before auto sleep reawake timeout occurs when mode is passive (default: {flag_mode_alarm}). - --auto-update TRACK Set a release track to automatically update and + --auto-update SET Set a releases set to automatically update and install. - all - All updates in the current release track. + all - All updates in the our release track. critical - Only consensus/security updates. none - No updates will be auto-installed. (default: {flag_auto_update}). + --releases-track TRACK Set which release track we should use for updates. + stable - Stable releases. + beta - Beta releases. + nightly - Nightly releases (unstable). + testing - Testing releases (do not use). + current - Whatever track this executable was + released on (default: {flag_releases_track}). --no-download Normally new releases will be downloaded ready for updating. This disables it. Not recommended. (default: {flag_no_download}). @@ -47,7 +54,7 @@ Operating Options: issues regarding consensus. Not recommended. (default: {flag_no_consensus}). --force-direct Run the originally installed version of Parity, - ignoring any updates that have since been installed. + ignoring any updates that have since been installed. --chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file or olympic, frontier, homestead, mainnet, morden, ropsten, classic, expanse, diff --git a/parity/configuration.rs b/parity/configuration.rs index 4019c4ec3a9..577f8a6c9f1 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -37,7 +37,7 @@ use ethcore_logger::Config as LogConfig; use dir::Directories; use dapps::Configuration as DappsConfiguration; use signer::{Configuration as SignerConfiguration}; -use updater::{UpdatePolicy, UpdateFilter}; +use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat}; use presale::ImportWallet; @@ -693,6 +693,14 @@ impl Configuration { "all" => UpdateFilter::All, _ => return Err("Invalid value for `--auto-update`. See `--help` for more information.".into()), }, + track: match self.args.flag_releases_track.as_ref() { + "stable" => ReleaseTrack::Stable, + "beta" => ReleaseTrack::Beta, + "nightly" => ReleaseTrack::Nightly, + "testing" => ReleaseTrack::Testing, + "current" => ReleaseTrack::Unknown, + _ => return Err("Invalid value for `--releases-track`. See `--help` for more information.".into()), + }, }) } diff --git a/rpc/src/v1/types/consensus_status.rs b/rpc/src/v1/types/consensus_status.rs index fabc3a1d866..f420d426e85 100644 --- a/rpc/src/v1/types/consensus_status.rs +++ b/rpc/src/v1/types/consensus_status.rs @@ -58,6 +58,9 @@ pub enum ReleaseTrack { /// Nightly track. #[serde(rename="nightly")] Nightly, + /// Testing track. + #[serde(rename="testing")] + Testing, /// No known track. #[serde(rename="null")] Unknown, @@ -69,6 +72,7 @@ impl Into<ReleaseTrack> for updater::ReleaseTrack { updater::ReleaseTrack::Stable => ReleaseTrack::Stable, updater::ReleaseTrack::Beta => ReleaseTrack::Beta, updater::ReleaseTrack::Nightly => ReleaseTrack::Nightly, + updater::ReleaseTrack::Testing => ReleaseTrack::Testing, updater::ReleaseTrack::Unknown => ReleaseTrack::Unknown, } } diff --git a/updater/src/updater.rs b/updater/src/updater.rs index aed62873da1..736d7e8a840 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Weak}; use std::{fs, env}; use std::io::Write; use std::path::{PathBuf}; -//use util::misc::platform; +use util::misc::platform; use ipc_common_types::{VersionInfo, ReleaseTrack}; use util::{Address, H160, H256, FixedHash, Mutex, Bytes}; use ethcore::client::{BlockId, BlockChainClient, ChainNotify}; @@ -47,6 +47,8 @@ pub struct UpdatePolicy { pub require_consensus: bool, /// Which of those downloaded should be automatically installed. pub filter: UpdateFilter, + /// Which track we should be following. + pub track: ReleaseTrack, } impl Default for UpdatePolicy { @@ -55,6 +57,7 @@ impl Default for UpdatePolicy { enable_downloading: false, require_consensus: true, filter: UpdateFilter::None, + track: ReleaseTrack::Unknown, } } } @@ -89,14 +92,9 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; -// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! REMOVE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -fn platform() -> String { - "test".to_owned() -} - impl Updater { pub fn new(client: Weak<BlockChainClient>, update_policy: UpdatePolicy) -> Arc<Self> { - let mut u = Updater { + let r = Arc::new(Updater { update_policy: update_policy, weak_self: Mutex::new(Default::default()), client: client.clone(), @@ -105,14 +103,7 @@ impl Updater { exit_handler: Mutex::new(None), this: VersionInfo::this(), state: Mutex::new(Default::default()), - }; - - // TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! REMOVE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - if u.this.track == ReleaseTrack::Unknown { - u.this.track = ReleaseTrack::Nightly; - } - - let r = Arc::new(u); + }); *r.fetcher.lock() = Some(fetch::Client::new(r.clone())); *r.weak_self.lock() = Arc::downgrade(&r); r.poll(); @@ -135,6 +126,13 @@ impl Updater { }) } + fn track(&self) -> ReleaseTrack { + match self.update_policy.track { + ReleaseTrack::Unknown => self.this.track, + x => x, + } + } + fn collect_latest(&self) -> Result<OperationsInfo, String> { if let Some(ref operations) = *self.operations.lock() { let hh: H256 = self.this.hash.into(); @@ -145,15 +143,15 @@ impl Updater { if track > 0 {Some(fork as u64)} else {None} }); - if self.this.track == ReleaseTrack::Unknown { + if self.track() == ReleaseTrack::Unknown { return Err(format!("Current executable ({}) is unreleased.", H160::from(self.this.hash))); } - let latest_in_track = operations.latest_in_track(CLIENT_ID, self.this.track.into())?; + let latest_in_track = operations.latest_in_track(CLIENT_ID, self.track().into())?; let in_track = Self::collect_release_info(operations, &latest_in_track)?; let mut in_minor = Some(in_track.clone()); const PROOF: &'static str = "in_minor initialised and assigned with Some; loop breaks if None assigned; qed"; - while in_minor.as_ref().expect(PROOF).version.track != self.this.track { + while in_minor.as_ref().expect(PROOF).version.track != self.track() { let track = match in_minor.as_ref().expect(PROOF).version.track { ReleaseTrack::Beta => ReleaseTrack::Stable, ReleaseTrack::Nightly => ReleaseTrack::Beta, From d737de0fc0ee89d6c574bbfd21709cd596a7412e Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 19:17:44 +0100 Subject: [PATCH 346/382] Fix tests. --- parity/configuration.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/parity/configuration.rs b/parity/configuration.rs index 577f8a6c9f1..cb8b58f2ae9 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -817,7 +817,7 @@ mod tests { use params::SpecType; use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts}; use devtools::{RandomTempPath}; - use updater::{UpdatePolicy, UpdateFilter}; + use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use std::io::Write; use std::fs::{File, create_dir}; @@ -1011,7 +1011,7 @@ mod tests { acc_conf: Default::default(), gas_pricer: Default::default(), miner_extras: Default::default(), - update_policy: UpdatePolicy { enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical }, + update_policy: UpdatePolicy { enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Unknown }, mode: Default::default(), tracing: Default::default(), compaction: Default::default(), @@ -1058,15 +1058,15 @@ mod tests { #[test] fn should_parse_updater_options() { // when - let conf0 = parse(&["parity"]); + let conf0 = parse(&["parity", "--release-track=testing"]); let conf1 = parse(&["parity", "--auto-update", "all", "--no-consensus"]); let conf2 = parse(&["parity", "--no-download", "--auto-update=all"]); - let conf3 = parse(&["parity", "--auto-update=xxx"]); + let conf3 = parse(&["parity", "--auto-update=xxx", "--release-track=beta"]); // then - assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical}); - assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: false, filter: UpdateFilter::All}); - assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, require_consensus: true, filter: UpdateFilter::All}); + assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Testing}); + assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: false, filter: UpdateFilter::All, track: ReleaseTrack::Unknown}); + assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, require_consensus: true, filter: UpdateFilter::All, track: ReleaseTrack::Beta}); assert!(conf3.update_policy().is_err()); } From 995fafebee58060355d70e5a3405b7e9baa92b65 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 19:19:50 +0100 Subject: [PATCH 347/382] Fix last remaining test. --- parity/configuration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parity/configuration.rs b/parity/configuration.rs index cb8b58f2ae9..57be84dca21 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -1060,8 +1060,8 @@ mod tests { // when let conf0 = parse(&["parity", "--release-track=testing"]); let conf1 = parse(&["parity", "--auto-update", "all", "--no-consensus"]); - let conf2 = parse(&["parity", "--no-download", "--auto-update=all"]); - let conf3 = parse(&["parity", "--auto-update=xxx", "--release-track=beta"]); + let conf2 = parse(&["parity", "--no-download", "--auto-update=all", "--release-track=beta"]); + let conf3 = parse(&["parity", "--auto-update=xxx"]); // then assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Testing}); From 801596395e7fc88643d8872453635e436afb77fa Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 19:23:25 +0100 Subject: [PATCH 348/382] --release-track. --- parity/cli/config.full.toml | 2 +- parity/cli/mod.rs | 8 ++++---- parity/cli/usage.txt | 4 ++-- parity/configuration.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index ed204e0f31e..60571b46188 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -3,7 +3,7 @@ mode = "last" mode_timeout = 300 mode_alarm = 3600 auto_update = "critical" -releases_track = "current" +release_track = "current" no_download = false no_consensus = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 3ae20be03a8..1b86faadc1a 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -86,7 +86,7 @@ usage! { flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), flag_auto_update: String = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(), - flag_releases_track: String = "current", or |c: &Config| otry!(c.parity).releases_track.clone(), + flag_release_track: String = "current", or |c: &Config| otry!(c.parity).release_track.clone(), flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), @@ -314,7 +314,7 @@ struct Operating { mode_timeout: Option<u64>, mode_alarm: Option<u64>, auto_update: Option<String>, - releases_track: Option<String>, + release_track: Option<String>, no_download: Option<bool>, no_consensus: Option<bool>, chain: Option<String>, @@ -542,7 +542,7 @@ mod tests { flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, flag_auto_update: "critical".into(), - flag_releases_track: "current".into(), + flag_release_track: "current".into(), flag_no_download: false, flag_no_consensus: false, flag_chain: "xyz".into(), @@ -720,7 +720,7 @@ mod tests { mode_timeout: Some(15u64), mode_alarm: Some(10u64), auto_update: None, - releases_track: None, + release_track: None, no_download: None, no_consensus: None, chain: Some("./chain.json".into()), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index b2f033bfc6f..baf898ad85a 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -40,13 +40,13 @@ Operating Options: critical - Only consensus/security updates. none - No updates will be auto-installed. (default: {flag_auto_update}). - --releases-track TRACK Set which release track we should use for updates. + --release-track TRACK Set which release track we should use for updates. stable - Stable releases. beta - Beta releases. nightly - Nightly releases (unstable). testing - Testing releases (do not use). current - Whatever track this executable was - released on (default: {flag_releases_track}). + released on (default: {flag_release_track}). --no-download Normally new releases will be downloaded ready for updating. This disables it. Not recommended. (default: {flag_no_download}). diff --git a/parity/configuration.rs b/parity/configuration.rs index 57be84dca21..9f9cf654e5d 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -693,7 +693,7 @@ impl Configuration { "all" => UpdateFilter::All, _ => return Err("Invalid value for `--auto-update`. See `--help` for more information.".into()), }, - track: match self.args.flag_releases_track.as_ref() { + track: match self.args.flag_release_track.as_ref() { "stable" => ReleaseTrack::Stable, "beta" => ReleaseTrack::Beta, "nightly" => ReleaseTrack::Nightly, From b88d50dc9b44ab705599b21dc27b41e27337700d Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 19:27:06 +0100 Subject: [PATCH 349/382] fix naming collision --- ethcore/src/engines/authority_round.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 8d1c004c512..cf9325dd448 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -118,13 +118,9 @@ impl AuthorityRound { Ok(engine) } - fn step(&self) -> usize { - self.step.load(AtomicOrdering::SeqCst) - } - fn remaining_step_duration(&self) -> Duration { let now = unix_now(); - let step_end = self.our_params.step_duration * (self.step() as u32 + 1); + let step_end = self.our_params.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); if step_end > now { step_end - now } else { @@ -228,7 +224,7 @@ impl Engine for AuthorityRound { fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); - let step = self.step(); + let step = self.step.load(AtomicOrdering::SeqCst); if self.is_step_proposer(step, header.author()) { if let Some(ref ap) = *self.account_provider.lock() { // Account should be permanently unlocked, otherwise sealing will fail. @@ -265,7 +261,7 @@ impl Engine for AuthorityRound { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let header_step = try!(header_step(header)); // Give one step slack if step is lagging, double vote is still not possible. - if header_step <= self.step() + 1 { + if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 { let proposer_signature = try!(header_signature(header)); let ok_sig = try!(verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())); if ok_sig { From 3ac0794d2862288dcce12a5f56d689aa186e81b7 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 19:31:26 +0100 Subject: [PATCH 350/382] Bump jsonrpc-core for rpc_cli --- Cargo.lock | 27 +-------------------------- rpc_client/Cargo.toml | 2 +- rpc_client/src/client.rs | 8 ++++---- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0caea9b8ebf..6285f159b16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -880,18 +880,6 @@ name = "itoa" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "jsonrpc-core" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "jsonrpc-core" version = "4.0.0" @@ -1332,7 +1320,7 @@ dependencies = [ "ethcore-signer 1.5.0", "ethcore-util 1.5.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1368,17 +1356,6 @@ dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parking_lot" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parking_lot" version = "0.3.6" @@ -2182,7 +2159,6 @@ dependencies = [ "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" -"checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414" "checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" @@ -2230,7 +2206,6 @@ dependencies = [ "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>" -"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" diff --git a/rpc_client/Cargo.toml b/rpc_client/Cargo.toml index 9b93a1a5bac..e5c8e9b126f 100644 --- a/rpc_client/Cargo.toml +++ b/rpc_client/Cargo.toml @@ -8,7 +8,6 @@ version = "1.4.0" [dependencies] futures = "0.1" -jsonrpc-core = "3.0.2" lazy_static = "0.2.1" log = "0.3.6" matches = "0.1.2" @@ -17,6 +16,7 @@ serde = "0.8" serde_json = "0.8" tempdir = "0.3.5" url = "1.2.0" +jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } ws = { git = "https://github.com/ethcore/ws-rs.git", branch = "mio-upstream-stable" } ethcore-rpc = { path = "../rpc" } ethcore-signer = { path = "../signer" } diff --git a/rpc_client/src/client.rs b/rpc_client/src/client.rs index 9ee9f5c9d4d..c32b5be7a2e 100644 --- a/rpc_client/src/client.rs +++ b/rpc_client/src/client.rs @@ -36,7 +36,7 @@ use futures::{BoxFuture, Canceled, Complete, Future, oneshot, done}; use jsonrpc_core::{Id, Version, Params, Error as JsonRpcError}; use jsonrpc_core::request::MethodCall; -use jsonrpc_core::response::{SyncOutput, Success, Failure}; +use jsonrpc_core::response::{Output, Success, Failure}; /// The actual websocket connection handler, passed into the /// event loop of ws-rs @@ -107,13 +107,13 @@ impl Handler for RpcHandler { let ret: Result<JsonValue, JsonRpcError>; let response_id; let string = &msg.to_string(); - match json::from_str::<SyncOutput>(&string) { - Ok(SyncOutput::Success(Success { result, id: Id::Num(id), .. })) => + match json::from_str::<Output>(&string) { + Ok(Output::Success(Success { result, id: Id::Num(id), .. })) => { ret = Ok(result); response_id = id as usize; } - Ok(SyncOutput::Failure(Failure { error, id: Id::Num(id), .. })) => { + Ok(Output::Failure(Failure { error, id: Id::Num(id), .. })) => { ret = Err(error); response_id = id as usize; } From 4410fb635a6d71939e958fd762e40f55292f69ad Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 19:53:13 +0100 Subject: [PATCH 351/382] Respect system paths. --- parity/configuration.rs | 13 +++++++------ parity/dir.rs | 5 +++++ parity/main.rs | 5 ++--- updater/src/updater.rs | 14 ++++++++------ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/parity/configuration.rs b/parity/configuration.rs index 9f9cf654e5d..c46dc5e6b11 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -34,7 +34,7 @@ use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_pri geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; use ethcore_logger::Config as LogConfig; -use dir::Directories; +use dir::{Directories, default_hypervisor_path}; use dapps::Configuration as DappsConfiguration; use signer::{Configuration as SignerConfiguration}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; @@ -701,6 +701,7 @@ impl Configuration { "current" => ReleaseTrack::Unknown, _ => return Err("Invalid value for `--releases-track`. See `--help` for more information.".into()), }, + path: default_hypervisor_path(), }) } @@ -810,7 +811,7 @@ mod tests { use ethcore::miner::{MinerOptions, PrioritizationStrategy}; use helpers::{default_network_config}; use run::RunCmd; - use dir::Directories; + use dir::{Directories, default_hypervisor_path}; use signer::{Configuration as SignerConfiguration}; use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState}; use presale::ImportWallet; @@ -1011,7 +1012,7 @@ mod tests { acc_conf: Default::default(), gas_pricer: Default::default(), miner_extras: Default::default(), - update_policy: UpdatePolicy { enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Unknown }, + update_policy: UpdatePolicy { enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Unknown, path: default_hypervisor_path() }, mode: Default::default(), tracing: Default::default(), compaction: Default::default(), @@ -1064,9 +1065,9 @@ mod tests { let conf3 = parse(&["parity", "--auto-update=xxx"]); // then - assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Testing}); - assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: false, filter: UpdateFilter::All, track: ReleaseTrack::Unknown}); - assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, require_consensus: true, filter: UpdateFilter::All, track: ReleaseTrack::Beta}); + assert_eq!(conf0.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: true, filter: UpdateFilter::Critical, track: ReleaseTrack::Testing, path: default_hypervisor_path()}); + assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy{enable_downloading: true, require_consensus: false, filter: UpdateFilter::All, track: ReleaseTrack::Unknown, path: default_hypervisor_path()}); + assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy{enable_downloading: false, require_consensus: true, filter: UpdateFilter::All, track: ReleaseTrack::Beta, path: default_hypervisor_path()}); assert!(conf3.update_policy().is_err()); } diff --git a/parity/dir.rs b/parity/dir.rs index f9c2f30c96f..05515fc8238 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -195,6 +195,11 @@ pub fn default_data_path() -> String { get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) } +pub fn default_hypervisor_path() -> String { + let app_info = AppInfo { name: "parity-hypervisor", author: "parity" }; + get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity-hypervisor".to_owned()) +} + #[cfg(test)] mod tests { use super::Directories; diff --git a/parity/main.rs b/parity/main.rs index cd6dc8a170d..1fb290f4b30 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -126,6 +126,7 @@ use cli::Args; use configuration::{Cmd, Execute, Configuration}; use deprecated::find_deprecated; use ethcore_logger::setup_log; +use dir::default_hypervisor_path; fn print_hash_of(maybe_file: Option<String>) -> Result<String, String> { if let Some(file) = maybe_file { @@ -193,10 +194,8 @@ fn sync_main(alt_mains: &mut HashMap<String, fn()>) { alt_mains.insert("sync".to_owned(), sync::main); } -// TODO: merge with version in Updater. fn updates_path(name: &str) -> PathBuf { - let mut dest = PathBuf::from(env::home_dir().unwrap().to_str().expect("env filesystem paths really should be valid; qed")); - dest.push(".parity-updates"); + let mut dest = PathBuf::from(default_hypervisor_path()); dest.push(name); dest } diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 736d7e8a840..8f4977fc2f5 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. use std::sync::{Arc, Weak}; -use std::{fs, env}; +use std::fs; use std::io::Write; use std::path::{PathBuf}; use util::misc::platform; @@ -49,6 +49,8 @@ pub struct UpdatePolicy { pub filter: UpdateFilter, /// Which track we should be following. pub track: ReleaseTrack, + /// Path for the updates to go. + pub path: String, } impl Default for UpdatePolicy { @@ -58,6 +60,7 @@ impl Default for UpdatePolicy { require_consensus: true, filter: UpdateFilter::None, track: ReleaseTrack::Unknown, + path: Default::default(), } } } @@ -175,9 +178,8 @@ impl Updater { format!("parity-{}.{}.{}-{:?}", v.version.major, v.version.minor, v.version.patch, v.hash) } - fn updates_path(name: &str) -> PathBuf { - let mut dest = PathBuf::from(env::home_dir().unwrap().to_str().expect("env filesystem paths really should be valid; qed")); - dest.push(".parity-updates"); + fn updates_path(&self, name: &str) -> PathBuf { + let mut dest = PathBuf::from(self.update_policy.path.clone()); dest.push(name); dest } @@ -189,7 +191,7 @@ impl Updater { let fetched = s.fetching.take().unwrap(); let b = result.map_err(|e| format!("Unable to fetch update ({}): {:?}", fetched.version, e))?; info!(target: "updater", "Fetched latest version ({}) OK to {}", fetched.version, b.display()); - let dest = Self::updates_path(&Self::update_file_name(&fetched.version)); + let dest = self.updates_path(&Self::update_file_name(&fetched.version)); fs::create_dir_all(dest.parent().expect("at least one thing pushed; qed")).map_err(|e| format!("Unable to create updates path: {:?}", e))?; fs::copy(&b, &dest).map_err(|e| format!("Unable to copy update: {:?}", e))?; info!(target: "updater", "Copied file to {}", dest.display()); @@ -322,7 +324,7 @@ impl Service for Updater { let mut s = self.state.lock(); if let Some(r) = s.ready.take() { let p = Self::update_file_name(&r.version); - let n = Self::updates_path("latest"); + let n = self.updates_path("latest"); // TODO: creating then writing is a bit fragile. would be nice to make it atomic. match fs::File::create(&n).and_then(|mut f| f.write_all(p.as_bytes())) { Ok(_) => { From b7b531ca75111ee0b02afd9ab3308bb599b9041f Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 19:54:28 +0100 Subject: [PATCH 352/382] more slack in test --- ethcore/src/engines/authority_round.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index cf9325dd448..4e7d564fe4e 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -462,7 +462,7 @@ mod tests { // Spec starts with step 2. header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_ok()); - header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + header.set_seal(vec![encode(&11usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_err()); } } From 27580586e00c97de272fe0237629e2a778a5f40b Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 20:09:05 +0100 Subject: [PATCH 353/382] Minor tweaks --- ethcore/src/miner/transaction_queue.rs | 2 +- ethcore/src/types/transaction.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index a13fc01c4f3..1c8a3d17d25 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -984,7 +984,7 @@ impl TransactionQueue { let mut delayed = HashSet::new(); for t in self.current.by_priority.iter() { let tx = self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`"); - let sender = tx.transaction.sender().expect("Queue only contains transactions with valid sender"); + let sender = tx.sender(); if delayed.contains(&sender) { continue; } diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 681e38fcce9..25e91afe548 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -390,7 +390,7 @@ impl Deref for LocalizedTransaction { } } -/// Queued information with additional information. +/// Queued transaction with additional information. #[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct PendingTransaction { /// Signed transaction data. From d2962fe47b52777a42f4d48967b3cfaa9e451b20 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Thu, 15 Dec 2016 20:45:49 +0100 Subject: [PATCH 354/382] store certifier index in constant --- js/src/modals/Verification/email-store.js | 4 +++- js/src/modals/Verification/sms-store.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index 7855732c7e5..a86f9e4b1b1 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -23,6 +23,8 @@ import VerificationStore, { } from './store'; import { postToServer } from '../../3rdparty/email-verification'; +const EMAIL_VERIFICATION = 4; // id in the `BadgeReg.sol` contract + export default class EmailVerificationStore extends VerificationStore { @observable email = ''; @@ -54,7 +56,7 @@ export default class EmailVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, EmailVerificationABI, 4, account, isTestnet); + super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet); } requestValues = () => [ sha3(this.email) ] diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js index 84a44545211..3c58b6ef9a0 100644 --- a/js/src/modals/Verification/sms-store.js +++ b/js/src/modals/Verification/sms-store.js @@ -23,6 +23,8 @@ import VerificationStore, { } from './store'; import { postToServer } from '../../3rdparty/sms-verification'; +const SMS_VERIFICATION = 0; // id in the `BadgeReg.sol` contract + export default class SMSVerificationStore extends VerificationStore { @observable number = ''; @@ -53,7 +55,7 @@ export default class SMSVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, SMSVerificationABI, 0, account, isTestnet); + super(api, SMSVerificationABI, SMS_VERIFICATION, account, isTestnet); } @action setNumber = (number) => { From 1a790b784fd32d5ae01441f75fc8798a577a17d2 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 20:49:09 +0100 Subject: [PATCH 355/382] Fix centos's reported build platform. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 757f5dac1b9..c1c10f0cf85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -95,7 +95,7 @@ linux-centos: script: - export CXX="g++" - export CC="gcc" - - export PLATFORM=x86_64-unknown-linux-gnu + - export PLATFORM=x86_64-unknown-centos-gnu - cargo build -j $(nproc) --release $CARGOFLAGS - strip target/release/parity - md5sum target/release/parity > parity.md5 From e1dd986c413e9a20177d2fcbe42580886858fc2f Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 21:16:32 +0100 Subject: [PATCH 356/382] Hex-encoded block numbers --- rpc/src/v1/impls/signer.rs | 4 ++-- rpc/src/v1/types/block_number.rs | 25 +++++++++++++++++++++++-- rpc/src/v1/types/confirmations.rs | 10 +++++----- rpc/src/v1/types/transaction.rs | 6 +++--- rpc/src/v1/types/transaction_request.rs | 16 ++++++++-------- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 6dc813b73a6..375cfdf6e7c 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -82,8 +82,8 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient, if let Some(gas) = modification.gas { request.gas = gas.into(); } - if let Some(min_block) = modification.min_block { - request.min_block = min_block; + if let Some(ref min_block) = modification.min_block { + request.min_block = min_block.as_ref().and_then(|b| b.to_min_block_num()); } } let result = f(&*client, &*miner, &*accounts, payload); diff --git a/rpc/src/v1/types/block_number.rs b/rpc/src/v1/types/block_number.rs index 00ee1f22bb9..65ebd76be27 100644 --- a/rpc/src/v1/types/block_number.rs +++ b/rpc/src/v1/types/block_number.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -use serde::{Deserialize, Deserializer, Error}; +use serde::{Deserialize, Deserializer, Error, Serialize, Serializer}; use serde::de::Visitor; use ethcore::client::BlockId; /// Represents rpc api block number param. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] pub enum BlockNumber { /// Number Num(u64), @@ -44,6 +44,27 @@ impl Deserialize for BlockNumber { } } +impl BlockNumber { + /// Convert block number to min block target. + pub fn to_min_block_num(&self) -> Option<u64> { + match *self { + BlockNumber::Num(ref x) => Some(*x), + _ => None, + } + } +} + +impl Serialize for BlockNumber { + fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + match *self { + BlockNumber::Num(ref x) => serializer.serialize_str(&format!("0x{:x}", x)), + BlockNumber::Latest => serializer.serialize_str("latest"), + BlockNumber::Earliest => serializer.serialize_str("earliest"), + BlockNumber::Pending => serializer.serialize_str("pending"), + } + } +} + struct BlockNumberVisitor; impl Visitor for BlockNumberVisitor { diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 0df3e4b528a..5f79f675d27 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -20,7 +20,7 @@ use std::fmt; use serde::{Serialize, Serializer}; use util::log::Colour; -use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes}; +use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, BlockNumber}; use v1::helpers; /// Confirmation waiting in a queue @@ -195,7 +195,7 @@ pub struct TransactionModification { pub gas: Option<U256>, /// Modified min block #[serde(rename="minBlock")] - pub min_block: Option<Option<u64>>, + pub min_block: Option<Option<BlockNumber>>, } /// Represents two possible return values. @@ -237,7 +237,7 @@ impl<A, B> Serialize for Either<A, B> where mod tests { use std::str::FromStr; use serde_json; - use v1::types::{U256, H256}; + use v1::types::{U256, H256, BlockNumber}; use v1::helpers; use super::*; @@ -330,7 +330,7 @@ mod tests { // given let s1 = r#"{ "gasPrice":"0xba43b7400", - "minBlock":42 + "minBlock":"0x42" }"#; let s2 = r#"{"gas": "0x1233"}"#; let s3 = r#"{}"#; @@ -344,7 +344,7 @@ mod tests { assert_eq!(res1, TransactionModification { gas_price: Some(U256::from_str("0ba43b7400").unwrap()), gas: None, - min_block: Some(Some(42)), + min_block: Some(Some(BlockNumber::Num(0x42))), }); assert_eq!(res2, TransactionModification { gas_price: None, diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 04275323d68..0db61488715 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -19,7 +19,7 @@ use ethcore::miner; use ethcore::contract_address; use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use v1::helpers::errors; -use v1::types::{Bytes, H160, H256, U256, H512}; +use v1::types::{Bytes, H160, H256, U256, H512, BlockNumber}; /// Transaction #[derive(Debug, Default, Clone, PartialEq, Serialize)] @@ -71,7 +71,7 @@ pub struct Transaction { pub s: U256, /// Transaction activates at specified block. #[serde(rename="minBlock")] - pub min_block: Option<u64>, + pub min_block: Option<BlockNumber>, } /// Local Transaction Status @@ -232,7 +232,7 @@ impl From<SignedTransaction> for Transaction { impl From<PendingTransaction> for Transaction { fn from(t: PendingTransaction) -> Transaction { let mut r = Transaction::from(t.transaction); - r.min_block = t.min_block; + r.min_block = t.min_block.map(|b| BlockNumber::Num(b)); r } } diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 07ab88588d9..49cf37ce0c2 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -16,7 +16,7 @@ //! `TransactionRequest` type -use v1::types::{Bytes, H160, U256}; +use v1::types::{Bytes, H160, U256, BlockNumber}; use v1::helpers; use util::log::Colour; @@ -43,7 +43,7 @@ pub struct TransactionRequest { pub nonce: Option<U256>, /// Delay until this block if specified. #[serde(rename="minBlock")] - pub min_block: Option<u64>, + pub min_block: Option<BlockNumber>, } pub fn format_ether(i: U256) -> String { @@ -93,7 +93,7 @@ impl From<helpers::TransactionRequest> for TransactionRequest { value: r.value.map(Into::into), data: r.data.map(Into::into), nonce: r.nonce.map(Into::into), - min_block: r.min_block, + min_block: r.min_block.map(|b| BlockNumber::Num(b)), } } } @@ -108,7 +108,7 @@ impl From<helpers::FilledTransactionRequest> for TransactionRequest { value: Some(r.value.into()), data: Some(r.data.into()), nonce: r.nonce.map(Into::into), - min_block: r.min_block, + min_block: r.min_block.map(|b| BlockNumber::Num(b)), } } } @@ -123,7 +123,7 @@ impl Into<helpers::TransactionRequest> for TransactionRequest { value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), - min_block: self.min_block, + min_block: self.min_block.and_then(|b| b.to_min_block_num()), } } } @@ -134,7 +134,7 @@ mod tests { use std::str::FromStr; use rustc_serialize::hex::FromHex; use serde_json; - use v1::types::{U256, H160}; + use v1::types::{U256, H160, BlockNumber}; use super::*; #[test] @@ -147,7 +147,7 @@ mod tests { "value":"0x3", "data":"0x123456", "nonce":"0x4", - "minBlock":13 + "minBlock":"0x13" }"#; let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); @@ -159,7 +159,7 @@ mod tests { value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), - min_block: Some(13), + min_block: Some(BlockNumber::Num(0x13)), }); } From 3e83aeeb1765ffe02ed8682002cabec58cfaed48 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Thu, 15 Dec 2016 20:21:20 +0000 Subject: [PATCH 357/382] [ci skip] js-precompiled 20161215-201838 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a6ed9be6cb..e6a27f20670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,7 +1363,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#2d07c405453bcf1e603c3965387b7f920565b6d8" +source = "git+https://github.com/ethcore/js-precompiled.git#2cdda91549dfeebd94775b348a443f8ee5446e9f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index d247714ab69..3b0be967195 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.126", + "version": "0.2.127", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 060cc799a627392578efd164e831786e50b94cf5 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 21:44:31 +0100 Subject: [PATCH 358/382] Minor tweaks --- ethcore/src/miner/transaction_queue.rs | 6 +++--- rpc_client/src/signer_client.rs | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 1c8a3d17d25..9f5b6399a7f 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -978,7 +978,7 @@ impl TransactionQueue { } - fn collect_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F) + fn filter_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F) where F: FnMut(&VerifiedTransaction) { let mut delayed = HashSet::new(); @@ -999,14 +999,14 @@ impl TransactionQueue { /// Returns top transactions from the queue ordered by priority. pub fn top_transactions_at(&self, best_block: BlockNumber) -> Vec<SignedTransaction> { let mut r = Vec::new(); - self.collect_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); + self.filter_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); r } /// Return all ready transactions. pub fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { let mut r = Vec::new(); - self.collect_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); + self.filter_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); r } diff --git a/rpc_client/src/signer_client.rs b/rpc_client/src/signer_client.rs index 996fb078729..6284111ca74 100644 --- a/rpc_client/src/signer_client.rs +++ b/rpc_client/src/signer_client.rs @@ -1,7 +1,5 @@ use client::{Rpc, RpcError}; -use rpc::v1::types::{ConfirmationRequest, - TransactionModification, - U256}; +use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, BlockNumber}; use serde_json::{Value as JsonValue, to_value}; use std::path::PathBuf; use futures::{BoxFuture, Canceled}; @@ -24,7 +22,7 @@ impl SignerRpc { id: U256, new_gas: Option<U256>, new_gas_price: Option<U256>, - new_min_block: Option<Option<u64>>, + new_min_block: Option<Option<BlockNumber>>, pwd: &str ) -> BoxFuture<Result<U256, RpcError>, Canceled> { From e2371632c52b85ecc62b7812c06fd1d1085f42c9 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Thu, 15 Dec 2016 21:56:45 +0100 Subject: [PATCH 359/382] Base dir option --- ethcore/res/ethereum/tests | 2 +- parity/blockchain.rs | 4 ++-- parity/cli/config.full.toml | 3 ++- parity/cli/mod.rs | 20 ++++++++++++-------- parity/cli/usage.txt | 4 +++- parity/configuration.rs | 10 ++++++---- parity/dapps.rs | 2 +- parity/dir.rs | 35 ++++++++++++++++++++--------------- parity/helpers.rs | 7 ++++--- parity/rpc.rs | 2 +- parity/run.rs | 2 +- parity/signer.rs | 2 +- parity/snapshot.rs | 2 +- parity/upgrade.rs | 6 +++--- 14 files changed, 58 insertions(+), 43 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index e8f4624b7f1..9028c4801fd 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 94e9e770909..72c56ad4cdf 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -180,7 +180,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<String, String> { let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); + try!(execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); // create dirs used by parity try!(cmd.dirs.create_dirs(false, false)); @@ -351,7 +351,7 @@ fn start_client( let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&db_dirs, algorithm, compaction.compaction_profile(db_dirs.db_root_path().as_path()))); + try!(execute_upgrades(&dirs.base, &db_dirs, algorithm, compaction.compaction_profile(db_dirs.db_root_path().as_path()))); // create dirs used by parity try!(dirs.create_dirs(false, false)); diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 48dac61d3d2..4d222a80e4b 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -3,7 +3,8 @@ mode = "last" mode_timeout = 300 mode_alarm = 3600 chain = "homestead" -db_path = "$HOME/.parity" +base_path = "$HOME/.parity" +db_path = "$HOME/.parity/chains" keys_path = "$HOME/.parity/keys" identity = "" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 59f478bb34e..93851746970 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -86,8 +86,9 @@ usage! { flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), - flag_db_path: String = default_data_path(), or |c: &Config| otry!(c.parity).db_path.clone(), - flag_keys_path: String = "$DATA/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), + flag_base_path: String = default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone(), + flag_db_path: String = "$BASE/chains", or |c: &Config| otry!(c.parity).db_path.clone(), + flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(), // -- Account Options @@ -106,7 +107,7 @@ usage! { or |c: &Config| otry!(c.ui).port.clone(), flag_ui_interface: String = "local", or |c: &Config| otry!(c.ui).interface.clone(), - flag_ui_path: String = "$DATA/signer", + flag_ui_path: String = "$BASE/signer", or |c: &Config| otry!(c.ui).path.clone(), // NOTE [todr] For security reasons don't put this to config files flag_ui_no_validation: bool = false, or |_| None, @@ -162,7 +163,7 @@ usage! { // IPC flag_no_ipc: bool = false, or |c: &Config| otry!(c.ipc).disable.clone(), - flag_ipc_path: String = "$DATA/jsonrpc.ipc", + flag_ipc_path: String = "$BASE/jsonrpc.ipc", or |c: &Config| otry!(c.ipc).path.clone(), flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc", or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), @@ -176,7 +177,7 @@ usage! { or |c: &Config| otry!(c.dapps).interface.clone(), flag_dapps_hosts: String = "none", or |c: &Config| otry!(c.dapps).hosts.clone().map(|vec| vec.join(",")), - flag_dapps_path: String = "$DATA/dapps", + flag_dapps_path: String = "$BASE/dapps", or |c: &Config| otry!(c.dapps).path.clone(), flag_dapps_user: Option<String> = None, or |c: &Config| otry!(c.dapps).user.clone().map(Some), @@ -277,7 +278,7 @@ usage! { or |c: &Config| otry!(c.vm).jit.clone(), // -- Miscellaneous Options - flag_config: String = "$DATA/config.toml", or |_| None, + flag_config: String = "$BASE/config.toml", or |_| None, flag_logging: Option<String> = None, or |c: &Config| otry!(c.misc).logging.clone().map(Some), flag_log_file: Option<String> = None, @@ -310,6 +311,7 @@ struct Operating { mode_timeout: Option<u64>, mode_alarm: Option<u64>, chain: Option<String>, + base_path: Option<String>, db_path: Option<String>, keys_path: Option<String>, identity: Option<String>, @@ -534,7 +536,8 @@ mod tests { flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, flag_chain: "xyz".into(), - flag_db_path: "$HOME/.parity".into(), + flag_base_path: "$HOME/.parity".into(), + flag_db_path: "$HOME/.parity/chains".into(), flag_keys_path: "$HOME/.parity/keys".into(), flag_identity: "".into(), @@ -676,7 +679,7 @@ mod tests { // -- Miscellaneous Options flag_version: false, - flag_config: "$DATA/config.toml".into(), + flag_config: "$BASE/config.toml".into(), flag_logging: Some("own_tx=trace".into()), flag_log_file: Some("/var/log/parity.log".into()), flag_no_color: false, @@ -708,6 +711,7 @@ mod tests { mode_timeout: Some(15u64), mode_alarm: Some(10u64), chain: Some("./chain.json".into()), + base_path: None, db_path: None, keys_path: None, identity: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index bbf7ac236a1..ecb06799236 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -38,7 +38,9 @@ Operating Options: JSON chain specification file or olympic, frontier, homestead, mainnet, morden, ropsten, classic, expanse, testnet or dev (default: {flag_chain}). - -d --db-path PATH Specify the database & configuration directory path + -d --base-path PATH Specify the base data storage path. + (default: {flag_base_path}). + --db-path PATH Specify the database directory path (default: {flag_db_path}). --keys-path PATH Specify the path for JSON key files to be found (default: {flag_keys_path}). diff --git a/parity/configuration.rs b/parity/configuration.rs index b9ae2e95821..10ba2a0c6b1 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -589,7 +589,7 @@ impl Configuration { ret.snapshot_peers = self.snapshot_peers(); ret.allow_ips = try!(self.allow_ips()); ret.max_pending_peers = self.max_pending_peers(); - let mut net_path = PathBuf::from(self.directories().data); + let mut net_path = PathBuf::from(self.directories().base); net_path.push("network"); ret.config_path = Some(net_path.to_str().unwrap().to_owned()); ret.reserved_nodes = try!(self.init_reserved_nodes()); @@ -683,8 +683,9 @@ impl Configuration { fn directories(&self) -> Directories { use util::path; - let data_path = replace_home("", self.args.flag_datadir.as_ref().unwrap_or(&self.args.flag_db_path)); + let data_path = replace_home("", self.args.flag_datadir.as_ref().unwrap_or(&self.args.flag_base_path)); + let db_path = replace_home(&data_path, &self.args.flag_db_path); let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); @@ -706,7 +707,8 @@ impl Configuration { Directories { keys: keys_path, - data: data_path, + base: data_path, + db: db_path, dapps: dapps_path, signer: ui_path, } @@ -716,7 +718,7 @@ impl Configuration { if self.args.flag_geth { geth_ipc_path(self.args.flag_testnet) } else { - parity_ipc_path(&self.directories().data, &self.args.flag_ipcpath.clone().unwrap_or(self.args.flag_ipc_path.clone())) + parity_ipc_path(&self.directories().base, &self.args.flag_ipcpath.clone().unwrap_or(self.args.flag_ipc_path.clone())) } } diff --git a/parity/dapps.rs b/parity/dapps.rs index e54638ed228..80fe8d67535 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -43,7 +43,7 @@ impl Default for Configuration { hosts: Some(Vec::new()), user: None, pass: None, - dapps_path: replace_home(&data_dir, "$DATA/dapps"), + dapps_path: replace_home(&data_dir, "$BASE/dapps"), } } } diff --git a/parity/dir.rs b/parity/dir.rs index f9c2f30c96f..89c435d0d3b 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -27,7 +27,8 @@ const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; #[derive(Debug, PartialEq)] pub struct Directories { - pub data: String, + pub base: String, + pub db: String, pub keys: String, pub signer: String, pub dapps: String, @@ -37,17 +38,19 @@ impl Default for Directories { fn default() -> Self { let data_dir = default_data_path(); Directories { - data: replace_home(&data_dir, "$DATA"), - keys: replace_home(&data_dir, "$DATA/keys"), - signer: replace_home(&data_dir, "$DATA/signer"), - dapps: replace_home(&data_dir, "$DATA/dapps"), + base: replace_home(&data_dir, "$BASE"), + db: replace_home(&data_dir, "$BASE/chains"), + keys: replace_home(&data_dir, "$BASE/keys"), + signer: replace_home(&data_dir, "$BASE/signer"), + dapps: replace_home(&data_dir, "$BASE/dapps"), } } } impl Directories { pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool) -> Result<(), String> { - try!(fs::create_dir_all(&self.data).map_err(|e| e.to_string())); + try!(fs::create_dir_all(&self.base).map_err(|e| e.to_string())); + try!(fs::create_dir_all(&self.db).map_err(|e| e.to_string())); try!(fs::create_dir_all(&self.keys).map_err(|e| e.to_string())); if signer_enabled { try!(fs::create_dir_all(&self.signer).map_err(|e| e.to_string())); @@ -61,7 +64,8 @@ impl Directories { /// Database paths. pub fn database(&self, genesis_hash: H256, fork_name: Option<String>, spec_name: String) -> DatabaseDirectories { DatabaseDirectories { - path: self.data.clone(), + path: self.db.clone(), + legacy_path: self.base.clone(), genesis_hash: genesis_hash, fork_name: fork_name, spec_name: spec_name, @@ -70,14 +74,14 @@ impl Directories { /// Get the ipc sockets path pub fn ipc_path(&self) -> PathBuf { - let mut dir = Path::new(&self.data).to_path_buf(); + let mut dir = Path::new(&self.base).to_path_buf(); dir.push("ipc"); dir } // TODO: remove in 1.7 pub fn legacy_keys_path(&self, testnet: bool) -> PathBuf { - let mut dir = Path::new(&self.data).to_path_buf(); + let mut dir = Path::new(&self.base).to_path_buf(); if testnet { dir.push("testnet_keys"); } else { @@ -96,6 +100,7 @@ impl Directories { #[derive(Debug, PartialEq)] pub struct DatabaseDirectories { pub path: String, + pub legacy_path: String, pub genesis_hash: H256, pub fork_name: Option<String>, pub spec_name: String, @@ -105,14 +110,13 @@ impl DatabaseDirectories { /// Base DB directory for the given fork. // TODO: remove in 1.7 pub fn legacy_fork_path(&self) -> PathBuf { - let mut dir = Path::new(&self.path).to_path_buf(); + let mut dir = Path::new(&self.legacy_path).to_path_buf(); dir.push(format!("{:?}{}", H64::from(self.genesis_hash), self.fork_name.as_ref().map(|f| format!("-{}", f)).unwrap_or_default())); dir } pub fn spec_root_path(&self) -> PathBuf { let mut dir = Path::new(&self.path).to_path_buf(); - dir.push("chains"); dir.push(&self.spec_name); dir } @@ -204,10 +208,11 @@ mod tests { fn test_default_directories() { let data_dir = super::default_data_path(); let expected = Directories { - data: replace_home(&data_dir, "$DATA"), - keys: replace_home(&data_dir, "$DATA/keys"), - signer: replace_home(&data_dir, "$DATA/signer"), - dapps: replace_home(&data_dir, "$DATA/dapps"), + base: replace_home(&data_dir, "$BASE"), + db: replace_home(&data_dir, "$BASE/chains"), + keys: replace_home(&data_dir, "$BASE/keys"), + signer: replace_home(&data_dir, "$BASE/signer"), + dapps: replace_home(&data_dir, "$BASE/dapps"), }; assert_eq!(expected, Directories::default()); } diff --git a/parity/helpers.rs b/parity/helpers.rs index f359d617f51..74e5bab6313 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -135,7 +135,7 @@ pub fn to_price(s: &str) -> Result<f32, String> { pub fn replace_home(base: &str, arg: &str) -> String { // the $HOME directory on mac os should be `~/Library` or `~/Library/Application Support` let r = arg.replace("$HOME", env::home_dir().unwrap().to_str().unwrap()); - let r = r.replace("$DATA", base ); + let r = r.replace("$BASE", base ); r.replace("/", &::std::path::MAIN_SEPARATOR.to_string() ) } @@ -188,7 +188,7 @@ pub fn to_bootnodes(bootnodes: &Option<String>) -> Result<Vec<String>, String> { pub fn default_network_config() -> ::ethsync::NetworkConfiguration { use ethsync::{NetworkConfiguration, AllowIP}; NetworkConfiguration { - config_path: Some(replace_home(&::dir::default_data_path(), "$DATA/network")), + config_path: Some(replace_home(&::dir::default_data_path(), "$BASE/network")), net_config_path: None, listen_address: Some("0.0.0.0:30303".into()), public_address: None, @@ -257,12 +257,13 @@ pub fn to_client_config( } pub fn execute_upgrades( + base_path: &str, dirs: &DatabaseDirectories, pruning: Algorithm, compaction_profile: CompactionProfile ) -> Result<(), String> { - upgrade_data_paths(dirs, pruning); + upgrade_data_paths(base_path, dirs, pruning); match upgrade(Some(&dirs.path)) { Ok(upgrades_applied) if upgrades_applied > 0 => { diff --git a/parity/rpc.rs b/parity/rpc.rs index b0668bf574e..f5ff05e202b 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -62,7 +62,7 @@ impl Default for IpcConfiguration { let data_dir = default_data_path(); IpcConfiguration { enabled: true, - socket_addr: parity_ipc_path(&data_dir, "$DATA/jsonrpc.ipc"), + socket_addr: parity_ipc_path(&data_dir, "$BASE/jsonrpc.ipc"), apis: ApiSet::IpcContext, } } diff --git a/parity/run.rs b/parity/run.rs index 140c2050cf2..a30001b70b0 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -164,7 +164,7 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> { let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); + try!(execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); // create dirs used by parity try!(cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled)); diff --git a/parity/signer.rs b/parity/signer.rs index 4d282888e33..7cc258ed943 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -44,7 +44,7 @@ impl Default for Configuration { enabled: true, port: 8180, interface: "127.0.0.1".into(), - signer_path: replace_home(&data_dir, "$DATA/signer"), + signer_path: replace_home(&data_dir, "$BASE/signer"), skip_origin_validation: false, } } diff --git a/parity/snapshot.rs b/parity/snapshot.rs index d71ffe92454..42ba8fec2c6 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -167,7 +167,7 @@ impl SnapshotCommand { let snapshot_path = db_dirs.snapshot_path(); // execute upgrades - try!(execute_upgrades(&db_dirs, algorithm, self.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); + try!(execute_upgrades(&self.dirs.base, &db_dirs, algorithm, self.compaction.compaction_profile(db_dirs.db_root_path().as_path()))); // prepare client config let client_config = to_client_config(&self.cache_config, Mode::Active, tracing, fat_db, self.compaction, self.wal, VMType::default(), "".into(), algorithm, self.pruning_history, true); diff --git a/parity/upgrade.rs b/parity/upgrade.rs index 5c49cb99706..94d0adfe478 100644 --- a/parity/upgrade.rs +++ b/parity/upgrade.rs @@ -200,11 +200,11 @@ fn upgrade_user_defaults(dirs: &DatabaseDirectories) { } } -pub fn upgrade_data_paths(dirs: &DatabaseDirectories, pruning: Algorithm) { +pub fn upgrade_data_paths(base_path: &str, dirs: &DatabaseDirectories, pruning: Algorithm) { let legacy_root_path = replace_home("", "$HOME/.parity"); let default_path = default_data_path(); - if legacy_root_path != dirs.path && dirs.path == default_path { - upgrade_dir_location(&PathBuf::from(legacy_root_path), &PathBuf::from(&dirs.path)); + if legacy_root_path != base_path && base_path == default_path { + upgrade_dir_location(&PathBuf::from(legacy_root_path), &PathBuf::from(&base_path)); } upgrade_dir_location(&dirs.legacy_version_path(pruning), &dirs.db_path(pruning)); upgrade_dir_location(&dirs.legacy_snapshot_path(), &dirs.snapshot_path()); From ca0d87da1006eeb920b0bb02b2a6e744c0e94e6b Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 22:05:54 +0100 Subject: [PATCH 360/382] 125 -> 69. --- Cargo.lock | 11 ----------- ethcore/src/client/client.rs | 1 + ethcore/src/client/traits.rs | 2 +- js/src/api/format/output.js | 3 ++- parity/cli/usage.txt | 2 +- 5 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b402ae7838..f86b37a1486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1409,17 +1409,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parking_lot" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parking_lot" version = "0.3.6" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 972960c7371..bd1cc85cc97 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -933,6 +933,7 @@ impl BlockChainClient for Client { fn disable(&self) { self.set_mode(IpcMode::Off); self.enabled.store(false, AtomicOrdering::Relaxed); + self.clear_queue(); } fn set_mode(&self, new_mode: IpcMode) { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index b062990a003..407ecac2862 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -265,7 +265,7 @@ pub trait BlockChainClient : Sync + Send { /// Returns engine-related extra info for `BlockId`. fn block_extra_info(&self, id: BlockId) -> Option<BTreeMap<String, String>>; - /// Returns engine-related extra info for `UncleID`. + /// Returns engine-related extra info for `UncleId`. fn uncle_extra_info(&self, id: UncleId) -> Option<BTreeMap<String, String>>; /// Returns information about pruning/data availability. diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 632ab6f28f1..2fb68445064 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -110,7 +110,8 @@ export function outPeers (peers) { return { active: outNumber(peers.active), connected: outNumber(peers.connected), - max: outNumber(peers.max) + max: outNumber(peers.max), + peers: peers.peers.map(p => { Object.keys(p.protocols).forEach(k => p.protocols[k].difficulty = outNumber(p.protocols[k].difficulty)); return p; }) }; } diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index baf898ad85a..29d65298986 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -361,7 +361,7 @@ Legacy Options: --cache MB Equivalent to --cache-size MB. Internal Options: - --can-restart Executable will auto-restart if exiting with 125. + --can-restart Executable will auto-restart if exiting with 69. Miscellaneous Options: -c --config CONFIG Specify a filename containing a configuration file. From 869fbf5108a559142eebffc2c514748c1b79389f Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 22:07:25 +0100 Subject: [PATCH 361/382] Fix linting. --- js/src/api/format/output.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 2fb68445064..ca1d54ede59 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -111,7 +111,7 @@ export function outPeers (peers) { active: outNumber(peers.active), connected: outNumber(peers.connected), max: outNumber(peers.max), - peers: peers.peers.map(p => { Object.keys(p.protocols).forEach(k => p.protocols[k].difficulty = outNumber(p.protocols[k].difficulty)); return p; }) + peers: peers.peers.map(p => { Object.keys(p.protocols).forEach(k => { p.protocols[k].difficulty = outNumber(p.protocols[k].difficulty); }); return p; }) }; } From 7cac50601fad9dda83f364c8840f421c39f45a81 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 22:45:35 +0100 Subject: [PATCH 362/382] Add JS tests for parity_netPeers --- js/src/api/format/output.spec.js | 44 ++++++++++++++++++++++++++-- js/src/api/rpc/parity/parity.spec.js | 2 +- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index b440476106f..d791e340c41 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -147,10 +147,50 @@ describe('api/format/output', () => { describe('outPeers', () => { it('converts all internal numbers to BigNumbers', () => { - expect(outPeers({ active: 789, connected: '456', max: 0x7b })).to.deep.equal({ + expect(outPeers({ + active: 789, + connected: '456', + max: 0x7b, + peers: [ + { + caps: ["par/1"], + id: "0x01", + name: "Parity", + network: { + localAddress: "10.0.0.1", + remoteAddress: "10.0.0.1" + }, + protocols: { + par: { + difficulty: "0x0f", + head: "0x02", + version: 63 + } + } + } + ] + })).to.deep.equal({ active: new BigNumber(789), connected: new BigNumber(456), - max: new BigNumber(123) + max: new BigNumber(123), + peers: [ + { + caps: ["par/1"], + id: "0x01", + name: "Parity", + network: { + localAddress: "10.0.0.1", + remoteAddress: "10.0.0.1" + }, + protocols: { + par: { + difficulty: new BigNumber(15), + head: "0x02", + version: 63 + } + } + } + ] }); }); }); diff --git a/js/src/api/rpc/parity/parity.spec.js b/js/src/api/rpc/parity/parity.spec.js index d0d97cd0b75..b58c8f85c87 100644 --- a/js/src/api/rpc/parity/parity.spec.js +++ b/js/src/api/rpc/parity/parity.spec.js @@ -80,7 +80,7 @@ describe('api/rpc/parity', () => { describe('newPeers', () => { it('returns the peer structure, formatted', () => { - mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]); + mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789, peers: [] } } }]); return instance.netPeers().then((peers) => { expect(peers.active.eq(123)).to.be.true; From a6a8e431f5c4a6cacece255a052a3b11425cb659 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Thu, 15 Dec 2016 22:46:36 +0100 Subject: [PATCH 363/382] Fix lint --- js/src/api/format/output.spec.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index d791e340c41..1958b57d8c1 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -153,17 +153,17 @@ describe('api/format/output', () => { max: 0x7b, peers: [ { - caps: ["par/1"], - id: "0x01", - name: "Parity", + caps: ['par/1'], + id: '0x01', + name: 'Parity', network: { - localAddress: "10.0.0.1", - remoteAddress: "10.0.0.1" + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' }, protocols: { par: { - difficulty: "0x0f", - head: "0x02", + difficulty: '0x0f', + head: '0x02', version: 63 } } @@ -175,17 +175,17 @@ describe('api/format/output', () => { max: new BigNumber(123), peers: [ { - caps: ["par/1"], - id: "0x01", - name: "Parity", + caps: ['par/1'], + id: '0x01', + name: 'Parity', network: { - localAddress: "10.0.0.1", - remoteAddress: "10.0.0.1" + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' }, protocols: { par: { difficulty: new BigNumber(15), - head: "0x02", + head: '0x02', version: 63 } } From 447196398aab89d9d41fa3b10f4f4dfd7ab7c63a Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Thu, 15 Dec 2016 23:36:06 +0100 Subject: [PATCH 364/382] fix start_step --- ethcore/src/engines/authority_round.rs | 8 ++++++-- sync/src/tests/consensus.rs | 15 ++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 4e7d564fe4e..57bb513aebc 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -100,6 +100,7 @@ impl AsMillis for Duration { impl AuthorityRound { /// Create a new instance of AuthorityRound engine. pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> { + let should_timeout = our_params.start_step.is_none(); let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / our_params.step_duration.as_secs())) as usize; let engine = Arc::new( AuthorityRound { @@ -113,8 +114,11 @@ impl AuthorityRound { account_provider: Mutex::new(None), password: RwLock::new(None), }); - let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; - try!(engine.transition_service.register_handler(Arc::new(handler))); + // Do not initialize timeouts for tests. + if should_timeout { + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; + try!(engine.transition_service.register_handler(Arc::new(handler))); + } Ok(engine) } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index c1fa8fce798..e1f73d06670 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -82,12 +82,14 @@ fn authority_round() { net.sync(); // Trigger block proposal net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into())).unwrap(); // Sync a block net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into())).unwrap(); + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into())).unwrap(); // Move to next proposer step net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); @@ -96,14 +98,17 @@ fn authority_round() { assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); // Fork the network - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into())).unwrap(); + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into())).unwrap(); net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); - assert_eq!(net.peer(0).chain.chain_info().best_block_number, 3); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into())).unwrap(); net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); - assert_eq!(net.peer(1).chain.chain_info().best_block_number, 3); + let ci0 = net.peer(0).chain.chain_info(); + let ci1 = net.peer(1).chain.chain_info(); + assert_eq!(ci0.best_block_number, 3); + assert_eq!(ci1.best_block_number, 3); + assert!(ci0.best_block_hash != ci1.best_block_hash); // Reorg to the correct one. net.sync(); let ci0 = net.peer(0).chain.chain_info(); From d8470cc5ca4dc0e3c21d95972d5333ac2e5cc555 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Fri, 16 Dec 2016 00:07:07 +0100 Subject: [PATCH 365/382] revert slack --- ethcore/src/engines/authority_round.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 57bb513aebc..11d77bc7885 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -466,7 +466,7 @@ mod tests { // Spec starts with step 2. header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_ok()); - header.set_seal(vec![encode(&11usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_err()); } } From ba60e046bea22a40bf72b0a614d90ad2186402b3 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 10:02:42 +0100 Subject: [PATCH 366/382] Tone down logging. --- updater/src/updater.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 8f4977fc2f5..c5697c62367 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -139,10 +139,10 @@ impl Updater { fn collect_latest(&self) -> Result<OperationsInfo, String> { if let Some(ref operations) = *self.operations.lock() { let hh: H256 = self.this.hash.into(); - info!(target: "updater", "Looking up this_fork for our release: {}/{:?}", CLIENT_ID, hh); + trace!(target: "updater", "Looking up this_fork for our release: {}/{:?}", CLIENT_ID, hh); let this_fork = operations.release(CLIENT_ID, &self.this.hash.into()).ok() .and_then(|(fork, track, _, _)| { - info!(target: "updater", "Operations returned fork={}, track={}", fork as u64, track); + trace!(target: "updater", "Operations returned fork={}, track={}", fork as u64, track); if track > 0 {Some(fork as u64)} else {None} }); @@ -212,7 +212,7 @@ impl Updater { } fn poll(&self) { - info!(target: "updater", "Current release is {} ({:?})", self.this, self.this.hash); + trace!(target: "updater", "Current release is {} ({:?})", self.this, self.this.hash); // We rely on a secure state. Bail if we're unsure about it. if self.client.upgrade().map_or(true, |s| !s.chain_info().security_level().is_full()) { @@ -235,7 +235,7 @@ impl Updater { let mut capability = CapState::Unknown; let latest = self.collect_latest().ok(); if let Some(ref latest) = latest { - info!(target: "updater", "Latest release in our track is v{} it is {}critical ({} binary is {})", + trace!(target: "updater", "Latest release in our track is v{} it is {}critical ({} binary is {})", latest.track.version, if latest.track.is_critical {""} else {"non-"}, platform(), @@ -259,7 +259,7 @@ impl Updater { } } } - info!(target: "updater", "Fork: this/current/latest/latest-known: {}/#{}/#{}/#{}", match latest.this_fork { Some(f) => format!("#{}", f), None => "unknown".into(), }, current_number, latest.track.fork, latest.fork); + trace!(target: "updater", "Fork: this/current/latest/latest-known: {}/#{}/#{}/#{}", match latest.this_fork { Some(f) => format!("#{}", f), None => "unknown".into(), }, current_number, latest.track.fork, latest.fork); if let Some(this_fork) = latest.this_fork { if this_fork < latest.fork { From 35b18485d408f71039a43578e4e437fcffca1b3b Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 10:14:44 +0100 Subject: [PATCH 367/382] Don't check for updates while syncing. --- Cargo.lock | 1 + updater/Cargo.toml | 1 + updater/src/lib.rs | 1 + updater/src/updater.rs | 14 +++++++++----- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f86b37a1486..8000617df19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1393,6 +1393,7 @@ dependencies = [ "ethcore-ipc 1.5.0", "ethcore-ipc-codegen 1.5.0", "ethcore-util 1.5.0", + "ethsync 1.5.0", "ipc-common-types 1.5.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.5.0", diff --git a/updater/Cargo.toml b/updater/Cargo.toml index b97e057d755..e166b4c6559 100644 --- a/updater/Cargo.toml +++ b/updater/Cargo.toml @@ -13,6 +13,7 @@ ethcore-ipc-codegen = { path = "../ipc/codegen" } log = "0.3" ethabi = "0.2.2" ethcore = { path = "../ethcore" } +ethsync = { path = "../sync" } ethcore-util = { path = "../util" } parity-hash-fetch = { path = "../hash-fetch" } ipc-common-types = { path = "../ipc-common-types" } diff --git a/updater/src/lib.rs b/updater/src/lib.rs index 1567a88a089..92992deb2af 100644 --- a/updater/src/lib.rs +++ b/updater/src/lib.rs @@ -22,6 +22,7 @@ extern crate ipc_common_types; extern crate parity_hash_fetch as hash_fetch; extern crate ethcore; extern crate ethabi; +extern crate ethsync; extern crate ethcore_ipc as ipc; mod updater; diff --git a/updater/src/updater.rs b/updater/src/updater.rs index c5697c62367..a82748c84e9 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -21,11 +21,13 @@ use std::path::{PathBuf}; use util::misc::platform; use ipc_common_types::{VersionInfo, ReleaseTrack}; use util::{Address, H160, H256, FixedHash, Mutex, Bytes}; +use ethsync::{SyncProvider}; use ethcore::client::{BlockId, BlockChainClient, ChainNotify}; use hash_fetch::{self as fetch, HashFetch}; use operations::Operations; use service::{Service}; use types::all::{ReleaseInfo, OperationsInfo, CapState}; +use ethcore_rpc::is_major_importing; /// Filter for releases. #[derive(Debug, Eq, PartialEq, Clone)] @@ -82,6 +84,7 @@ pub struct Updater { update_policy: UpdatePolicy, weak_self: Mutex<Weak<Updater>>, client: Weak<BlockChainClient>, + sync: Weak<SyncProvider>, fetcher: Mutex<Option<fetch::Client>>, operations: Mutex<Option<Operations>>, exit_handler: Mutex<Option<Box<Fn() + 'static + Send>>>, @@ -96,11 +99,12 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; impl Updater { - pub fn new(client: Weak<BlockChainClient>, update_policy: UpdatePolicy) -> Arc<Self> { + pub fn new(client: Weak<BlockChainClient>, sync: Arc<SyncProvider>, update_policy: UpdatePolicy) -> Arc<Self> { let r = Arc::new(Updater { update_policy: update_policy, weak_self: Mutex::new(Default::default()), client: client.clone(), + sync: sync.clone(), fetcher: Mutex::new(None), operations: Mutex::new(None), exit_handler: Mutex::new(None), @@ -290,10 +294,10 @@ impl Updater { impl ChainNotify for Updater { fn new_blocks(&self, _imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, _proposed: Vec<Bytes>, _duration: u64) { - // TODO: something like this -// if !self.client.upgrade().map_or(true, |c| c.is_major_syncing()) { - self.poll(); -// } + match (self.client.upgrade(), self.sync.upgrade()) { + (Some(c), Some(s)) if is_major_importing(s.status().state, c.queue_info()) => self.poll(), + _ => {}, + } } } From 396ab5b361d80f5e8af39d1908652207886ce5af Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 10:31:10 +0100 Subject: [PATCH 368/382] Refactor and make is_syncing. --- parity/run.rs | 2 +- sync/src/chain.rs | 32 +++++++++++++++++++++++++++++++- updater/src/updater.rs | 5 ++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/parity/run.rs b/parity/run.rs index 77d1bf1e701..23eebe18391 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -312,7 +312,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R } // the updater service - let updater = Updater::new(Arc::downgrade(&(service.client() as Arc<BlockChainClient>)), update_policy); + let updater = Updater::new(Arc::downgrade(&(service.client() as Arc<BlockChainClient>)), Arc::downgrade(&sync_provider), update_policy); service.add_notify(updater.clone()); // set up dependencies for rpc servers diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d3638f35d6b..5f969af354b 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -94,7 +94,7 @@ use rlp::*; use network::*; use ethcore::views::{HeaderView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; -use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo, BlockImportError}; +use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo, BlockImportError, BlockQueueInfo}; use ethcore::error::*; use ethcore::snapshot::{ManifestData, RestorationStatus}; use sync_io::SyncIo; @@ -235,6 +235,13 @@ impl SyncStatus { min_peers } } + + /// Is it doing a major sync? + pub fn is_syncing(&self, queue_info: BlockQueueInfo) -> bool { + let is_syncing_state = match self.state { SyncState::Idle | SyncState::NewBlocks => false, _ => true }; + let is_verifying = queue_info.unverified_queue_size + queue_info.verified_queue_size > 3; + is_verifying || is_syncing_state + } } #[derive(PartialEq, Eq, Debug, Clone)] @@ -2112,6 +2119,29 @@ mod tests { rlp.out() } + fn queue_info(unverified: usize, verified: usize) -> BlockQueueInfo { + BlockQueueInfo { + unverified_queue_size: unverified, + verified_queue_size: verified, + verifying_queue_size: 0, + max_queue_size: 1000, + max_mem_use: 1000, + mem_used: 500 + } + } + + #[test] + fn is_still_verifying() { + assert!(!is_major_importing(None, queue_info(2, 1))); + assert!(is_major_importing(None, queue_info(2, 2))); + } + + #[test] + fn is_synced_state() { + assert!(is_major_importing(Some(SyncState::Blocks), queue_info(0, 0))); + assert!(!is_major_importing(Some(SyncState::Idle), queue_info(0, 0))); + } + #[test] fn return_receipts_empty() { let mut client = TestBlockChainClient::new(); diff --git a/updater/src/updater.rs b/updater/src/updater.rs index a82748c84e9..9f781ed5ad5 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -27,7 +27,6 @@ use hash_fetch::{self as fetch, HashFetch}; use operations::Operations; use service::{Service}; use types::all::{ReleaseInfo, OperationsInfo, CapState}; -use ethcore_rpc::is_major_importing; /// Filter for releases. #[derive(Debug, Eq, PartialEq, Clone)] @@ -99,7 +98,7 @@ pub struct Updater { const CLIENT_ID: &'static str = "parity"; impl Updater { - pub fn new(client: Weak<BlockChainClient>, sync: Arc<SyncProvider>, update_policy: UpdatePolicy) -> Arc<Self> { + pub fn new(client: Weak<BlockChainClient>, sync: Weak<SyncProvider>, update_policy: UpdatePolicy) -> Arc<Self> { let r = Arc::new(Updater { update_policy: update_policy, weak_self: Mutex::new(Default::default()), @@ -295,7 +294,7 @@ impl Updater { impl ChainNotify for Updater { fn new_blocks(&self, _imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, _proposed: Vec<Bytes>, _duration: u64) { match (self.client.upgrade(), self.sync.upgrade()) { - (Some(c), Some(s)) if is_major_importing(s.status().state, c.queue_info()) => self.poll(), + (Some(ref c), Some(ref s)) if s.status().is_syncing(c.queue_info()) => self.poll(), _ => {}, } } From 90d3d330f582c2e66b0f394519477de98b449914 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 11:00:17 +0100 Subject: [PATCH 369/382] Use info! for blockchain status rather than stdout. --- parity/blockchain.rs | 24 ++++++++++++++---------- parity/main.rs | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/parity/blockchain.rs b/parity/blockchain.rs index c01bfdf756b..4129433f40b 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -133,7 +133,7 @@ pub struct ExportState { pub max_balance: Option<U256>, } -pub fn execute(cmd: BlockchainCmd) -> Result<String, String> { +pub fn execute(cmd: BlockchainCmd) -> Result<(), String> { match cmd { BlockchainCmd::Kill(kill_cmd) => kill_db(kill_cmd), BlockchainCmd::Import(import_cmd) => execute_import(import_cmd), @@ -142,7 +142,7 @@ pub fn execute(cmd: BlockchainCmd) -> Result<String, String> { } } -fn execute_import(cmd: ImportBlockchain) -> Result<String, String> { +fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { let timer = Instant::now(); // Setup panic handler @@ -293,7 +293,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<String, String> { let report = client.report(); let ms = timer.elapsed().as_milliseconds(); - Ok(format!("Import completed in {} seconds, {} blocks, {} blk/s, {} transactions, {} tx/s, {} Mgas, {} Mgas/s", + info!("Import completed in {} seconds, {} blocks, {} blk/s, {} transactions, {} tx/s, {} Mgas, {} Mgas/s", ms / 1000, report.blocks_imported, (report.blocks_imported * 1000) as u64 / ms, @@ -301,7 +301,8 @@ fn execute_import(cmd: ImportBlockchain) -> Result<String, String> { (report.transactions_applied * 1000) as u64 / ms, report.gas_processed / From::from(1_000_000), (report.gas_processed / From::from(ms * 1000)).low_u64(), - ).into()) + ); + Ok(()) } fn start_client( @@ -368,7 +369,7 @@ fn start_client( Ok(service) } -fn execute_export(cmd: ExportBlockchain) -> Result<String, String> { +fn execute_export(cmd: ExportBlockchain) -> Result<(), String> { // Setup panic handler let service = try!(start_client(cmd.dirs, cmd.spec, cmd.pruning, cmd.pruning_history, cmd.tracing, cmd.fat_db, cmd.compaction, cmd.wal, cmd.cache_config)); let panic_handler = PanicHandler::new_in_arc(); @@ -396,10 +397,11 @@ fn execute_export(cmd: ExportBlockchain) -> Result<String, String> { } } - Ok("Export completed.".into()) + info!("Export completed."); + Ok(()) } -fn execute_export_state(cmd: ExportState) -> Result<String, String> { +fn execute_export_state(cmd: ExportState) -> Result<(), String> { // Setup panic handler let service = try!(start_client(cmd.dirs, cmd.spec, cmd.pruning, cmd.pruning_history, cmd.tracing, cmd.fat_db, cmd.compaction, cmd.wal, cmd.cache_config)); let panic_handler = PanicHandler::new_in_arc(); @@ -475,10 +477,11 @@ fn execute_export_state(cmd: ExportState) -> Result<String, String> { } } out.write_fmt(format_args!("\n]}}")).expect("Write error"); - Ok("Export completed.".into()) + info!("Export completed."); + Ok(()) } -pub fn kill_db(cmd: KillBlockchain) -> Result<String, String> { +pub fn kill_db(cmd: KillBlockchain) -> Result<(), String> { let spec = try!(cmd.spec.spec()); let genesis_hash = spec.genesis_header().hash(); let db_dirs = cmd.dirs.database(genesis_hash, None, spec.data_dir); @@ -487,7 +490,8 @@ pub fn kill_db(cmd: KillBlockchain) -> Result<String, String> { let algorithm = cmd.pruning.to_algorithm(&user_defaults); let dir = db_dirs.db_path(algorithm); try!(fs::remove_dir_all(&dir).map_err(|e| format!("Error removing database: {:?}", e))); - Ok("Database deleted.".to_owned()) + info!("Database deleted."); + Ok(()) } #[cfg(test)] diff --git a/parity/main.rs b/parity/main.rs index 1fb290f4b30..91aab42a787 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -156,7 +156,7 @@ fn execute(command: Execute, can_restart: bool) -> Result<PostExecutionAction, S Cmd::Hash(maybe_file) => print_hash_of(maybe_file).map(|s| PostExecutionAction::Print(s)), Cmd::Account(account_cmd) => account::execute(account_cmd).map(|s| PostExecutionAction::Print(s)), Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)), - Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|s| PostExecutionAction::Print(s)), + Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|_| PostExecutionAction::Quit), Cmd::SignerToken(signer_cmd) => signer::execute(signer_cmd).map(|s| PostExecutionAction::Print(s)), Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| PostExecutionAction::Print(s)), Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| PostExecutionAction::Print(s)), From b4372650820910dc15ec606943e6da2592138813 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 11:27:00 +0100 Subject: [PATCH 370/382] Fix build. --- .gitlab-ci.yml | 2 +- sync/src/chain.rs | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1c10f0cf85..6deba8ec40b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -365,7 +365,7 @@ windows: - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - cargo build --release #%CARGOFLAGS% - - FOR /F "delims=" %i IN ('target\release\parity.exe tools hash target\release\parity.exe') DO set SHA3=%i + - FOR /F "tokens=* USEBACKQ" %%i IN ('target\release\parity.exe tools hash target\release\parity.exe') DO set SHA3=%%i - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 5f969af354b..81c35fbf1c9 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2130,16 +2130,35 @@ mod tests { } } + fn sync_status(state: SyncState) -> SyncStatus { + SyncStatus { + state: state, + protocol_version: 0, + network_id: 0, + start_block_number: 0, + last_imported_block_number: None, + highest_block_number: None, + blocks_total: 0, + blocks_received: 0, + num_peers: 0, + num_active_peers: 0, + mem_used: 0, + num_snapshot_chunks: 0, + snapshot_chunks_done: 0, + last_imported_old_block_number: None, + } + } + #[test] fn is_still_verifying() { - assert!(!is_major_importing(None, queue_info(2, 1))); - assert!(is_major_importing(None, queue_info(2, 2))); + assert!(!sync_status(SyncState::Idle).is_syncing(queue_info(2, 1))); + assert!(sync_status(SyncState::Idle).is_syncing(queue_info(2, 2))); } #[test] fn is_synced_state() { - assert!(is_major_importing(Some(SyncState::Blocks), queue_info(0, 0))); - assert!(!is_major_importing(Some(SyncState::Idle), queue_info(0, 0))); + assert!(sync_status(SyncState::Blocks).is_syncing(queue_info(0, 0))); + assert!(!sync_status(SyncState::Idle).is_syncing(queue_info(0, 0))); } #[test] From 3450538208409ca522e6ae1ce4b8e8c9f55af8db Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 16 Dec 2016 12:22:56 +0000 Subject: [PATCH 371/382] [ci skip] js-precompiled 20161216-121923 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25c241c1293..97bd9049162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1379,7 +1379,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#2cdda91549dfeebd94775b348a443f8ee5446e9f" +source = "git+https://github.com/ethcore/js-precompiled.git#3d390b35737ce212d358f26b5ec8d9644b252a88" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index edaf527cd04..6f8693b3909 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.127", + "version": "0.2.128", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From f967713d0815b96e2316bf7904cd60c26185fbd6 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Fri, 16 Dec 2016 13:27:14 +0100 Subject: [PATCH 372/382] Added a test --- ethcore/src/tests/client.rs | 37 ++++++++++++++++++++++++++++++++ ethcore/src/types/transaction.rs | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 51c7086b6ff..2e90871336d 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -28,6 +28,9 @@ use rlp::{Rlp, View}; use spec::Spec; use views::BlockView; use util::stats::Histogram; +use ethkey::KeyPair; +use transaction::{PendingTransaction, Transaction, Action}; +use miner::MinerService; #[test] fn imports_from_empty() { @@ -284,3 +287,37 @@ fn change_history_size() { let client = Client::new(config, &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap(); assert_eq!(client.state().balance(&address), 100.into()); } + +#[test] +fn does_not_propagate_delayed_transactions() { + let key = KeyPair::from_secret("test".sha3()).unwrap(); + let secret = key.secret(); + let tx0 = PendingTransaction::new(Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(secret, None), Some(2)); + let tx1 = PendingTransaction::new(Transaction { + nonce: 1.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(secret, None), None); + let client_result = generate_dummy_client(1); + let client = client_result.reference(); + + client.miner().import_own_transaction(&**client, tx0).unwrap(); + client.miner().import_own_transaction(&**client, tx1).unwrap(); + assert_eq!(0, client.pending_transactions().len()); + assert_eq!(2, client.miner().all_transactions().len()); + push_blocks_to_client(client, 53, 2, 1); + client.import_verified_blocks(); + assert_eq!(2, client.pending_transactions().len()); + assert_eq!(2, client.miner().all_transactions().len()); +} + diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 25e91afe548..2cd001bdb21 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -395,7 +395,7 @@ impl Deref for LocalizedTransaction { pub struct PendingTransaction { /// Signed transaction data. pub transaction: SignedTransaction, - /// Gas price. + /// To be activated at this block. `None` for immediately. pub min_block: Option<BlockNumber>, } From 65f07e5aa73b9c5635dc7c14771e003231f60cba Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Fri, 16 Dec 2016 14:54:26 +0100 Subject: [PATCH 373/382] Renamed some functions --- ethcore/light/src/client.rs | 2 +- ethcore/light/src/net/tests/mod.rs | 4 ++-- ethcore/light/src/provider.rs | 6 +++--- ethcore/src/client/client.rs | 4 ++-- ethcore/src/client/test_client.rs | 4 ++-- ethcore/src/client/traits.rs | 4 ++-- ethcore/src/miner/miner.rs | 16 ++++++++-------- ethcore/src/miner/mod.rs | 8 ++++---- ethcore/src/tests/client.rs | 10 +++++----- rpc/src/v1/impls/parity.rs | 2 +- rpc/src/v1/tests/helpers/miner_service.rs | 4 ++-- sync/src/chain.rs | 2 +- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 80100f17c02..edadc440c63 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -114,7 +114,7 @@ impl Provider for Client { Vec::new() } - fn pending_transactions(&self) -> Vec<PendingTransaction> { + fn ready_transactions(&self) -> Vec<PendingTransaction> { Vec::new() } } diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index f9551fc9a1b..64d53d9c850 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -169,8 +169,8 @@ impl Provider for TestProvider { req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() } - fn pending_transactions(&self) -> Vec<PendingTransaction> { - self.0.client.pending_transactions() + fn ready_transactions(&self) -> Vec<PendingTransaction> { + self.0.client.ready_transactions() } } diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index ce80929c2d9..1f9bbf8aaff 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -79,7 +79,7 @@ pub trait Provider: Send + Sync { fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>; /// Provide pending transactions. - fn pending_transactions(&self) -> Vec<PendingTransaction>; + fn ready_transactions(&self) -> Vec<PendingTransaction>; } // Implementation of a light client data provider for a client. @@ -178,7 +178,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() } - fn pending_transactions(&self) -> Vec<PendingTransaction> { - BlockChainClient::pending_transactions(self) + fn ready_transactions(&self) -> Vec<PendingTransaction> { + BlockChainClient::ready_transactions(self) } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 4e54f22b1cf..25f000c89ac 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1334,8 +1334,8 @@ impl BlockChainClient for Client { } } - fn pending_transactions(&self) -> Vec<PendingTransaction> { - self.miner.pending_transactions(self.chain.read().best_block_number()) + fn ready_transactions(&self) -> Vec<PendingTransaction> { + self.miner.ready_transactions(self.chain.read().best_block_number()) } fn queue_consensus_message(&self, message: Bytes) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a3344dab775..a384f12272b 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -688,8 +688,8 @@ impl BlockChainClient for TestBlockChainClient { fn broadcast_consensus_message(&self, _message: Bytes) {} - fn pending_transactions(&self) -> Vec<PendingTransaction> { - self.miner.pending_transactions(self.chain_info().best_block_number) + fn ready_transactions(&self) -> Vec<PendingTransaction> { + self.miner.ready_transactions(self.chain_info().best_block_number) } fn signing_network_id(&self) -> Option<u64> { None } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 4c3b2a37ee4..caba647d122 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -211,8 +211,8 @@ pub trait BlockChainClient : Sync + Send { /// Used by PoA to communicate with peers. fn broadcast_consensus_message(&self, message: Bytes); - /// list all transactions - fn pending_transactions(&self) -> Vec<PendingTransaction>; + /// List all transactions that are allowed into the next block. + fn ready_transactions(&self) -> Vec<PendingTransaction>; /// Sorted list of transaction gas prices from at least last sample_size blocks. fn gas_price_corpus(&self, sample_size: usize) -> Vec<U256> { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3002a1ffc12..c7bff478466 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -909,7 +909,7 @@ impl MinerService for Miner { imported } - fn all_transactions(&self) -> Vec<PendingTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { let queue = self.transaction_queue.lock(); queue.pending_transactions(BlockNumber::max_value()) } @@ -926,7 +926,7 @@ impl MinerService for Miner { self.transaction_queue.lock().future_transactions() } - fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { + fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { let queue = self.transaction_queue.lock(); match self.options.pending_set { PendingSet::AlwaysQueue => queue.pending_transactions(best_block), @@ -1253,8 +1253,8 @@ mod tests { // then assert_eq!(res.unwrap(), TransactionImportResult::Current); - assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions(best_block).len(), 1); + assert_eq!(miner.pending_transactions().len(), 1); + assert_eq!(miner.ready_transactions(best_block).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); assert_eq!(miner.pending_receipts(best_block).len(), 1); // This method will let us know if pending block was created (before calling that method) @@ -1273,8 +1273,8 @@ mod tests { // then assert_eq!(res.unwrap(), TransactionImportResult::Current); - assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.pending_transactions().len(), 1); + assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0); } @@ -1291,9 +1291,9 @@ mod tests { // then assert_eq!(res.unwrap(), TransactionImportResult::Current); - assert_eq!(miner.all_transactions().len(), 1); + assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); - assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index fa8e7df0ab3..563e068a6a0 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -144,11 +144,11 @@ pub trait MinerService : Send + Sync { /// Query pending transactions for hash. fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<SignedTransaction>; - /// Get a list of all ready transactions in the queue. - fn all_transactions(&self) -> Vec<PendingTransaction>; + /// Get a list of all pending transactions in the queue. + fn pending_transactions(&self) -> Vec<PendingTransaction>; - /// Get a list of all pending transactions. - fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>; + /// Get a list of all transactions that can go into the given block. + fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>; /// Get a list of all future transactions. fn future_transactions(&self) -> Vec<PendingTransaction>; diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 2e90871336d..b56feef051c 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -313,11 +313,11 @@ fn does_not_propagate_delayed_transactions() { client.miner().import_own_transaction(&**client, tx0).unwrap(); client.miner().import_own_transaction(&**client, tx1).unwrap(); - assert_eq!(0, client.pending_transactions().len()); - assert_eq!(2, client.miner().all_transactions().len()); - push_blocks_to_client(client, 53, 2, 1); + assert_eq!(0, client.ready_transactions().len()); + assert_eq!(2, client.miner().pending_transactions().len()); + push_blocks_to_client(client, 53, 2, 2); client.import_verified_blocks(); - assert_eq!(2, client.pending_transactions().len()); - assert_eq!(2, client.miner().all_transactions().len()); + assert_eq!(2, client.ready_transactions().len()); + assert_eq!(2, client.miner().pending_transactions().len()); } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 0cfc1ba4d18..326dc9c2b8f 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -270,7 +270,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where fn pending_transactions(&self) -> Result<Vec<Transaction>, Error> { try!(self.active()); - Ok(take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::<Vec<_>>()) + Ok(take_weak!(self.miner).pending_transactions().into_iter().map(Into::into).collect::<Vec<_>>()) } fn future_transactions(&self) -> Result<Vec<Transaction>, Error> { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 0ef0ee0a972..b25bcc39c75 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -204,7 +204,7 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().get(hash).cloned() } - fn all_transactions(&self) -> Vec<PendingTransaction> { + fn pending_transactions(&self) -> Vec<PendingTransaction> { self.pending_transactions.lock().values().cloned().map(Into::into).collect() } @@ -212,7 +212,7 @@ impl MinerService for TestMinerService { self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() } - fn pending_transactions(&self, _best_block: BlockNumber) -> Vec<PendingTransaction> { + fn ready_transactions(&self, _best_block: BlockNumber) -> Vec<PendingTransaction> { self.pending_transactions.lock().values().cloned().map(Into::into).collect() } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 375cf659562..968107ba5c3 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1919,7 +1919,7 @@ impl ChainSync { return 0; } - let transactions = io.chain().pending_transactions(); + let transactions = io.chain().ready_transactions(); if transactions.is_empty() { return 0; } From f345458b65b9334d1ddd14d3bfa9d6d74fd500e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomasz@ethcore.io> Date: Fri, 16 Dec 2016 15:03:31 +0100 Subject: [PATCH 374/382] Loading config from default path --- parity/cli/usage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/cli/usage.rs b/parity/cli/usage.rs index 8c8b54c8707..378ae8ce4d9 100644 --- a/parity/cli/usage.rs +++ b/parity/cli/usage.rs @@ -145,7 +145,7 @@ macro_rules! usage { } let config_file = raw_args.flag_config.clone().unwrap_or_else(|| raw_args.clone().into_args(Config::default()).flag_config); - let config_file = replace_home("", &config_file); + let config_file = replace_home(&::dir::default_data_path(), &config_file); let config = match (fs::File::open(&config_file), raw_args.flag_config.is_some()) { // Load config file (Ok(mut file), _) => { From b89d10239bfa885485a61752a45ff2931db27efc Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 15:12:35 +0100 Subject: [PATCH 375/382] Update tests, gitlabci --- .gitlab-ci.yml | 5 +++-- ethcore/res/ethereum/tests | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6deba8ec40b..245d890adfb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -365,7 +365,8 @@ windows: - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - cargo build --release #%CARGOFLAGS% - - FOR /F "tokens=* USEBACKQ" %%i IN ('target\release\parity.exe tools hash target\release\parity.exe') DO set SHA3=%%i + - target\release\parity.exe tools hash target\release\parity.exe > parity.sha3 + - set /p SHA3 = < parity.sha3 - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe @@ -400,7 +401,7 @@ windows: - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5 - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5 - - curl --data "commit=$CI_BUILD_REF&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://icarus.parity.io/push-build/$CI_BUILD_REF_NAME/%PLATFORM% + - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://icarus.parity.io/push-build/%CI_BUILD_REF_NAME%/%PLATFORM% tags: - rust-windows artifacts: diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 9028c4801fd..e8f4624b7f1 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be From 6966fd6c636859b90451f8dd30bdb05fb5e177d5 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Fri, 16 Dec 2016 15:24:38 +0100 Subject: [PATCH 376/382] New paths --- parity/dir.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/parity/dir.rs b/parity/dir.rs index e788fbd56fc..1a045ca0931 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -21,6 +21,16 @@ use util::journaldb::Algorithm; use helpers::replace_home; use app_dirs::{AppInfo, get_app_root, AppDataType}; +#[cfg(target_os = "macos")] const AUTHOR: &'static str = "Parity"; +#[cfg(target_os = "macos")] const PRODUCT: &'static str = "io.parity.ethereum"; +#[cfg(target_os = "macos")] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-hypervisor"; +#[cfg(target_os = "windows")] const AUTHOR: &'static str = "Parity"; +#[cfg(target_os = "windows")] const PRODUCT: &'static str = "Ethereum"; +#[cfg(target_os = "windows")] const PRODUCT_HYPERVISOR: &'static str = "EthereumHypervisor"; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] const AUTHOR: &'static str = "parity"; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT: &'static str = "parity"; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT_HYPERVISOR: &'static str = "parity-hypervisor"; + // this const is irrelevent cause we do have migrations now, // but we still use it for backwards compatibility const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; @@ -195,12 +205,12 @@ impl DatabaseDirectories { } pub fn default_data_path() -> String { - let app_info = AppInfo { name: "parity", author: "parity" }; + let app_info = AppInfo { name: PRODUCT, author: AUTHOR }; get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) } pub fn default_hypervisor_path() -> String { - let app_info = AppInfo { name: "parity-hypervisor", author: "parity" }; + let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR }; get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity-hypervisor".to_owned()) } From b9cd68e7e6a0a9a8a2cf7bf1d7910dc59eebfcdf Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 17:38:02 +0100 Subject: [PATCH 377/382] Minor paths change --- parity/dir.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parity/dir.rs b/parity/dir.rs index 1a045ca0931..5b11d806609 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -23,13 +23,13 @@ use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(target_os = "macos")] const AUTHOR: &'static str = "Parity"; #[cfg(target_os = "macos")] const PRODUCT: &'static str = "io.parity.ethereum"; -#[cfg(target_os = "macos")] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-hypervisor"; +#[cfg(target_os = "macos")] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-updates"; #[cfg(target_os = "windows")] const AUTHOR: &'static str = "Parity"; #[cfg(target_os = "windows")] const PRODUCT: &'static str = "Ethereum"; -#[cfg(target_os = "windows")] const PRODUCT_HYPERVISOR: &'static str = "EthereumHypervisor"; +#[cfg(target_os = "windows")] const PRODUCT_HYPERVISOR: &'static str = "EthereumUpdates"; #[cfg(not(any(target_os = "windows", target_os = "macos")))] const AUTHOR: &'static str = "parity"; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT: &'static str = "parity"; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT_HYPERVISOR: &'static str = "parity-hypervisor"; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT: &'static str = "io.parity.ethereum"; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-updates"; // this const is irrelevent cause we do have migrations now, // but we still use it for backwards compatibility From 14e4fefbcfcb2193bdc2eea1daa999f1b5a26558 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 18:17:15 +0100 Subject: [PATCH 378/382] Fix updater permissions and refactor existing code. --- parity/signer.rs | 2 +- updater/src/updater.rs | 6 ++++-- util/network/src/host.rs | 4 ++-- util/src/path.rs | 15 ++++++--------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/parity/signer.rs b/parity/signer.rs index 7cc258ed943..b4b3679ef41 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -72,7 +72,7 @@ pub fn start(conf: Configuration, deps: Dependencies) -> Result<Option<SignerSer fn codes_path(path: String) -> PathBuf { let mut p = PathBuf::from(path); p.push(CODES_FILENAME); - let _ = restrict_permissions_owner(&p); + let _ = restrict_permissions_owner(&p, true, false); p } diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 9f781ed5ad5..5af36b52f4b 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -18,8 +18,9 @@ use std::sync::{Arc, Weak}; use std::fs; use std::io::Write; use std::path::{PathBuf}; -use util::misc::platform; use ipc_common_types::{VersionInfo, ReleaseTrack}; +use util::path::restrict_permissions_owner; +use util::misc::platform; use util::{Address, H160, H256, FixedHash, Mutex, Bytes}; use ethsync::{SyncProvider}; use ethcore::client::{BlockId, BlockChainClient, ChainNotify}; @@ -197,7 +198,8 @@ impl Updater { let dest = self.updates_path(&Self::update_file_name(&fetched.version)); fs::create_dir_all(dest.parent().expect("at least one thing pushed; qed")).map_err(|e| format!("Unable to create updates path: {:?}", e))?; fs::copy(&b, &dest).map_err(|e| format!("Unable to copy update: {:?}", e))?; - info!(target: "updater", "Copied file to {}", dest.display()); + restrict_permissions_owner(&dest, false, true).map_err(|e| format!("Unable to update permissions: {}", e))?; + info!(target: "updater", "Installed updated binary to {}", dest.display()); let auto = match self.update_policy.filter { UpdateFilter::All => true, UpdateFilter::Critical if fetched.is_critical /* TODO: or is on a bad fork */ => true, diff --git a/util/network/src/host.rs b/util/network/src/host.rs index 2bdeab93e0b..681773c36b6 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -1168,8 +1168,8 @@ fn save_key(path: &Path, key: &Secret) { return; } }; - if let Err(e) = restrict_permissions_owner(path) { - warn!(target: "network", "Failed to modify permissions of the file (chmod: {})", e); + if let Err(e) = restrict_permissions_owner(path, true, false) { + warn!(target: "network", "Failed to modify permissions of the file ({})", e); } if let Err(e) = file.write(&key.hex().into_bytes()) { warn!("Error writing key file: {:?}", e); diff --git a/util/src/path.rs b/util/src/path.rs index 4cb0b413d6f..cbeec8a4428 100644 --- a/util/src/path.rs +++ b/util/src/path.rs @@ -86,18 +86,15 @@ pub mod ethereum { } /// Restricts the permissions of given path only to the owner. -#[cfg(not(windows))] -pub fn restrict_permissions_owner(file_path: &Path) -> Result<(), i32> { - let cstr = ::std::ffi::CString::new(file_path.to_str().unwrap()).unwrap(); - match unsafe { ::libc::chmod(cstr.as_ptr(), ::libc::S_IWUSR | ::libc::S_IRUSR) } { - 0 => Ok(()), - x => Err(x), - } +#[cfg(unix)] +pub fn restrict_permissions_owner(file_path: &Path, write: bool, executable: bool) -> Result<(), String> { + let perms = ::std::os::unix::fs::PermissionsExt::from_mode(0o400 + write as u32 * 0o200 + executable as u32 * 0o100); + ::std::fs::set_permissions(file_path, perms).map_err(|e| format!("{:?}", e)) } /// Restricts the permissions of given path only to the owner. -#[cfg(windows)] -pub fn restrict_permissions_owner(_file_path: &Path) -> Result<(), i32> { +#[cfg(not(unix))] +pub fn restrict_permissions_owner(_file_path: &Path) -> Result<(), String> { //TODO: implement me Ok(()) } From c2448f6c2fdf9eb553aae15b1fabb604af8d4970 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 18:32:33 +0100 Subject: [PATCH 379/382] Add mac icon. --- "mac/Parity Ethereum.app/Icon\r" | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "mac/Parity Ethereum.app/Icon\r" diff --git "a/mac/Parity Ethereum.app/Icon\r" "b/mac/Parity Ethereum.app/Icon\r" new file mode 100644 index 00000000000..e69de29bb2d From 6f977f03ae5d8c4c410b697d20e1951ff812c345 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 20:26:49 +0100 Subject: [PATCH 380/382] Rename from troublesome filename --- "mac/Parity Ethereum.app/Icon\r" => mac/icon | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "mac/Parity Ethereum.app/Icon\r" => mac/icon (100%) diff --git "a/mac/Parity Ethereum.app/Icon\r" b/mac/icon similarity index 100% rename from "mac/Parity Ethereum.app/Icon\r" rename to mac/icon From 75c92e935622090d34c9bd91a6b385856d451c48 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 16 Dec 2016 20:34:08 +0100 Subject: [PATCH 381/382] Add Parity.png --- mac/Parity Ethereum.app/Contents/Info.plist | 2 +- mac/Parity Ethereum.app/Contents/Parity.png | Bin 0 -> 108096 bytes mac/icon | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 mac/Parity Ethereum.app/Contents/Parity.png delete mode 100644 mac/icon diff --git a/mac/Parity Ethereum.app/Contents/Info.plist b/mac/Parity Ethereum.app/Contents/Info.plist index 8bb76f95cb0..6732cf753ed 100644 --- a/mac/Parity Ethereum.app/Contents/Info.plist +++ b/mac/Parity Ethereum.app/Contents/Info.plist @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>CFBundlePackageType</key><string>APPL</string><key>CFBundleInfoDictionaryVersion</key><string>6.0</string> +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>CFBundleIconFiles</key><string>Parity.png</string><key>CFBundlePackageType</key><string>APPL</string><key>CFBundleInfoDictionaryVersion</key><string>6.0</string> <key>CFBundleName</key> <string>Parity Ethereum</string> <key>CFBundleExecutable</key> <string>go.sh</string> diff --git a/mac/Parity Ethereum.app/Contents/Parity.png b/mac/Parity Ethereum.app/Contents/Parity.png new file mode 100644 index 0000000000000000000000000000000000000000..d54082a9ede759b0423b08963562e994f5415e71 GIT binary patch literal 108096 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 OK(n#1H-BpiN%|kn*A=1w literal 0 HcmV?d00001 diff --git a/mac/icon b/mac/icon deleted file mode 100644 index e69de29bb2d..00000000000 From 4c0741e568f457b8f8dd5a1e4b512d4894425037 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan <arkady.paronyan@gmail.com> Date: Fri, 16 Dec 2016 22:26:12 +0100 Subject: [PATCH 382/382] Fixed windows stub signature --- util/src/path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/path.rs b/util/src/path.rs index cbeec8a4428..09e7d8e72ab 100644 --- a/util/src/path.rs +++ b/util/src/path.rs @@ -94,7 +94,7 @@ pub fn restrict_permissions_owner(file_path: &Path, write: bool, executable: boo /// Restricts the permissions of given path only to the owner. #[cfg(not(unix))] -pub fn restrict_permissions_owner(_file_path: &Path) -> Result<(), String> { +pub fn restrict_permissions_owner(_file_path: &Path, _write: bool, _executable: bool) -> Result<(), String> { //TODO: implement me Ok(()) }