From 2bd5c3dba72d11a552dced820a35bc0a04988865 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 28 Jul 2017 19:38:52 +0200 Subject: [PATCH 01/14] checking proofs in safe contract --- ethcore/src/client/client.rs | 2 +- ethcore/src/engines/mod.rs | 14 +- ethcore/src/engines/tendermint/mod.rs | 45 +++--- .../engines/validator_set/safe_contract.rs | 138 +++++++++++------- 4 files changed, 124 insertions(+), 75 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 48cf481f1..af56bb7f7 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -765,7 +765,7 @@ impl Client { res.map(|(output, proof)| (output, proof.into_iter().map(|x| x.into_vec()).collect())) }; - match (with_state)(&call) { + match with_state.generate_proof(&call) { Ok(proof) => proof, Err(e) => { warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e); diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c5fa4818e..e28ae0bc1 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -120,12 +120,22 @@ pub type Headers<'a> = Fn(H256) -> Option
+ 'a; /// Type alias for a function we can query pending transitions by block hash through. pub type PendingTransitionStore<'a> = Fn(H256) -> Option + 'a; +/// Proof dependent on state. +pub trait StateDependentProof { + /// Generate a proof, given the state. + fn generate_proof(&self, caller: &Call) -> Result, String>; + /// Check a proof generated elsewhere (potentially by a peer). + // `engine` needed to check state proofs, while really this should + // just be state machine params. + fn check_proof(&self, engine: &Engine, proof: &[u8]) -> Result<(), String>; +} + /// Proof generated on epoch change. pub enum Proof { /// Known proof (exctracted from signal) Known(Vec), - /// Extract proof from caller. - WithState(Box Result, String>>), + /// State dependent proof. + WithState(Box), } /// Generated epoch verifier. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 453e8f9fb..1a9626fff 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -577,18 +577,35 @@ impl Engine for Tendermint { Ok(()) } - /// Verify validators and gas limit. + /// Verify gas limit. fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.number() == 0 { return Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }).into()); } + let gas_limit_divisor = self.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 { + self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default()); + return Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }).into()); + } + + Ok(()) + } + + fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if let Ok(proposal) = ConsensusMessage::new_proposal(header) { let proposer = proposal.verify()?; if !self.is_authority(&proposer) { return Err(EngineError::NotAuthorized(proposer).into()); } - self.check_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?; + self.check_view_proposer( + header.parent_hash(), + proposal.vote_step.height, + proposal.vote_step.view, + &proposer + ).map_err(Into::into) } else { let vote_step = VoteStep::new(header.number() as usize, consensus_view(header)?, Step::Precommit); let precommit_hash = message_hash(vote_step.clone(), header.bare_hash()); @@ -614,18 +631,8 @@ impl Engine for Tendermint { } } - self.check_above_threshold(origins.len())? + self.check_above_threshold(origins.len()).map_err(Into::into) } - - let gas_limit_divisor = self.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 { - self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default()); - return Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }).into()); - } - - Ok(()) } fn signals_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>) @@ -892,14 +899,14 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); + assert!(engine.verify_block_external(&header, None).is_ok()); let validator = insert_and_unlock(&tap, "0"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. - match engine.verify_block_family(&header, &parent_header, None) { + match engine.verify_block_external(&header, None) { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } @@ -909,7 +916,7 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Not authority. - match engine.verify_block_family(&header, &parent_header, None) { + match engine.verify_block_external(&header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; @@ -939,7 +946,7 @@ mod tests { header.set_seal(seal.clone()); // One good signature is not enough. - match engine.verify_block_family(&header, &parent_header, None) { + match engine.verify_block_external(&header, None) { Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, _ => panic!(), } @@ -950,7 +957,7 @@ mod tests { seal[2] = ::rlp::encode_list(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).into_vec(); header.set_seal(seal.clone()); - assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); + assert!(engine.verify_block_external(&header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); @@ -959,7 +966,7 @@ mod tests { header.set_seal(seal); // One good and one bad signature. - match engine.verify_block_family(&header, &parent_header, None) { + match engine.verify_block_external(&header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 2c42323be..f8171a9b9 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -44,6 +44,29 @@ lazy_static! { static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3(); } +// state-dependent proofs for the safe contract: +// only "first" proofs are such. +struct StateProof { + header: Header, + provider: Provider, +} + +impl ::engines::StateDependentProof for StateProof { + fn generate_proof(&self, caller: &Call) -> Result, String> { + prove_initial(&self.provider, &self.header, caller) + } + + fn check_proof(&self, engine: &Engine, proof: &[u8]) -> Result<(), String> { + let (header, state_items) = decode_first_proof(&UntrustedRlp::new(proof)) + .map_err(|e| format!("proof incorrectly encoded: {}", e))?; + if header != self.header { + return Err("wrong header in proof".into()); + } + + check_first_proof(engine, &self.provider, header, &state_items).map(|_| ()) + } +} + /// The validator contract should have the following interface: pub struct ValidatorSafeContract { pub address: Address, @@ -63,6 +86,59 @@ fn encode_first_proof(header: &Header, state_items: &[Vec]) -> Bytes { stream.out() } +// check a first proof: fetch the validator set at the given block. +fn check_first_proof(engine: &Engine, provider: &Provider, old_header: Header, state_items: &[DBValue]) + -> Result, String> +{ + use transaction::{Action, Transaction}; + + // TODO: match client contract_call_tx more cleanly without duplication. + const PROVIDED_GAS: u64 = 50_000_000; + + let env_info = ::evm::env_info::EnvInfo { + number: old_header.number(), + author: *old_header.author(), + difficulty: *old_header.difficulty(), + gas_limit: PROVIDED_GAS.into(), + timestamp: old_header.timestamp(), + last_hashes: { + // this will break if we don't inclue all 256 last hashes. + let mut last_hashes: Vec<_> = (0..256).map(|_| H256::default()).collect(); + last_hashes[255] = *old_header.parent_hash(); + Arc::new(last_hashes) + }, + gas_used: 0.into(), + }; + + // check state proof using given engine. + let number = old_header.number(); + provider.get_validators(move |a, d| { + let from = Address::default(); + let tx = Transaction { + nonce: engine.account_start_nonce(number), + action: Action::Call(a), + gas: PROVIDED_GAS.into(), + gas_price: U256::default(), + value: U256::default(), + data: d, + }.fake_sign(from); + + let res = ::state::check_proof( + state_items, + *old_header.state_root(), + &tx, + engine, + &env_info, + ); + + match res { + ::state::ProvedExecution::BadProof => Err("Bad proof".into()), + ::state::ProvedExecution::Failed(e) => Err(format!("Failed call: {}", e)), + ::state::ProvedExecution::Complete(e) => Ok(e.output), + } + }).wait() +} + fn decode_first_proof(rlp: &UntrustedRlp) -> Result<(Header, Vec), ::error::Error> { let header = rlp.val_at(0)?; let state_items = rlp.at(1)?.iter().map(|x| { @@ -100,8 +176,7 @@ fn prove_initial(provider: &Provider, header: &Header, caller: &Call) -> Result< Ok(result) }; - provider.get_validators(caller) - .wait() + provider.get_validators(caller).wait() }; res.map(|validators| { @@ -255,9 +330,11 @@ impl ValidatorSet for ValidatorSafeContract { // transition to the first block of a contract requires finality but has no log event. if first { debug!(target: "engine", "signalling transition to fresh contract."); - let (provider, header) = (self.provider.clone(), header.clone()); - let with_caller: Box _> = Box::new(move |caller| prove_initial(&provider, &header, caller)); - return ::engines::EpochChange::Yes(::engines::Proof::WithState(with_caller)) + let state_proof = Box::new(StateProof { + header: header.clone(), + provider: self.provider.clone(), + }); + return ::engines::EpochChange::Yes(::engines::Proof::WithState(state_proof as Box<_>)); } // otherwise, we're checking for logs. @@ -286,61 +363,16 @@ impl ValidatorSet for ValidatorSafeContract { fn epoch_set(&self, first: bool, engine: &Engine, _number: ::header::BlockNumber, proof: &[u8]) -> Result<(SimpleList, Option), ::error::Error> { - use transaction::{Action, Transaction}; - let rlp = UntrustedRlp::new(proof); if first { trace!(target: "engine", "Recovering initial epoch set"); - // TODO: match client contract_call_tx more cleanly without duplication. - const PROVIDED_GAS: u64 = 50_000_000; - let (old_header, state_items) = decode_first_proof(&rlp)?; - let old_hash = old_header.hash(); - - let env_info = ::evm::env_info::EnvInfo { - number: old_header.number(), - author: *old_header.author(), - difficulty: *old_header.difficulty(), - gas_limit: PROVIDED_GAS.into(), - timestamp: old_header.timestamp(), - last_hashes: { - // this will break if we don't inclue all 256 last hashes. - let mut last_hashes: Vec<_> = (0..256).map(|_| H256::default()).collect(); - last_hashes[255] = *old_header.parent_hash(); - Arc::new(last_hashes) - }, - gas_used: 0.into(), - }; - - // check state proof using given engine. let number = old_header.number(); - let addresses = self.provider.get_validators(move |a, d| { - let from = Address::default(); - let tx = Transaction { - nonce: engine.account_start_nonce(number), - action: Action::Call(a), - gas: PROVIDED_GAS.into(), - gas_price: U256::default(), - value: U256::default(), - data: d, - }.fake_sign(from); - - let res = ::state::check_proof( - &state_items, - *old_header.state_root(), - &tx, - engine, - &env_info, - ); - - match res { - ::state::ProvedExecution::BadProof => Err("Bad proof".into()), - ::state::ProvedExecution::Failed(e) => Err(format!("Failed call: {}", e)), - ::state::ProvedExecution::Complete(e) => Ok(e.output), - } - }).wait().map_err(::engines::EngineError::InsufficientProof)?; + let old_hash = old_header.hash(); + let addresses = check_first_proof(engine, &self.provider, old_header, &state_items) + .map_err(::engines::EngineError::InsufficientProof)?; trace!(target: "engine", "extracted epoch set at #{}: {} addresses", number, addresses.len()); From 0abf2abc813767f0c76528fe8a5e31aeefc020db Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 3 Aug 2017 18:18:19 +0200 Subject: [PATCH 02/14] checking for signals in the light client --- ethcore/light/src/client/fetch.rs | 69 ++++++++++++ ethcore/light/src/client/mod.rs | 116 +++++++++++++++++++-- ethcore/light/src/client/service.rs | 22 ++-- ethcore/src/engines/authority_round/mod.rs | 2 + ethcore/src/engines/mod.rs | 2 +- 5 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 ethcore/light/src/client/fetch.rs diff --git a/ethcore/light/src/client/fetch.rs b/ethcore/light/src/client/fetch.rs new file mode 100644 index 000000000..0f11ef2f4 --- /dev/null +++ b/ethcore/light/src/client/fetch.rs @@ -0,0 +1,69 @@ +// Copyright 2015-2017 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 . + +//! Trait for fetching chain data. + +use ethcore::header::Header; +use ethcore::receipt::Receipt; +use futures::future::IntoFuture; + +/// Provides full chain data. +pub trait ChainDataFetcher: Send + Sync + 'static { + /// Error type when data unavailable. + type Error: ::std::fmt::Debug; + + /// Future for fetching block body. + type Body: IntoFuture,Error=Self::Error>; + /// Future for fetching block receipts. + type Receipts: IntoFuture,Error=Self::Error>; + /// Future for fetching epoch transition + type Transition: IntoFuture,Error=Self::Error>; + + /// Fetch a block body. + fn block_body(&self, header: &Header) -> Self::Body; + + /// Fetch block receipts. + fn block_receipts(&self, header: &Header) -> Self::Receipts; + + /// Fetch epoch transition proof at given header. + fn epoch_transition(&self, header: &Header) -> Self::Transition; +} + +/// Fetcher implementation which cannot fetch anything. +pub struct Unavailable; + +/// Create a fetcher which has all data unavailable. +pub fn unavailable() -> Unavailable { Unavailable } + +impl ChainDataFetcher for Unavailable { + type Error = &'static str; + + type Body = Result, &'static str>; + type Receipts = Result, &'static str>; + type Transition = Result, &'static str>; + + fn block_body(&self, _header: &Header) -> Self::Body { + Err("fetching block bodies unavailable") + } + + fn block_receipts(&self, _header: &Header) -> Self::Receipts { + Err("fetching block receipts unavailable") + } + + fn epoch_transition(&self, _header: &Header) -> Self::Body { + Err("fetching epoch transition proofs unavailable") + } +} diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 3f0e50584..b3d2dff4f 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -20,7 +20,7 @@ use std::sync::{Weak, Arc}; use ethcore::block_status::BlockStatus; use ethcore::client::{ClientReport, EnvInfo}; -use ethcore::engines::Engine; +use ethcore::engines::{Engine, EpochChange, Proof, Unsure}; use ethcore::error::BlockImportError; use ethcore::ids::BlockId; use ethcore::header::Header; @@ -31,9 +31,12 @@ use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; +use futures::{IntoFuture, Future}; + use util::{H256, U256, Mutex, RwLock}; use util::kvdb::{KeyValueDB, CompactionProfile}; +use self::fetch::ChainDataFetcher; use self::header_chain::{AncestryIter, HeaderChain}; use cache::Cache; @@ -43,6 +46,8 @@ pub use self::service::Service; mod header_chain; mod service; +pub mod fetch; + /// Configuration for the light client. #[derive(Debug, Clone)] pub struct Config { @@ -154,7 +159,7 @@ impl AsLightClient for T { } /// Light client implementation. -pub struct Client { +pub struct Client { queue: HeaderQueue, engine: Arc, chain: HeaderChain, @@ -162,12 +167,21 @@ pub struct Client { import_lock: Mutex<()>, db: Arc, listeners: RwLock>>, + fetcher: T, verify_full: bool, } -impl Client { +impl Client { /// Create a new `Client`. - pub fn new(config: Config, db: Arc, chain_col: Option, spec: &Spec, io_channel: IoChannel, cache: Arc>) -> Result { + pub fn new( + config: Config, + db: Arc, + chain_col: Option, + spec: &Spec, + fetcher: T, + io_channel: IoChannel, + cache: Arc> + ) -> Result { let gh = ::rlp::encode(&spec.genesis_header()); Ok(Client { @@ -178,6 +192,7 @@ impl Client { import_lock: Mutex::new(()), db: db, listeners: RwLock::new(vec![]), + fetcher: fetcher, verify_full: config.verify_full, }) } @@ -189,10 +204,24 @@ impl Client { /// Create a new `Client` backed purely in-memory. /// This will ignore all database options in the configuration. - pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel, cache: Arc>) -> Self { + pub fn in_memory( + config: Config, + spec: &Spec, + fetcher: T, + io_channel: IoChannel, + cache: Arc> + ) -> Self { let db = ::util::kvdb::in_memory(0); - Client::new(config, Arc::new(db), None, spec, io_channel, cache).expect("New DB creation infallible; qed") + Client::new( + config, + Arc::new(db), + None, + spec, + fetcher, + io_channel, + cache + ).expect("New DB creation infallible; qed") } /// Import a header to the queue for additional verification. @@ -291,9 +320,14 @@ impl Client { continue } - // TODO: `epoch_end_signal`, `is_epoch_end`. - // proofs we get from the network would be _complete_, whereas we need - // _incomplete_ signals + let _write_proof_result = match self.check_epoch_signal(&verified_header) { + Ok(Some(proof)) => self.write_pending_proof(&verified_header, proof), + Ok(None) => Ok(()), + Err(e) => + panic!("Unable to fetch epoch transition proof: {:?}", e), + }; + + // TODO: check epoch end. let mut tx = self.db.transaction(); let pending = match self.chain.insert(&mut tx, verified_header) { @@ -419,9 +453,71 @@ impl Client { true } + + fn check_epoch_signal(&self, verified_header: &Header) -> Result, T::Error> { + let (mut block, mut receipts) = (None, None); + + // First, check without providing auxiliary data. + match self.engine.signals_epoch_end(verified_header, None, None) { + EpochChange::No => return Ok(None), + EpochChange::Yes(proof) => return Ok(Some(proof)), + EpochChange::Unsure(unsure) => { + let (b, r) = match unsure { + Unsure::NeedsBody => + (Some(self.fetcher.block_body(verified_header)), None), + Unsure::NeedsReceipts => + (None, Some(self.fetcher.block_receipts(verified_header))), + Unsure::NeedsBoth => ( + Some(self.fetcher.block_body(verified_header)), + Some(self.fetcher.block_receipts(verified_header)), + ), + }; + + if let Some(b) = b { + block = Some(b.into_future().wait()?); + } + + if let Some(r) = r { + receipts = Some(r.into_future().wait()?); + } + } + } + + let block = block.as_ref().map(|x| &x[..]); + let receipts = receipts.as_ref().map(|x| &x[..]); + + // Check again now that required data has been fetched. + match self.engine.signals_epoch_end(verified_header, block, receipts) { + EpochChange::No => return Ok(None), + EpochChange::Yes(proof) => return Ok(Some(proof)), + EpochChange::Unsure(_) => + panic!("Detected faulty engine implementation: requests additional \ + data to check epoch end signal when everything necessary provided"), + } + } + + // attempts to fetch the epoch proof from the network until successful. + fn write_pending_proof(&self, header: &Header, proof: Proof) -> Result<(), T::Error> { + let _proof = match proof { + Proof::Known(known) => known, + Proof::WithState(state_dependent) => { + loop { + let proof = self.fetcher.epoch_transition(header).into_future().wait()?; + match state_dependent.check_proof(&*self.engine, &proof) { + Ok(()) => break proof, + Err(e) => + debug!(target: "client", "Fetched bad epoch transition proof from network: {}", e), + } + } + } + }; + + // TODO: actually write it + unimplemented!() + } } -impl LightChainClient for Client { +impl LightChainClient for Client { fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } fn queue_header(&self, header: Header) -> Result { diff --git a/ethcore/light/src/client/service.rs b/ethcore/light/src/client/service.rs index 83949a2f1..59a5d28c6 100644 --- a/ethcore/light/src/client/service.rs +++ b/ethcore/light/src/client/service.rs @@ -30,7 +30,7 @@ use util::kvdb::{Database, DatabaseConfig}; use cache::Cache; use util::Mutex; -use super::{Client, Config as ClientConfig}; +use super::{ChainDataFetcher, Client, Config as ClientConfig}; /// Errors on service initialization. #[derive(Debug)] @@ -51,14 +51,14 @@ impl fmt::Display for Error { } /// Light client service. -pub struct Service { - client: Arc, +pub struct Service { + client: Arc>, io_service: IoService, } -impl Service { +impl Service { /// Start the service: initialize I/O workers and client itself. - pub fn start(config: ClientConfig, spec: &Spec, path: &Path, cache: Arc>) -> Result { + pub fn start(config: ClientConfig, spec: &Spec, fetcher: T, path: &Path, cache: Arc>) -> Result { // initialize database. let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS); @@ -81,6 +81,7 @@ impl Service { db, db::COL_LIGHT_CHAIN, spec, + fetcher, io_service.channel(), cache, ).map_err(Error::Database)?); @@ -97,14 +98,14 @@ impl Service { } /// Get a handle to the client. - pub fn client(&self) -> &Arc { + pub fn client(&self) -> &Arc> { &self.client } } -struct ImportBlocks(Arc); +struct ImportBlocks(Arc>); -impl IoHandler for ImportBlocks { +impl IoHandler for ImportBlocks { fn message(&self, _io: &IoContext, message: &ClientIoMessage) { if let ClientIoMessage::BlockVerified = *message { self.0.import_verified(); @@ -117,9 +118,10 @@ mod tests { use super::Service; use devtools::RandomTempPath; use ethcore::spec::Spec; - + use std::sync::Arc; use cache::Cache; + use client::fetch; use time::Duration; use util::Mutex; @@ -129,6 +131,6 @@ mod tests { let temp_path = RandomTempPath::new(); let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - Service::start(Default::default(), &spec, temp_path.as_path(), cache).unwrap(); + Service::start(Default::default(), &spec, fetch::unavailable(), temp_path.as_path(), cache).unwrap(); } } diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 3210368db..0dc33d342 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -666,6 +666,8 @@ impl Engine for AuthorityRound { (&active_set as &_, epoch_manager.epoch_transition_number) }; + // always report with "self.validators" so that the report actually gets + // to the contract. let report = |report| match report { Report::Benign(address, block_number) => self.validators.report_benign(&address, set_number, block_number), diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index e28ae0bc1..3997b66c3 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -132,7 +132,7 @@ pub trait StateDependentProof { /// Proof generated on epoch change. pub enum Proof { - /// Known proof (exctracted from signal) + /// Known proof (extracted from signal) Known(Vec), /// State dependent proof. WithState(Box), From 1e269c94a6787611b7359f63ca0de599791080af Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Aug 2017 00:12:40 +0200 Subject: [PATCH 03/14] prove_transaction function on state --- ethcore/src/client/client.rs | 24 ++++++++++------------- ethcore/src/spec/spec.rs | 3 +-- ethcore/src/state/mod.rs | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index af56bb7f7..2695ad84a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1884,27 +1884,23 @@ impl ProvingBlockChainClient for Client { } fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec)> { - let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) { + let (header, mut env_info) = match (self.block_header(id), self.env_info(id)) { (Some(s), Some(e)) => (s, e), _ => return None, }; env_info.gas_limit = transaction.gas.clone(); let mut jdb = self.state_db.lock().journal_db().boxed_clone(); - let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); - let mut state = state.replace_backend(backend); - let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false }; - let res = Executive::new(&mut state, &env_info, &*self.engine).transact(&transaction, options); - - match res { - Err(ExecutionError::Internal(_)) => None, - Err(e) => { - trace!(target: "client", "Proved call failed: {}", e); - Some((Vec::new(), state.drop().1.extract_proof())) - } - Ok(res) => Some((res.output, state.drop().1.extract_proof())), - } + state::prove_transaction( + jdb.as_hashdb_mut(), + header.state_root().clone(), + &transaction, + &*self.engine, + &env_info, + self.factories.clone(), + false, + ) } } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 7d3900f2c..df83d2e4f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -32,7 +32,6 @@ use factory::Factories; use header::{BlockNumber, Header}; use pod_state::*; use rlp::{Rlp, RlpStream}; -use state_db::StateDB; use state::{Backend, State, Substate}; use state::backend::Basic as BasicBackend; use trace::{NoopTracer, NoopVMTracer}; @@ -421,7 +420,7 @@ impl Spec { } /// Ensure that the given state DB has the trie nodes in for the genesis state. - pub fn ensure_db_good(&self, db: StateDB, factories: &Factories) -> Result { + pub fn ensure_db_good(&self, db: T, factories: &Factories) -> Result { if db.as_hashdb().contains(&self.state_root()) { return Ok(db) } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 354da2cc3..f15846b73 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -209,6 +209,43 @@ pub fn check_proof( } } +/// Prove a transaction on the given state. +/// Returns `None` when the transacion could not be proved, +/// and a proof otherwise. +pub fn prove_transaction( + db: H, + root: H256, + transaction: &SignedTransaction, + engine: &Engine, + env_info: &EnvInfo, + factories: Factories, + virt: bool, +) -> Option<(Bytes, Vec)> { + use self::backend::Proving; + + let backend = Proving::new(db); + let res = State::from_existing( + backend, + root, + engine.account_start_nonce(env_info.number), + factories, + ); + + let mut state = match res { + Ok(state) => state, + Err(_) => return None, + }; + + match state.execute(env_info, engine, transaction, false, virt) { + Err(ExecutionError::Internal(_)) => None, + Err(e) => { + trace!(target: "state", "Proved call failed: {}", e); + Some((Vec::new(), state.drop().1.extract_proof())) + } + Ok(res) => Some((res.output, state.drop().1.extract_proof())), + } +} + /// Representation of the entire state of all accounts in the system. /// /// `State` can work together with `StateDB` to share account cache. From 6ab0fc4e147809dbba4247410d637c581f9748ac Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sat, 19 Aug 2017 17:07:31 +0900 Subject: [PATCH 04/14] epoch transitions in the header chain DB --- ethcore/light/src/client/header_chain.rs | 357 ++++++++++++++++++++--- ethcore/light/src/client/mod.rs | 29 +- 2 files changed, 342 insertions(+), 44 deletions(-) diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index d6ac109c7..1f97e281b 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -18,11 +18,12 @@ //! //! Unlike a full node's `BlockChain` this doesn't store much in the database. //! It stores candidates for the last 2048-4096 blocks as well as CHT roots for -//! historical blocks all the way to the genesis. +//! historical blocks all the way to the genesis. If the engine makes use +//! of epoch transitions, those are stored as well. //! //! This is separate from the `BlockChain` for two reasons: //! - It stores only headers (and a pruned subset of them) -//! - To allow for flexibility in the database layout once that's incorporated. +//! - To allow for flexibility in the database layout.. use std::collections::BTreeMap; use std::sync::Arc; @@ -30,13 +31,18 @@ use std::sync::Arc; use cht; use ethcore::block_status::BlockStatus; -use ethcore::error::BlockError; +use ethcore::error::{BlockImportError, BlockError}; use ethcore::encoded; use ethcore::header::Header; use ethcore::ids::BlockId; +use ethcore::spec::Spec; +use ethcore::engines::epoch::{ + Transition as EpochTransition, + PendingTransition as PendingEpochTransition +}; use rlp::{Encodable, Decodable, DecoderError, RlpStream, Rlp, UntrustedRlp}; -use util::{H256, U256, HeapSizeOf, RwLock}; +use util::{H256, H256FastMap, H264, U256, HeapSizeOf, RwLock}; use util::kvdb::{DBTransaction, KeyValueDB}; use cache::Cache; @@ -52,6 +58,9 @@ const HISTORY: u64 = 2048; /// The best block key. Maps to an RLP list: [best_era, last_era] const CURRENT_KEY: &'static [u8] = &*b"best_and_latest"; +/// Key storing the last canonical epoch transition. +const LAST_CANONICAL_TRANSITION: &'static [u8] = &*b"canonical_transition"; + /// Information about a block. #[derive(Debug, Clone)] pub struct BlockDescriptor { @@ -99,7 +108,6 @@ impl Encodable for Entry { impl Decodable for Entry { fn decode(rlp: &UntrustedRlp) -> Result { - let mut candidates = SmallVec::<[Candidate; 3]>::new(); for item in rlp.iter() { @@ -129,16 +137,110 @@ fn era_key(number: u64) -> String { format!("candidates_{}", number) } +fn pending_transition_key(block_hash: H256) -> H264 { + const LEADING: u8 = 1; + + let mut key = H264::default(); + + key[0] = LEADING; + key.0[1..].copy_from_slice(&block_hash.0[..]); + + key +} + +fn transition_key(block_hash: H256) -> H264 { + const LEADING: u8 = 2; + + let mut key = H264::default(); + + key[0] = LEADING; + key.0[1..].copy_from_slice(&block_hash.0[..]); + + key +} + +// encode last canonical transition entry: header and proof. +fn encode_canonical_transition(header: &Header, proof: &[u8]) -> Vec { + let mut stream = RlpStream::new_list(2); + stream.append(header).append(&proof); + stream.out() +} + +// decode last canonical transition entry. +fn decode_canonical_transition(t: &[u8]) -> Result<(Header, &[u8]), DecoderError> { + let rlp = UntrustedRlp::new(t); + + Ok((rlp.val_at(0)?, rlp.at(1)?.data()?)) +} + /// Pending changes from `insert` to be applied after the database write has finished. pub struct PendingChanges { best_block: Option, // new best block. } +// initialize genesis epoch data, using in-memory database for +// constructor. +// TODO: move to `Spec`? +fn genesis_epoch_data(spec: &Spec, genesis: &Header) -> Result, String> { + use ethcore::state::backend::Basic as BasicBackend; + use ethcore::transaction::{Action, Transaction}; + use util::{journaldb, kvdb, Address}; + + let factories = Default::default(); + let mut db = journaldb::new( + Arc::new(kvdb::in_memory(0)), + journaldb::Algorithm::Archive, + None, + ); + + spec.ensure_db_good(BasicBackend(db.as_hashdb_mut()), &factories) + .map_err(|e| format!("Unable to initialize genesis state: {}", e))?; + + let call = |a, d| { + let mut db = db.boxed_clone(); + let env_info = ::evm::EnvInfo { + number: 0, + author: *genesis.author(), + timestamp: genesis.timestamp(), + difficulty: *genesis.difficulty(), + gas_limit: *genesis.gas_limit(), + last_hashes: Arc::new(Vec::new()), + gas_used: 0.into() + }; + + let from = Address::default(); + let tx = Transaction { + nonce: spec.engine.account_start_nonce(0), + action: Action::Call(a), + gas: U256::from(50_000_000), // TODO: share with client. + gas_price: U256::default(), + value: U256::default(), + data: d, + }.fake_sign(from); + + let res = ::ethcore::state::prove_transaction( + db.as_hashdb_mut(), + *genesis.state_root(), + &tx, + &*spec.engine, + &env_info, + factories.clone(), + true, + ); + + res.map(|(out, proof)| (out, proof.into_iter().map(|x| x.into_vec()).collect())) + .ok_or_else(|| "Failed to prove call: insufficient state".into()) + }; + + spec.engine.genesis_epoch_data(&genesis, &call) +} + /// Header chain. See module docs for more details. pub struct HeaderChain { genesis_header: encoded::Header, // special-case the genesis. candidates: RwLock>, best_block: RwLock, + live_epoch_proofs: RwLock>, db: Arc, col: Option, cache: Arc>, @@ -146,9 +248,15 @@ pub struct HeaderChain { impl HeaderChain { /// Create a new header chain given this genesis block and database to read from. - pub fn new(db: Arc, col: Option, genesis: &[u8], cache: Arc>) -> Result { - use ethcore::views::HeaderView; + pub fn new( + db: Arc, + col: Option, + spec: &Spec, + cache: Arc>, + ) -> Result { + let mut live_epoch_proofs = ::std::collections::HashMap::default(); + let genesis = ::rlp::encode(&spec.genesis_header()).into_vec(); let chain = if let Some(current) = db.get(col, CURRENT_KEY)? { let (best_number, highest_number) = { let rlp = Rlp::new(¤t); @@ -158,12 +266,24 @@ impl HeaderChain { let mut cur_number = highest_number; let mut candidates = BTreeMap::new(); - // load all era entries and referenced headers within them. + // load all era entries, referenced headers within them, + // and live epoch proofs. while let Some(entry) = db.get(col, era_key(cur_number).as_bytes())? { let entry: Entry = ::rlp::decode(&entry); trace!(target: "chain", "loaded header chain entry for era {} with {} candidates", cur_number, entry.candidates.len()); + for c in &entry.candidates { + let key = transition_key(c.hash); + + if let Some(proof) = db.get(col, &*key)? { + live_epoch_proofs.insert(c.hash, EpochTransition { + block_hash: c.hash, + block_number: cur_number, + proof: proof.into_vec(), + }); + } + } candidates.insert(cur_number, entry); cur_number -= 1; @@ -185,23 +305,34 @@ impl HeaderChain { }; HeaderChain { - genesis_header: encoded::Header::new(genesis.to_owned()), + genesis_header: encoded::Header::new(genesis), best_block: RwLock::new(best_block), candidates: RwLock::new(candidates), + live_epoch_proofs: RwLock::new(live_epoch_proofs), db: db, col: col, cache: cache, } } else { - let g_view = HeaderView::new(genesis); + let decoded_header: Header = ::rlp::decode(&genesis); + let genesis_data = genesis_epoch_data(spec, &decoded_header)?; + + { + let mut batch = db.transaction(); + let data = encode_canonical_transition(&decoded_header, &genesis_data); + batch.put_vec(col, LAST_CANONICAL_TRANSITION, data); + db.write(batch)?; + } + HeaderChain { - genesis_header: encoded::Header::new(genesis.to_owned()), + genesis_header: encoded::Header::new(genesis), best_block: RwLock::new(BlockDescriptor { - hash: g_view.hash(), + hash: decoded_header.hash(), number: 0, - total_difficulty: g_view.difficulty(), + total_difficulty: *decoded_header.difficulty(), }), candidates: RwLock::new(BTreeMap::new()), + live_epoch_proofs: RwLock::new(live_epoch_proofs), db: db, col: col, cache: cache, @@ -216,10 +347,24 @@ impl HeaderChain { /// This blindly trusts that the data given to it is sensible. /// Returns a set of pending changes to be applied with `apply_pending` /// before the next call to insert and after the transaction has been written. - pub fn insert(&self, transaction: &mut DBTransaction, header: Header) -> Result { + /// + /// If the block is an epoch transition, provide the transition along with + /// the header. + pub fn insert( + &self, + transaction: &mut DBTransaction, + header: Header, + transition_proof: Option>, + ) -> Result { let hash = header.hash(); let number = header.number(); let parent_hash = *header.parent_hash(); + let transition = transition_proof.map(|proof| EpochTransition { + block_hash: hash, + block_number: number, + proof: proof, + }); + let mut pending = PendingChanges { best_block: None, }; @@ -235,7 +380,8 @@ impl HeaderChain { candidates.get(&(number - 1)) .and_then(|entry| entry.candidates.iter().find(|c| c.hash == parent_hash)) .map(|c| c.total_difficulty) - .ok_or_else(|| BlockError::UnknownParent(parent_hash))? + .ok_or_else(|| BlockError::UnknownParent(parent_hash)) + .map_err(BlockImportError::Block)? }; let total_difficulty = parent_td + *header.difficulty(); @@ -260,6 +406,11 @@ impl HeaderChain { transaction.put(self.col, era_key(number).as_bytes(), &::rlp::encode(&*cur_era)) } + if let Some(transition) = transition { + transaction.put(self.col, &*transition_key(hash), &transition.proof); + self.live_epoch_proofs.write().insert(hash, transition); + } + let raw = ::rlp::encode(&header); transaction.put(self.col, &hash[..], &*raw); @@ -314,8 +465,10 @@ impl HeaderChain { let cht_num = cht::block_to_cht_number(earliest_era) .expect("fails only for number == 0; genesis never imported; qed"); + let mut last_canonical_transition = None; let cht_root = { let mut i = earliest_era; + let mut live_epoch_proofs = self.live_epoch_proofs.write(); // iterable function which removes the candidates as it goes // along. this will only be called until the CHT is complete. @@ -326,7 +479,25 @@ impl HeaderChain { i += 1; + // prune old blocks and epoch proofs. for ancient in &era_entry.candidates { + let maybe_transition = live_epoch_proofs.remove(&ancient.hash); + if let Some(epoch_transition) = maybe_transition { + transaction.delete(self.col, &*transition_key(ancient.hash)); + + if ancient.hash == era_entry.canonical_hash { + last_canonical_transition = match self.db.get(self.col, &ancient.hash) { + Err(e) => { + warn!(target: "chain", "Error reading from DB: {}\n + ", e); + None + } + Ok(None) => panic!("stored candidates always have corresponding headers; qed"), + Ok(Some(header)) => Some((epoch_transition, ::rlp::decode(&header))), + }; + } + } + transaction.delete(self.col, &ancient.hash); } @@ -340,6 +511,12 @@ impl HeaderChain { // write the CHT root to the database. debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root); transaction.put(self.col, cht_key(cht_num).as_bytes(), &::rlp::encode(&cht_root)); + + // update the last canonical transition proof + if let Some((epoch_transition, header)) = last_canonical_transition { + let x = encode_canonical_transition(&header, &epoch_transition.proof); + transaction.put_vec(self.col, LAST_CANONICAL_TRANSITION, x); + } } } @@ -365,7 +542,7 @@ impl HeaderChain { /// will be returned. pub fn block_hash(&self, id: BlockId) -> Option { match id { - BlockId::Earliest => Some(self.genesis_hash()), + BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_hash()), BlockId::Hash(hash) => Some(hash), BlockId::Number(num) => { if self.best_block.read().number < num { return None } @@ -516,6 +693,56 @@ impl HeaderChain { false => BlockStatus::Unknown, } } + + /// Insert a pending transition. + pub fn insert_pending_transition(&self, batch: &mut DBTransaction, hash: H256, t: PendingEpochTransition) { + let key = pending_transition_key(hash); + batch.put(self.col, &*key, &*::rlp::encode(&t)); + } + + /// Get pending transition for a specific block hash. + pub fn pending_transition(&self, hash: H256) -> Option { + let key = pending_transition_key(hash); + match self.db.get(self.col, &*key) { + Ok(val) => val.map(|x| ::rlp::decode(&x)), + Err(e) => { + warn!(target: "chain", "Error reading from database: {}", e); + None + } + } + } + + /// Get the transition to the epoch the given parent hash is part of + /// or transitions to. + /// This will give the epoch that any children of this parent belong to. + /// + /// The header corresponding the the parent hash must be stored already. + pub fn epoch_transition_for(&self, parent_hash: H256) -> Option<(Header, Vec)> { + // slow path: loop back block by block + let live_proofs = self.live_epoch_proofs.read(); + + for hdr in self.ancestry_iter(BlockId::Hash(parent_hash)) { + if let Some(transition) = live_proofs.get(&hdr.hash()).cloned() { + return Some((hdr.decode(), transition.proof)) + } + } + + // any blocks left must be descendants of the last canonical transition block. + match self.db.get(self.col, LAST_CANONICAL_TRANSITION) { + Ok(x) => { + let x = x.expect("last canonical transition always instantiated; qed"); + + let (hdr, proof) = decode_canonical_transition(&x) + .expect("last canonical transition always encoded correctly; qed"); + + Some((hdr, proof.to_vec())) + } + Err(e) => { + warn!("Error reading from DB: {}", e); + None + } + } + } } impl HeapSizeOf for HeaderChain { @@ -568,7 +795,7 @@ mod tests { let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache).unwrap(); let mut parent_hash = genesis_header.hash(); let mut rolling_timestamp = genesis_header.timestamp(); @@ -581,7 +808,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -601,7 +828,7 @@ mod tests { let db = make_db(); let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache).unwrap(); let mut parent_hash = genesis_header.hash(); let mut rolling_timestamp = genesis_header.timestamp(); @@ -614,7 +841,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -633,7 +860,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -657,7 +884,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -680,12 +907,10 @@ mod tests { #[test] fn earliest_is_latest() { let spec = Spec::new_test(); - let genesis_header = spec.genesis_header(); let db = make_db(); let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache).unwrap(); - + let chain = HeaderChain::new(db.clone(), None, &spec, cache).unwrap(); assert!(chain.block_header(BlockId::Earliest).is_some()); assert!(chain.block_header(BlockId::Latest).is_some()); @@ -700,7 +925,7 @@ mod tests { let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); { - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone()).unwrap(); let mut parent_hash = genesis_header.hash(); let mut rolling_timestamp = genesis_header.timestamp(); for i in 1..10000 { @@ -712,7 +937,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -720,7 +945,7 @@ mod tests { } } - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone()).unwrap(); assert!(chain.block_header(BlockId::Number(10)).is_none()); assert!(chain.block_header(BlockId::Number(9000)).is_some()); assert!(chain.cht_root(2).is_some()); @@ -736,7 +961,7 @@ mod tests { let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); { - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone()).unwrap(); let mut parent_hash = genesis_header.hash(); let mut rolling_timestamp = genesis_header.timestamp(); @@ -750,7 +975,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -767,7 +992,7 @@ mod tests { parent_hash = header.hash(); let mut tx = db.transaction(); - let pending = chain.insert(&mut tx, header).unwrap(); + let pending = chain.insert(&mut tx, header, None).unwrap(); db.write(tx).unwrap(); chain.apply_pending(pending); @@ -778,7 +1003,7 @@ mod tests { } // after restoration, non-canonical eras should still be loaded. - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone()).unwrap(); assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10); assert!(chain.candidates.read().get(&100).is_some()) } @@ -790,10 +1015,76 @@ mod tests { let db = make_db(); let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header), cache.clone()).unwrap(); + let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone()).unwrap(); assert!(chain.block_header(BlockId::Earliest).is_some()); assert!(chain.block_header(BlockId::Number(0)).is_some()); assert!(chain.block_header(BlockId::Hash(genesis_header.hash())).is_some()); } + + #[test] + fn epoch_transitions_available_after_cht() { + let spec = Spec::new_test(); + let genesis_header = spec.genesis_header(); + let db = make_db(); + let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); + + let chain = HeaderChain::new(db.clone(), None, &spec, cache).unwrap(); + + let mut parent_hash = genesis_header.hash(); + let mut rolling_timestamp = genesis_header.timestamp(); + for i in 1..6 { + let mut header = Header::new(); + header.set_parent_hash(parent_hash); + header.set_number(i); + header.set_timestamp(rolling_timestamp); + header.set_difficulty(*genesis_header.difficulty() * i.into()); + parent_hash = header.hash(); + + let mut tx = db.transaction(); + let epoch_proof = if i == 3 { + Some(vec![1, 2, 3, 4]) + } else { + None + }; + + let pending = chain.insert(&mut tx, header, epoch_proof).unwrap(); + db.write(tx).unwrap(); + chain.apply_pending(pending); + + rolling_timestamp += 10; + } + + // these 3 should end up falling back to the genesis epoch proof in DB + for i in 0..3 { + let hash = chain.block_hash(BlockId::Number(i)).unwrap(); + assert_eq!(chain.epoch_transition_for(hash).unwrap().1, Vec::::new()); + } + + // these are live. + for i in 3..6 { + let hash = chain.block_hash(BlockId::Number(i)).unwrap(); + assert_eq!(chain.epoch_transition_for(hash).unwrap().1, vec![1, 2, 3, 4]); + } + + for i in 6..10000 { + let mut header = Header::new(); + header.set_parent_hash(parent_hash); + header.set_number(i); + header.set_timestamp(rolling_timestamp); + header.set_difficulty(*genesis_header.difficulty() * i.into()); + parent_hash = header.hash(); + + let mut tx = db.transaction(); + let pending = chain.insert(&mut tx, header, None).unwrap(); + db.write(tx).unwrap(); + chain.apply_pending(pending); + + rolling_timestamp += 10; + } + + // no live blocks have associated epoch proofs -- make sure we aren't leaking memory. + assert!(chain.live_epoch_proofs.read().is_empty()); + assert_eq!(chain.epoch_transition_for(parent_hash).unwrap().1, vec![1, 2, 3, 4]); + } } diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index b3d2dff4f..a41aa551d 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -20,7 +20,7 @@ use std::sync::{Weak, Arc}; use ethcore::block_status::BlockStatus; use ethcore::client::{ClientReport, EnvInfo}; -use ethcore::engines::{Engine, EpochChange, Proof, Unsure}; +use ethcore::engines::{epoch, Engine, EpochChange, Proof, Unsure}; use ethcore::error::BlockImportError; use ethcore::ids::BlockId; use ethcore::header::Header; @@ -182,12 +182,10 @@ impl Client { io_channel: IoChannel, cache: Arc> ) -> Result { - let gh = ::rlp::encode(&spec.genesis_header()); - Ok(Client { queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, config.check_seal), engine: spec.engine.clone(), - chain: HeaderChain::new(db.clone(), chain_col, &gh, cache)?, + chain: HeaderChain::new(db.clone(), chain_col, &spec, cache)?, report: RwLock::new(ClientReport::default()), import_lock: Mutex::new(()), db: db, @@ -320,29 +318,34 @@ impl Client { continue } - let _write_proof_result = match self.check_epoch_signal(&verified_header) { + let write_proof_result = match self.check_epoch_signal(&verified_header) { Ok(Some(proof)) => self.write_pending_proof(&verified_header, proof), Ok(None) => Ok(()), Err(e) => panic!("Unable to fetch epoch transition proof: {:?}", e), }; - // TODO: check epoch end. + if let Err(e) = write_proof_result { + warn!(target: "client", "Error writing pending transition proof to DB: {:?} \ + The node may not be able to synchronize further.", e); + } let mut tx = self.db.transaction(); - let pending = match self.chain.insert(&mut tx, verified_header) { + let pending = match self.chain.insert(&mut tx, verified_header, None) { Ok(pending) => { good.push(hash); self.report.write().blocks_imported += 1; pending } Err(e) => { - debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e); + debug!(target: "client", "Error importing header {:?}: {:?}", (num, hash), e); bad.push(hash); continue; } }; + // TODO: check epoch end and verify under epoch verifier. + self.db.write_buffered(tx); self.chain.apply_pending(pending); } @@ -498,7 +501,7 @@ impl Client { // attempts to fetch the epoch proof from the network until successful. fn write_pending_proof(&self, header: &Header, proof: Proof) -> Result<(), T::Error> { - let _proof = match proof { + let proof = match proof { Proof::Known(known) => known, Proof::WithState(state_dependent) => { loop { @@ -512,8 +515,12 @@ impl Client { } }; - // TODO: actually write it - unimplemented!() + let mut batch = self.db.transaction(); + self.chain.insert_pending_transition(&mut batch, header.hash(), epoch::PendingTransition { + proof: proof, + }); + self.db.write_buffered(batch); + Ok(()) } } From 2985561012a30a26cfbbea2da09ee80b3feac1dd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 21 Aug 2017 18:31:25 +0900 Subject: [PATCH 05/14] detect epoch changes in light client --- ethcore/light/src/client/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index a41aa551d..c13d6ba91 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -330,8 +330,14 @@ impl Client { The node may not be able to synchronize further.", e); } + let epoch_proof = self.engine.is_epoch_end( + &verified_header, + &|h| self.chain.block_header(BlockId::Hash(h)).map(|hdr| hdr.decode()), + &|h| self.chain.pending_transition(h), + ); + let mut tx = self.db.transaction(); - let pending = match self.chain.insert(&mut tx, verified_header, None) { + let pending = match self.chain.insert(&mut tx, verified_header, epoch_proof) { Ok(pending) => { good.push(hash); self.report.write().blocks_imported += 1; @@ -344,8 +350,6 @@ impl Client { } }; - // TODO: check epoch end and verify under epoch verifier. - self.db.write_buffered(tx); self.chain.apply_pending(pending); } From 9ae2ed39ec555bd30054436bc947d694eaeffd4a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 22 Aug 2017 16:00:44 +0800 Subject: [PATCH 06/14] only use engineclient trait for engines, separated from client trait --- ethcore/src/client/client.rs | 20 +++++++++++++++-- ethcore/src/client/test_client.rs | 20 +++++++++++++++-- ethcore/src/client/traits.rs | 13 ++++++++++- ethcore/src/engines/authority_round/mod.rs | 6 ++--- ethcore/src/engines/basic_authority.rs | 6 ++--- ethcore/src/engines/mod.rs | 4 ++-- ethcore/src/engines/tendermint/mod.rs | 12 +++++----- ethcore/src/engines/validator_set/contract.rs | 12 +++++----- ethcore/src/engines/validator_set/mod.rs | 4 ++-- ethcore/src/engines/validator_set/multi.rs | 22 +++++++++---------- .../engines/validator_set/safe_contract.rs | 22 +++++++++---------- ethcore/src/service.rs | 2 +- .../src/snapshot/tests/proof_of_authority.rs | 2 +- 13 files changed, 93 insertions(+), 52 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 2695ad84a..e3c7be66f 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -37,7 +37,7 @@ use client::ancient_import::AncientVerifier; use client::Error as ClientError; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, - MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode, + MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, PruningInfo, ProvingBlockChainClient, }; use encoded; @@ -1852,7 +1852,7 @@ impl MiningBlockChainClient for Client { } } -impl EngineClient for Client { +impl super::traits::EngineClient for Client { fn update_sealing(&self) { self.miner.update_sealing(self) } @@ -1870,6 +1870,22 @@ impl EngineClient for Client { fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition> { self.chain.read().epoch_transition_for(parent_hash) } + + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result { + BlockChainClient::call_contract(self, id, address, data) + } + + fn transact_contract(&self, address: Address, data: Bytes) -> Result { + BlockChainClient::transact_contract(self, address, data) + } + + fn block_number(&self, id: BlockId) -> Option { + BlockChainClient::block_number(self, id) + } } impl ProvingBlockChainClient for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 5dfdf5c25..52e89fd0c 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -25,7 +25,7 @@ use devtools::*; use transaction::{Transaction, LocalizedTransaction, PendingTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; use client::{ - BlockChainClient, MiningBlockChainClient, EngineClient, BlockChainInfo, BlockStatus, BlockId, + BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, ProvingBlockChainClient, }; @@ -780,7 +780,7 @@ impl ProvingBlockChainClient for TestBlockChainClient { } } -impl EngineClient for TestBlockChainClient { +impl super::traits::EngineClient for TestBlockChainClient { fn update_sealing(&self) { self.miner.update_sealing(self) } @@ -796,4 +796,20 @@ impl EngineClient for TestBlockChainClient { fn epoch_transition_for(&self, _block_hash: H256) -> Option<::engines::EpochTransition> { None } + + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result { + BlockChainClient::call_contract(self, id, address, data) + } + + fn transact_contract(&self, address: Address, data: Bytes) -> Result { + BlockChainClient::transact_contract(self, address, data) + } + + fn block_number(&self, id: BlockId) -> Option { + BlockChainClient::block_number(self, id) + } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index aca369b21..294648e8e 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -307,7 +307,7 @@ pub trait MiningBlockChainClient: BlockChainClient { } /// Client facilities used by internally sealing Engines. -pub trait EngineClient: MiningBlockChainClient { +pub trait EngineClient: Sync + Send { /// Make a new block and seal it. fn update_sealing(&self); @@ -323,6 +323,17 @@ pub trait EngineClient: MiningBlockChainClient { /// /// The block corresponding the the parent hash must be stored already. fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition>; + + /// Get block chain info. + fn chain_info(&self) -> BlockChainInfo; + + /// Like `call`, but with various defaults. Designed to be used for calling contracts. + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; + + /// Import a transaction: used for misbehaviour reporting. + fn transact_contract(&self, address: Address, data: Bytes) -> Result; + + fn block_number(&self, id: BlockId) -> Option; } /// Extended client interface for providing proofs of the state. diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 0dc33d342..a2e75ee4a 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -23,7 +23,7 @@ use std::time::{UNIX_EPOCH, Duration}; use account_provider::AccountProvider; use block::*; use builtin::Builtin; -use client::{Client, EngineClient}; +use client::EngineClient; use engines::{Call, Engine, Seal, EngineError, ConstructedVerifier}; use error::{Error, TransactionError, BlockError}; use ethjson; @@ -829,9 +829,9 @@ impl Engine for AuthorityRound { Ok(()) } - fn register_client(&self, client: Weak) { + fn register_client(&self, client: Weak) { *self.client.write() = Some(client.clone()); - self.validators.register_contract(client); + self.validators.register_client(client); } fn set_signer(&self, ap: Arc, address: Address, password: String) { diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 68759131d..83a0c0ca3 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -28,7 +28,7 @@ use error::{BlockError, Error}; use evm::Schedule; use ethjson; use header::{Header, BlockNumber}; -use client::Client; +use client::EngineClient; use super::signer::EngineSigner; use super::validator_set::{ValidatorSet, SimpleList, new_validator_set}; @@ -235,8 +235,8 @@ impl Engine for BasicAuthority { } } - fn register_client(&self, client: Weak) { - self.validators.register_contract(client); + fn register_client(&self, client: Weak) { + self.validators.register_client(client); } fn set_signer(&self, ap: Arc, address: Address, password: String) { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 3997b66c3..0324c2651 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -42,7 +42,7 @@ use self::epoch::PendingTransition; use account_provider::AccountProvider; use block::ExecutedBlock; use builtin::Builtin; -use client::Client; +use client::EngineClient; use evm::env_info::{EnvInfo, LastHashes}; use error::Error; use evm::Schedule; @@ -367,7 +367,7 @@ pub trait Engine : Sync + Send { fn sign(&self, _hash: H256) -> Result { unimplemented!() } /// Add Client which can be used for sealing, querying the state and sending messages. - fn register_client(&self, _client: Weak) {} + fn register_client(&self, _client: Weak) {} /// Trigger next step of the consensus engine. fn step(&self) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1a9626fff..4e84f130e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -28,7 +28,7 @@ mod params; use std::sync::Weak; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; -use client::{Client, EngineClient}; +use client::EngineClient; use error::{Error, BlockError}; use header::{Header, BlockNumber}; use builtin::Builtin; @@ -766,13 +766,12 @@ impl Engine for Tendermint { self.to_step(next_step); } - fn register_client(&self, client: Weak) { - use client::BlockChainClient; + fn register_client(&self, client: Weak) { if let Some(c) = client.upgrade() { self.height.store(c.chain_info().best_block_number as usize + 1, AtomicOrdering::SeqCst); } *self.client.write() = Some(client.clone()); - self.validators.register_contract(client); + self.validators.register_client(client); } } @@ -1012,7 +1011,7 @@ mod tests { let client = generate_dummy_client(0); let notify = Arc::new(TestNotify::default()); client.add_notify(notify.clone()); - engine.register_client(Arc::downgrade(&client)); + engine.register_client(Arc::downgrade(&client) as _); let prevote_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); @@ -1030,7 +1029,6 @@ mod tests { fn seal_submission() { use ethkey::{Generator, Random}; use transaction::{Transaction, Action}; - use client::BlockChainClient; let tap = Arc::new(AccountProvider::transient_provider()); // Accounts for signing votes. @@ -1043,7 +1041,7 @@ mod tests { let notify = Arc::new(TestNotify::default()); client.add_notify(notify.clone()); - engine.register_client(Arc::downgrade(&client)); + engine.register_client(Arc::downgrade(&client) as _); let keypair = Random.generate().unwrap(); let transaction = Transaction { diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 46f5c8512..a802db43a 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -23,7 +23,7 @@ use util::*; use futures::Future; use native_contracts::ValidatorReport as Provider; -use client::{Client, BlockChainClient}; +use client::EngineClient; use engines::{Call, Engine}; use header::{Header, BlockNumber}; @@ -34,7 +34,7 @@ use super::safe_contract::ValidatorSafeContract; pub struct ValidatorContract { validators: ValidatorSafeContract, provider: Provider, - client: RwLock>>, // TODO [keorn]: remove + client: RwLock>>, // TODO [keorn]: remove } impl ValidatorContract { @@ -118,8 +118,8 @@ impl ValidatorSet for ValidatorContract { } } - fn register_contract(&self, client: Weak) { - self.validators.register_contract(client.clone()); + fn register_client(&self, client: Weak) { + self.validators.register_client(client.clone()); *self.client.write() = Some(client); } } @@ -143,7 +143,7 @@ mod tests { fn fetches_validators() { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); - vc.register_contract(Arc::downgrade(&client)); + vc.register_client(Arc::downgrade(&client) as _); let last_hash = client.best_block_header().hash(); assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); @@ -154,7 +154,7 @@ mod tests { let tap = Arc::new(AccountProvider::transient_provider()); let v1 = tap.insert_account("1".sha3().into(), "").unwrap(); let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone())); - client.engine().register_client(Arc::downgrade(&client)); + client.engine().register_client(Arc::downgrade(&client) as _); let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap(); // Make sure reporting can be done. diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 56cff365f..06b844ad3 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -27,7 +27,7 @@ use std::sync::Weak; use ids::BlockId; use util::{Bytes, Address, H256}; use ethjson::spec::ValidatorSet as ValidatorSpec; -use client::Client; +use client::EngineClient; use header::{Header, BlockNumber}; #[cfg(test)] @@ -141,5 +141,5 @@ pub trait ValidatorSet: Send + Sync { /// Notifies about benign misbehaviour. fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {} /// Allows blockchain state access. - fn register_contract(&self, _client: Weak) {} + fn register_client(&self, _client: Weak) {} } diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index 5835dbcdb..086f45b98 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -22,7 +22,7 @@ use engines::{Call, Engine}; use util::{Bytes, H256, Address, RwLock}; use ids::BlockId; use header::{BlockNumber, Header}; -use client::{Client, BlockChainClient}; +use client::EngineClient; use super::{SystemCall, ValidatorSet}; type BlockNumberLookup = Box Result + Send + Sync + 'static>; @@ -129,9 +129,9 @@ impl ValidatorSet for Multi { self.correct_set_by_number(set_block).1.report_benign(validator, set_block, block); } - fn register_contract(&self, client: Weak) { + fn register_client(&self, client: Weak) { for set in self.sets.values() { - set.register_contract(client.clone()); + set.register_client(client.clone()); } *self.block_number.write() = Box::new(move |id| client .upgrade() @@ -143,7 +143,7 @@ impl ValidatorSet for Multi { #[cfg(test)] mod tests { use account_provider::AccountProvider; - use client::{BlockChainClient, EngineClient}; + use client::BlockChainClient; use engines::EpochChange; use engines::validator_set::ValidatorSet; use ethkey::Secret; @@ -165,7 +165,7 @@ mod tests { let v0 = tap.insert_account(s0.clone(), "").unwrap(); let v1 = tap.insert_account("1".sha3().into(), "").unwrap(); let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap)); - client.engine().register_client(Arc::downgrade(&client)); + client.engine().register_client(Arc::downgrade(&client) as _); // Make sure txs go through. client.miner().set_gas_floor_target(1_000_000.into()); @@ -173,27 +173,27 @@ mod tests { // Wrong signer for the first block. client.miner().set_engine_signer(v1, "".into()).unwrap(); client.transact_contract(Default::default(), Default::default()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 0); // Right signer for the first block. client.miner().set_engine_signer(v0, "".into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); // This time v0 is wrong. client.transact_contract(Default::default(), Default::default()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); client.miner().set_engine_signer(v1, "".into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 2); // v1 is still good. client.transact_contract(Default::default(), Default::default()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 3); // Check syncing. let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]); - sync_client.engine().register_client(Arc::downgrade(&sync_client)); + sync_client.engine().register_client(Arc::downgrade(&sync_client) as _); for i in 1..4 { sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); } diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index f8171a9b9..065c17ef9 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -25,7 +25,7 @@ use util::cache::MemoryLruCache; use rlp::{UntrustedRlp, RlpStream}; use basic_types::LogBloom; -use client::{Client, BlockChainClient}; +use client::EngineClient; use engines::{Call, Engine}; use header::Header; use ids::BlockId; @@ -72,7 +72,7 @@ pub struct ValidatorSafeContract { pub address: Address, validators: RwLock>, provider: Provider, - client: RwLock>>, // TODO [keorn]: remove + client: RwLock>>, // TODO [keorn]: remove } // first proof is just a state proof call of `getValidators` at header's state. @@ -446,7 +446,7 @@ impl ValidatorSet for ValidatorSafeContract { })) } - fn register_contract(&self, client: Weak) { + fn register_client(&self, client: Weak) { trace!(target: "engine", "Setting up contract caller."); *self.client.write() = Some(client); } @@ -460,7 +460,7 @@ mod tests { use spec::Spec; use account_provider::AccountProvider; use transaction::{Transaction, Action}; - use client::{BlockChainClient, EngineClient}; + use client::BlockChainClient; use ethkey::Secret; use miner::MinerService; use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; @@ -471,7 +471,7 @@ mod tests { fn fetches_validators() { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); - vc.register_contract(Arc::downgrade(&client)); + vc.register_client(Arc::downgrade(&client) as _); let last_hash = client.best_block_header().hash(); assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); @@ -485,7 +485,7 @@ mod tests { let v1 = tap.insert_account("0".sha3().into(), "").unwrap(); let network_id = Spec::new_validator_safe_contract().network_id(); let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap)); - client.engine().register_client(Arc::downgrade(&client)); + client.engine().register_client(Arc::downgrade(&client) as _); let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap(); client.miner().set_engine_signer(v1, "".into()).unwrap(); @@ -499,7 +499,7 @@ mod tests { data: "bfc708a000000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(), }.sign(&s0, Some(network_id)); client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); // Add "1" validator back in. let tx = Transaction { @@ -511,13 +511,13 @@ mod tests { data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(), }.sign(&s0, Some(network_id)); client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); // The transaction is not yet included so still unable to seal. assert_eq!(client.chain_info().best_block_number, 1); // Switch to the validator that is still there. client.miner().set_engine_signer(v0, "".into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 2); // Switch back to the added validator, since the state is updated. client.miner().set_engine_signer(v1, "".into()).unwrap(); @@ -530,13 +530,13 @@ mod tests { data: Vec::new(), }.sign(&s0, Some(network_id)); client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); - client.update_sealing(); + ::client::EngineClient::update_sealing(&*client); // Able to seal again. assert_eq!(client.chain_info().best_block_number, 3); // Check syncing. let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]); - sync_client.engine().register_client(Arc::downgrade(&sync_client)); + sync_client.engine().register_client(Arc::downgrade(&sync_client) as _); for i in 1..4 { sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index e4c3d7519..5bc15d9bb 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -112,7 +112,7 @@ impl ClientService { }); io_service.register_handler(client_io)?; - spec.engine.register_client(Arc::downgrade(&client)); + spec.engine.register_client(Arc::downgrade(&client) as _); let stop_guard = ::devtools::StopGuard::new(); run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share()); diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index a62dbaf10..1d4841a96 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -93,7 +93,7 @@ fn make_chain(accounts: Arc, blocks_beyond: usize, transitions: let mut cur_signers = vec![*RICH_ADDR]; { let engine = client.engine(); - engine.register_client(Arc::downgrade(&client)); + engine.register_client(Arc::downgrade(&client) as _); } { From 7f3e718851166eb75ff8c4f99d302f041844340e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 22 Aug 2017 21:02:40 +0800 Subject: [PATCH 07/14] EngineClient implementation for light client --- ethcore/light/src/client/mod.rs | 51 ++++++++++++++++++++++++----- ethcore/light/src/client/service.rs | 3 ++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index c13d6ba91..e880ef3ba 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -19,11 +19,11 @@ use std::sync::{Weak, Arc}; use ethcore::block_status::BlockStatus; -use ethcore::client::{ClientReport, EnvInfo}; -use ethcore::engines::{epoch, Engine, EpochChange, Proof, Unsure}; -use ethcore::error::BlockImportError; +use ethcore::client::{TransactionImportResult, ClientReport, EnvInfo}; +use ethcore::engines::{epoch, Engine, EpochChange, EpochTransition, Proof, Unsure}; +use ethcore::error::{TransactionError, BlockImportError, Error as EthcoreError}; use ethcore::ids::BlockId; -use ethcore::header::Header; +use ethcore::header::{BlockNumber, Header}; use ethcore::verification::queue::{self, HeaderQueue}; use ethcore::blockchain_info::BlockChainInfo; use ethcore::spec::Spec; @@ -33,7 +33,7 @@ use io::IoChannel; use futures::{IntoFuture, Future}; -use util::{H256, U256, Mutex, RwLock}; +use util::{Address, H256, U256, Mutex, RwLock}; use util::kvdb::{KeyValueDB, CompactionProfile}; use self::fetch::ChainDataFetcher; @@ -131,7 +131,7 @@ pub trait LightChainClient: Send + Sync { fn cht_root(&self, i: usize) -> Option; /// Get the EIP-86 transition block number. - fn eip86_transition(&self) -> u64; + fn eip86_transition(&self) -> BlockNumber; /// Get a report of import activity since the last call. fn report(&self) -> ClientReport; @@ -555,7 +555,7 @@ impl LightChainClient for Client { Box::new(Client::ancestry_iter(self, start)) } - fn signing_network_id(&self) -> Option { + fn signing_network_id(&self) -> Option { Client::signing_network_id(self) } @@ -587,7 +587,7 @@ impl LightChainClient for Client { Client::cht_root(self, i) } - fn eip86_transition(&self) -> u64 { + fn eip86_transition(&self) -> BlockNumber { self.engine().params().eip86_transition } @@ -595,3 +595,38 @@ impl LightChainClient for Client { Client::report(self) } } + +impl ::ethcore::client::EngineClient for Client { + fn update_sealing(&self) { } + fn submit_seal(&self, _block_hash: H256, _seal: Vec>) { } + fn broadcast_consensus_message(&self, _message: Vec) { } + + fn epoch_transition_for(&self, parent_hash: H256) -> Option { + self.chain.epoch_transition_for(parent_hash).map(|(hdr, proof)| EpochTransition { + block_hash: hdr.hash(), + block_number: hdr.number(), + proof: proof, + }) + } + + fn chain_info(&self) -> BlockChainInfo { + Client::chain_info(self) + } + + fn call_contract(&self, _id: BlockId, _address: Address, _data: Vec) -> Result, String> { + Err("Contract calling not supported by light client".into()) + } + + fn transact_contract(&self, _address: Address, _data: Vec) + -> Result + { + // TODO: these are only really used for misbehavior reporting. + // no relevant clients will be running light clients, but maybe + // they could be at some point? + Err(TransactionError::LimitReached.into()) + } + + fn block_number(&self, id: BlockId) -> Option { + self.block_header(id).map(|hdr| hdr.number()) + } +} diff --git a/ethcore/light/src/client/service.rs b/ethcore/light/src/client/service.rs index 59a5d28c6..458db85c8 100644 --- a/ethcore/light/src/client/service.rs +++ b/ethcore/light/src/client/service.rs @@ -85,7 +85,10 @@ impl Service { io_service.channel(), cache, ).map_err(Error::Database)?); + io_service.register_handler(Arc::new(ImportBlocks(client.clone()))).map_err(Error::Io)?; + spec.engine.register_client(Arc::downgrade(&client) as _); + Ok(Service { client: client, io_service: io_service, From 2ff3dff6ea5da71803278e7ad98ee74ccfe851e7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Aug 2017 15:37:35 +0200 Subject: [PATCH 08/14] serve epoch signals over network and check them --- ethcore/light/src/client/fetch.rs | 8 +- ethcore/light/src/client/mod.rs | 13 +- ethcore/light/src/net/load_timer.rs | 1 + ethcore/light/src/net/mod.rs | 13 +- ethcore/light/src/net/request_credits.rs | 13 +- ethcore/light/src/net/request_set.rs | 1 + ethcore/light/src/net/tests/mod.rs | 50 ++++++ ethcore/light/src/on_demand/mod.rs | 2 + ethcore/light/src/on_demand/request.rs | 42 ++++- ethcore/light/src/provider.rs | 13 ++ ethcore/light/src/types/request/mod.rs | 143 ++++++++++++++++++ ethcore/src/client/client.rs | 6 + ethcore/src/client/test_client.rs | 4 + ethcore/src/client/traits.rs | 3 + ethcore/src/engines/mod.rs | 4 +- .../engines/validator_set/safe_contract.rs | 12 +- 16 files changed, 298 insertions(+), 30 deletions(-) diff --git a/ethcore/light/src/client/fetch.rs b/ethcore/light/src/client/fetch.rs index 0f11ef2f4..2e125bb42 100644 --- a/ethcore/light/src/client/fetch.rs +++ b/ethcore/light/src/client/fetch.rs @@ -16,9 +16,13 @@ //! Trait for fetching chain data. +use std::sync::Arc; + +use ethcore::engines::{Engine, StateDependentProof}; use ethcore::header::Header; use ethcore::receipt::Receipt; use futures::future::IntoFuture; +use util::H256; /// Provides full chain data. pub trait ChainDataFetcher: Send + Sync + 'static { @@ -39,7 +43,7 @@ pub trait ChainDataFetcher: Send + Sync + 'static { fn block_receipts(&self, header: &Header) -> Self::Receipts; /// Fetch epoch transition proof at given header. - fn epoch_transition(&self, header: &Header) -> Self::Transition; + fn epoch_transition(&self, hash: H256, engine: Arc, checker: Arc) -> Self::Transition; } /// Fetcher implementation which cannot fetch anything. @@ -63,7 +67,7 @@ impl ChainDataFetcher for Unavailable { Err("fetching block receipts unavailable") } - fn epoch_transition(&self, _header: &Header) -> Self::Body { + fn epoch_transition(&self, _h: H256, _e: Arc, _check: Arc) -> Self::Transition { Err("fetching epoch transition proofs unavailable") } } diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index e880ef3ba..ec15f79a7 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -508,14 +508,11 @@ impl Client { let proof = match proof { Proof::Known(known) => known, Proof::WithState(state_dependent) => { - loop { - let proof = self.fetcher.epoch_transition(header).into_future().wait()?; - match state_dependent.check_proof(&*self.engine, &proof) { - Ok(()) => break proof, - Err(e) => - debug!(target: "client", "Fetched bad epoch transition proof from network: {}", e), - } - } + self.fetcher.epoch_transition( + header.hash(), + self.engine.clone(), + state_dependent + ).into_future().wait()? } }; diff --git a/ethcore/light/src/net/load_timer.rs b/ethcore/light/src/net/load_timer.rs index a169b86d9..2cdbb300f 100644 --- a/ethcore/light/src/net/load_timer.rs +++ b/ethcore/light/src/net/load_timer.rs @@ -62,6 +62,7 @@ fn hardcoded_serve_time(kind: Kind) -> u64 { Kind::Storage => 2_000_000, Kind::Code => 1_500_000, Kind::Execution => 250, // per gas. + Kind::Signal => 500_000, } } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 046dc68bd..2449d4568 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -102,9 +102,8 @@ mod packet { // relay transactions to peers. pub const SEND_TRANSACTIONS: u8 = 0x06; - // request and respond with epoch transition proof - pub const REQUEST_EPOCH_PROOF: u8 = 0x07; - pub const EPOCH_PROOF: u8 = 0x08; + // two packets were previously meant to be reserved for epoch proofs. + // these have since been moved to requests. } // timeouts for different kinds of requests. all values are in milliseconds. @@ -122,6 +121,7 @@ mod timeout { pub const CONTRACT_CODE: i64 = 100; pub const HEADER_PROOF: i64 = 100; pub const TRANSACTION_PROOF: i64 = 1000; // per gas? + pub const EPOCH_SIGNAL: i64 = 200; } /// A request id. @@ -582,12 +582,6 @@ impl LightProtocol { packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), - packet::REQUEST_EPOCH_PROOF | packet::EPOCH_PROOF => { - // ignore these for now, but leave them specified. - debug!(target: "pip", "Ignoring request/response for epoch proof"); - Ok(()) - } - other => { Err(Error::UnrecognizedPacket(other)) } @@ -950,6 +944,7 @@ impl LightProtocol { CompleteRequest::Storage(req) => self.provider.storage_proof(req).map(Response::Storage), CompleteRequest::Code(req) => self.provider.contract_code(req).map(Response::Code), CompleteRequest::Execution(req) => self.provider.transaction_proof(req).map(Response::Execution), + CompleteRequest::Signal(req) => self.provider.epoch_signal(req).map(Response::Signal), } }); diff --git a/ethcore/light/src/net/request_credits.rs b/ethcore/light/src/net/request_credits.rs index 950fa24d8..b1d83aaa6 100644 --- a/ethcore/light/src/net/request_credits.rs +++ b/ethcore/light/src/net/request_credits.rs @@ -91,6 +91,7 @@ pub struct CostTable { code: U256, header_proof: U256, transaction_proof: U256, // cost per gas. + epoch_signal: U256, } impl Default for CostTable { @@ -107,6 +108,7 @@ impl Default for CostTable { code: 20000.into(), header_proof: 15000.into(), transaction_proof: 2.into(), + epoch_signal: 10000.into(), } } } @@ -121,7 +123,7 @@ impl Encodable for CostTable { s.append(cost); } - s.begin_list(10).append(&self.base); + s.begin_list(11).append(&self.base); append_cost(s, &self.headers, request::Kind::Headers); append_cost(s, &self.transaction_index, request::Kind::TransactionIndex); append_cost(s, &self.body, request::Kind::Body); @@ -131,6 +133,7 @@ impl Encodable for CostTable { append_cost(s, &self.code, request::Kind::Code); append_cost(s, &self.header_proof, request::Kind::HeaderProof); append_cost(s, &self.transaction_proof, request::Kind::Execution); + append_cost(s, &self.epoch_signal, request::Kind::Signal); } } @@ -147,6 +150,7 @@ impl Decodable for CostTable { let mut code = None; let mut header_proof = None; let mut transaction_proof = None; + let mut epoch_signal = None; for cost_list in rlp.iter().skip(1) { let cost = cost_list.val_at(1)?; @@ -160,6 +164,7 @@ impl Decodable for CostTable { request::Kind::Code => code = Some(cost), request::Kind::HeaderProof => header_proof = Some(cost), request::Kind::Execution => transaction_proof = Some(cost), + request::Kind::Signal => epoch_signal = Some(cost), } } @@ -176,6 +181,7 @@ impl Decodable for CostTable { code: unwrap_cost(code)?, header_proof: unwrap_cost(header_proof)?, transaction_proof: unwrap_cost(transaction_proof)?, + epoch_signal: unwrap_cost(epoch_signal)?, }) } } @@ -238,6 +244,7 @@ impl FlowParams { code: cost_for_kind(Kind::Code), header_proof: cost_for_kind(Kind::HeaderProof), transaction_proof: cost_for_kind(Kind::Execution), + epoch_signal: cost_for_kind(Kind::Signal), }; FlowParams { @@ -263,7 +270,8 @@ impl FlowParams { storage: free_cost.clone(), code: free_cost.clone(), header_proof: free_cost.clone(), - transaction_proof: free_cost, + transaction_proof: free_cost.clone(), + epoch_signal: free_cost, } } } @@ -293,6 +301,7 @@ impl FlowParams { Request::Storage(_) => self.costs.storage, Request::Code(_) => self.costs.code, Request::Execution(ref req) => self.costs.transaction_proof * req.gas, + Request::Signal(_) => self.costs.epoch_signal, } } diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs index 35182f0bf..43051df4a 100644 --- a/ethcore/light/src/net/request_set.rs +++ b/ethcore/light/src/net/request_set.rs @@ -139,6 +139,7 @@ fn compute_timeout(reqs: &Requests) -> Duration { Request::Storage(_) => timeout::PROOF, Request::Code(_) => timeout::CONTRACT_CODE, Request::Execution(_) => timeout::TRANSACTION_PROOF, + Request::Signal(_) => timeout::EPOCH_SIGNAL, } })) } diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index ef86c08f5..0de79ed00 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -156,6 +156,12 @@ impl Provider for TestProvider { None } + fn epoch_signal(&self, _req: request::CompleteSignalRequest) -> Option { + Some(request::SignalResponse { + signal: vec![1, 2, 3, 4], + }) + } + fn ready_transactions(&self) -> Vec { self.0.client.ready_transactions() } @@ -521,6 +527,50 @@ fn get_contract_code() { proto.handle_packet(&expected, &1, packet::REQUEST, &request_body); } +#[test] +fn epoch_signal() { + let capabilities = capabilities(); + + let (provider, proto) = setup(capabilities.clone()); + let flow_params = proto.flow_params.read().clone(); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, &proto); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let request = Request::Signal(request::IncompleteSignalRequest { + block_hash: H256([1; 32]).into(), + }); + + let requests = encode_single(request.clone()); + let request_body = make_packet(req_id, &requests); + + let response = { + let response = vec![Response::Signal(SignalResponse { + signal: vec![1, 2, 3, 4], + })]; + + let limit = *flow_params.limit(); + let cost = flow_params.compute_cost_multi(requests.requests()); + + println!("limit = {}, cost = {}", limit, cost); + let new_creds = limit - cost; + + let mut response_stream = RlpStream::new_list(3); + response_stream.append(&req_id).append(&new_creds).append_list(&response); + + response_stream.out() + }; + + let expected = Expect::Respond(packet::RESPONSE, response); + proto.handle_packet(&expected, &1, packet::REQUEST, &request_body); +} + #[test] fn proof_of_execution() { let capabilities = capabilities(); diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index 42469725b..6d575a026 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -195,6 +195,8 @@ fn guess_capabilities(requests: &[CheckedRequest]) -> Capabilities { caps.serve_headers = true, CheckedRequest::HeaderByHash(_, _) => caps.serve_headers = true, + CheckedRequest::Signal(_, _) => + caps.serve_headers = true, CheckedRequest::Body(ref req, _) => if let Ok(ref hdr) = req.0.as_ref() { update_since(&mut caps.serve_chain_since, hdr.number()); }, diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index 9587dd8ed..09dd6cc89 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use ethcore::basic_account::BasicAccount; use ethcore::encoded; -use ethcore::engines::Engine; +use ethcore::engines::{Engine, StateDependentProof}; use ethcore::receipt::Receipt; use ethcore::state::{self, ProvedExecution}; use ethcore::transaction::SignedTransaction; @@ -53,6 +53,8 @@ pub enum Request { Code(Code), /// A request for proof of execution. Execution(TransactionProof), + /// A request for epoch change signal. + Signal(Signal), } /// A request argument. @@ -133,6 +135,7 @@ impl_single!(Body, Body, encoded::Block); impl_single!(Account, Account, Option); impl_single!(Code, Code, Bytes); impl_single!(Execution, TransactionProof, super::ExecutionResult); +impl_single!(Signal, Signal, Vec); macro_rules! impl_args { () => { @@ -241,6 +244,7 @@ pub enum CheckedRequest { Account(Account, net_request::IncompleteAccountRequest), Code(Code, net_request::IncompleteCodeRequest), Execution(TransactionProof, net_request::IncompleteExecutionRequest), + Signal(Signal, net_request::IncompleteSignalRequest) } impl From for CheckedRequest { @@ -299,6 +303,12 @@ impl From for CheckedRequest { }; CheckedRequest::Execution(req, net_req) } + Request::Signal(req) => { + let net_req = net_request::IncompleteSignalRequest { + block_hash: req.hash.into(), + }; + CheckedRequest::Signal(req, net_req) + } } } } @@ -316,6 +326,7 @@ impl CheckedRequest { CheckedRequest::Account(_, req) => NetRequest::Account(req), CheckedRequest::Code(_, req) => NetRequest::Code(req), CheckedRequest::Execution(_, req) => NetRequest::Execution(req), + CheckedRequest::Signal(_, req) => NetRequest::Signal(req), } } @@ -443,6 +454,7 @@ macro_rules! match_me { CheckedRequest::Account($check, $req) => $e, CheckedRequest::Code($check, $req) => $e, CheckedRequest::Execution($check, $req) => $e, + CheckedRequest::Signal($check, $req) => $e, } } } @@ -470,6 +482,7 @@ impl IncompleteRequest for CheckedRequest { CheckedRequest::Account(_, ref req) => req.check_outputs(f), CheckedRequest::Code(_, ref req) => req.check_outputs(f), CheckedRequest::Execution(_, ref req) => req.check_outputs(f), + CheckedRequest::Signal(_, ref req) => req.check_outputs(f), } } @@ -490,6 +503,7 @@ impl IncompleteRequest for CheckedRequest { CheckedRequest::Account(_, req) => req.complete().map(CompleteRequest::Account), CheckedRequest::Code(_, req) => req.complete().map(CompleteRequest::Code), CheckedRequest::Execution(_, req) => req.complete().map(CompleteRequest::Execution), + CheckedRequest::Signal(_, req) => req.complete().map(CompleteRequest::Signal), } } @@ -541,6 +555,9 @@ impl net_request::CheckedRequest for CheckedRequest { CheckedRequest::Execution(ref prover, _) => expect!((&NetResponse::Execution(ref res), _) => prover.check_response(cache, &res.items).map(Response::Execution)), + CheckedRequest::Signal(ref prover, _) => + expect!((&NetResponse::Signal(ref res), _) => + prover.check_response(cache, &res.signal).map(Response::Signal)), } } } @@ -564,6 +581,8 @@ pub enum Response { Code(Vec), /// Response to a request for proved execution. Execution(super::ExecutionResult), + /// Response to a request for epoch change signal. + Signal(Vec), } impl net_request::ResponseLike for Response { @@ -847,6 +866,27 @@ impl TransactionProof { } } +/// Request for epoch signal. +/// Provide engine and state-dependent proof checker. +#[derive(Clone)] +pub struct Signal { + /// Block hash and number to fetch proof for. + pub hash: H256, + /// Consensus engine, used to check the proof. + pub engine: Arc, + /// Special checker for the proof. + pub proof_check: Arc, +} + +impl Signal { + /// Check the signal, returning the signal or indicate that it's bad. + pub fn check_response(&self, _: &Mutex<::cache::Cache>, signal: &[u8]) -> Result, Error> { + self.proof_check.check_proof(&*self.engine, signal) + .map(|_| signal.to_owned()) + .map_err(|_| Error::BadProof) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 3632783ca..4050a73df 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -126,6 +126,9 @@ pub trait Provider: Send + Sync { /// Provide a proof-of-execution for the given transaction proof request. /// Returns a vector of all state items necessary to execute the transaction. fn transaction_proof(&self, req: request::CompleteExecutionRequest) -> Option; + + /// Provide epoch signal data at given block hash. This should be just the + fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option; } // Implementation of a light client data provider for a client. @@ -264,6 +267,12 @@ impl Provider for T { fn ready_transactions(&self) -> Vec { BlockChainClient::ready_transactions(self) } + + fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option { + self.epoch_signal(req.block_hash).map(|signal| request::SignalResponse { + signal: signal, + }) + } } /// The light client "provider" implementation. This wraps a `LightClient` and @@ -329,6 +338,10 @@ impl Provider for LightProvider { None } + fn epoch_signal(&self, _req: request::CompleteSignalRequest) -> Option { + None + } + fn ready_transactions(&self) -> Vec { let chain_info = self.chain_info(); self.txqueue.read().ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) diff --git a/ethcore/light/src/types/request/mod.rs b/ethcore/light/src/types/request/mod.rs index ff4a32535..c9863a354 100644 --- a/ethcore/light/src/types/request/mod.rs +++ b/ethcore/light/src/types/request/mod.rs @@ -67,6 +67,11 @@ pub use self::execution::{ Incomplete as IncompleteExecutionRequest, Response as ExecutionResponse, }; +pub use self::epoch_signal::{ + Complete as CompleteSignalRequest, + Incomplete as IncompleteSignalRequest, + Response as SignalResponse, +}; pub use self::builder::{RequestBuilder, Requests}; @@ -261,6 +266,8 @@ pub enum Request { Code(IncompleteCodeRequest), /// A request for proof of execution, Execution(IncompleteExecutionRequest), + /// A request for an epoch signal. + Signal(IncompleteSignalRequest), } /// All request types, in an answerable state. @@ -284,6 +291,8 @@ pub enum CompleteRequest { Code(CompleteCodeRequest), /// A request for proof of execution, Execution(CompleteExecutionRequest), + /// A request for an epoch signal. + Signal(CompleteSignalRequest), } impl CompleteRequest { @@ -299,6 +308,7 @@ impl CompleteRequest { CompleteRequest::Storage(_) => Kind::Storage, CompleteRequest::Code(_) => Kind::Code, CompleteRequest::Execution(_) => Kind::Execution, + CompleteRequest::Signal(_) => Kind::Signal, } } } @@ -316,6 +326,7 @@ impl Request { Request::Storage(_) => Kind::Storage, Request::Code(_) => Kind::Code, Request::Execution(_) => Kind::Execution, + Request::Signal(_) => Kind::Signal, } } } @@ -332,6 +343,7 @@ impl Decodable for Request { Kind::Storage => Ok(Request::Storage(rlp.val_at(1)?)), Kind::Code => Ok(Request::Code(rlp.val_at(1)?)), Kind::Execution => Ok(Request::Execution(rlp.val_at(1)?)), + Kind::Signal => Ok(Request::Signal(rlp.val_at(1)?)), } } } @@ -353,6 +365,7 @@ impl Encodable for Request { Request::Storage(ref req) => s.append(req), Request::Code(ref req) => s.append(req), Request::Execution(ref req) => s.append(req), + Request::Signal(ref req) => s.append(req), }; } } @@ -374,6 +387,7 @@ impl IncompleteRequest for Request { Request::Storage(ref req) => req.check_outputs(f), Request::Code(ref req) => req.check_outputs(f), Request::Execution(ref req) => req.check_outputs(f), + Request::Signal(ref req) => req.check_outputs(f), } } @@ -388,6 +402,7 @@ impl IncompleteRequest for Request { Request::Storage(ref req) => req.note_outputs(f), Request::Code(ref req) => req.note_outputs(f), Request::Execution(ref req) => req.note_outputs(f), + Request::Signal(ref req) => req.note_outputs(f), } } @@ -402,6 +417,7 @@ impl IncompleteRequest for Request { Request::Storage(ref mut req) => req.fill(oracle), Request::Code(ref mut req) => req.fill(oracle), Request::Execution(ref mut req) => req.fill(oracle), + Request::Signal(ref mut req) => req.fill(oracle), } } @@ -416,6 +432,7 @@ impl IncompleteRequest for Request { Request::Storage(req) => req.complete().map(CompleteRequest::Storage), Request::Code(req) => req.complete().map(CompleteRequest::Code), Request::Execution(req) => req.complete().map(CompleteRequest::Execution), + Request::Signal(req) => req.complete().map(CompleteRequest::Signal), } } @@ -430,6 +447,7 @@ impl IncompleteRequest for Request { Request::Storage(ref mut req) => req.adjust_refs(mapping), Request::Code(ref mut req) => req.adjust_refs(mapping), Request::Execution(ref mut req) => req.adjust_refs(mapping), + Request::Signal(ref mut req) => req.adjust_refs(mapping), } } } @@ -471,6 +489,8 @@ pub enum Kind { Code = 7, /// A request for transaction execution + state proof. Execution = 8, + /// A request for epoch transition signal. + Signal = 9, } impl Decodable for Kind { @@ -485,6 +505,7 @@ impl Decodable for Kind { 6 => Ok(Kind::Storage), 7 => Ok(Kind::Code), 8 => Ok(Kind::Execution), + 9 => Ok(Kind::Signal), _ => Err(DecoderError::Custom("Unknown PIP request ID.")), } } @@ -517,6 +538,8 @@ pub enum Response { Code(CodeResponse), /// A response for proof of execution, Execution(ExecutionResponse), + /// A response for epoch change signal. + Signal(SignalResponse), } impl ResponseLike for Response { @@ -532,6 +555,7 @@ impl ResponseLike for Response { Response::Storage(ref res) => res.fill_outputs(f), Response::Code(ref res) => res.fill_outputs(f), Response::Execution(ref res) => res.fill_outputs(f), + Response::Signal(ref res) => res.fill_outputs(f), } } } @@ -549,6 +573,7 @@ impl Response { Response::Storage(_) => Kind::Storage, Response::Code(_) => Kind::Code, Response::Execution(_) => Kind::Execution, + Response::Signal(_) => Kind::Signal, } } } @@ -565,6 +590,7 @@ impl Decodable for Response { Kind::Storage => Ok(Response::Storage(rlp.val_at(1)?)), Kind::Code => Ok(Response::Code(rlp.val_at(1)?)), Kind::Execution => Ok(Response::Execution(rlp.val_at(1)?)), + Kind::Signal => Ok(Response::Signal(rlp.val_at(1)?)), } } } @@ -586,6 +612,7 @@ impl Encodable for Response { Response::Storage(ref res) => s.append(res), Response::Code(ref res) => s.append(res), Response::Execution(ref res) => s.append(res), + Response::Signal(ref res) => s.append(res), }; } } @@ -1756,6 +1783,104 @@ pub mod execution { } } +/// A request for epoch signal data. +pub mod epoch_signal { + use super::{Field, NoSuchOutput, OutputKind, Output}; + use rlp::{Encodable, Decodable, DecoderError, RlpStream, UntrustedRlp}; + use util::{Bytes, H256}; + + /// Potentially incomplete epoch signal request. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Incomplete { + /// The block hash to request the signal for. + pub block_hash: Field, + } + + impl Decodable for Incomplete { + fn decode(rlp: &UntrustedRlp) -> Result { + Ok(Incomplete { + block_hash: rlp.val_at(0)?, + }) + } + } + + impl Encodable for Incomplete { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(1).append(&self.block_hash); + } + } + + impl super::IncompleteRequest for Incomplete { + type Complete = Complete; + type Response = Response; + + fn check_outputs(&self, mut f: F) -> Result<(), NoSuchOutput> + where F: FnMut(usize, usize, OutputKind) -> Result<(), NoSuchOutput> + { + if let Field::BackReference(req, idx) = self.block_hash { + f(req, idx, OutputKind::Hash)?; + } + + Ok(()) + } + + fn note_outputs(&self, _: F) where F: FnMut(usize, OutputKind) {} + + fn fill(&mut self, oracle: F) where F: Fn(usize, usize) -> Result { + if let Field::BackReference(req, idx) = self.block_hash { + self.block_hash = match oracle(req, idx) { + Ok(Output::Hash(block_hash)) => Field::Scalar(block_hash.into()), + _ => Field::BackReference(req, idx), + } + } + } + + fn complete(self) -> Result { + Ok(Complete { + block_hash: self.block_hash.into_scalar()?, + }) + } + + fn adjust_refs(&mut self, mut mapping: F) where F: FnMut(usize) -> usize { + self.block_hash.adjust_req(&mut mapping); + } + } + + /// A complete request. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Complete { + /// The block hash to request the epoch signal for. + pub block_hash: H256, + } + + /// The output of a request for an epoch signal. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Response { + /// The requested epoch signal. + pub signal: Bytes, + } + + impl super::ResponseLike for Response { + /// Fill reusable outputs by providing them to the function. + fn fill_outputs(&self, _: F) where F: FnMut(usize, Output) {} + } + + impl Decodable for Response { + fn decode(rlp: &UntrustedRlp) -> Result { + + Ok(Response { + signal: rlp.as_val()?, + }) + } + } + + impl Encodable for Response { + fn rlp_append(&self, s: &mut RlpStream) { + s.append(&self.signal); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -2044,4 +2169,22 @@ mod tests { let raw = ::rlp::encode_list(&reqs); assert_eq!(::rlp::decode_list::(&raw), reqs); } + + #[test] + fn epoch_signal_roundtrip() { + let req = IncompleteSignalRequest { + block_hash: Field::Scalar(Default::default()), + }; + + let full_req = Request::Signal(req.clone()); + let res = SignalResponse { + signal: vec![1, 2, 3, 4, 5, 6, 7, 6, 5, 4], + }; + let full_res = Response::Signal(res.clone()); + + check_roundtrip(req); + check_roundtrip(full_req); + check_roundtrip(res); + check_roundtrip(full_res); + } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e3c7be66f..dafef82f6 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1918,6 +1918,12 @@ impl ProvingBlockChainClient for Client { false, ) } + + fn epoch_signal(&self, hash: H256) -> Option> { + // pending transitions are never deleted, and do not contain + // finality proofs by definition. + self.chain.read().get_pending_transition(hash).map(|pending| pending.proof) + } } impl Drop for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 52e89fd0c..1d30c5800 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -778,6 +778,10 @@ impl ProvingBlockChainClient for TestBlockChainClient { fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option<(Bytes, Vec)> { None } + + fn epoch_signal(&self, _: H256) -> Option> { + None + } } impl super::traits::EngineClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 294648e8e..47cb51e3f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -353,4 +353,7 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Returns the output of the call and a vector of database items necessary /// to reproduce it. fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec)>; + + /// Get an epoch change signal by block hash. + fn epoch_signal(&self, hash: H256) -> Option>; } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 0324c2651..fc7a7186e 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -121,7 +121,7 @@ pub type Headers<'a> = Fn(H256) -> Option
+ 'a; pub type PendingTransitionStore<'a> = Fn(H256) -> Option + 'a; /// Proof dependent on state. -pub trait StateDependentProof { +pub trait StateDependentProof: Send + Sync { /// Generate a proof, given the state. fn generate_proof(&self, caller: &Call) -> Result, String>; /// Check a proof generated elsewhere (potentially by a peer). @@ -135,7 +135,7 @@ pub enum Proof { /// Known proof (extracted from signal) Known(Vec), /// State dependent proof. - WithState(Box), + WithState(Arc), } /// Generated epoch verifier. diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 065c17ef9..9c08ab963 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -47,19 +47,19 @@ lazy_static! { // state-dependent proofs for the safe contract: // only "first" proofs are such. struct StateProof { - header: Header, + header: Mutex
, provider: Provider, } impl ::engines::StateDependentProof for StateProof { fn generate_proof(&self, caller: &Call) -> Result, String> { - prove_initial(&self.provider, &self.header, caller) + prove_initial(&self.provider, &*self.header.lock(), caller) } fn check_proof(&self, engine: &Engine, proof: &[u8]) -> Result<(), String> { let (header, state_items) = decode_first_proof(&UntrustedRlp::new(proof)) .map_err(|e| format!("proof incorrectly encoded: {}", e))?; - if header != self.header { + if &header != &*self.header.lock(){ return Err("wrong header in proof".into()); } @@ -330,11 +330,11 @@ impl ValidatorSet for ValidatorSafeContract { // transition to the first block of a contract requires finality but has no log event. if first { debug!(target: "engine", "signalling transition to fresh contract."); - let state_proof = Box::new(StateProof { - header: header.clone(), + let state_proof = Arc::new(StateProof { + header: Mutex::new(header.clone()), provider: self.provider.clone(), }); - return ::engines::EpochChange::Yes(::engines::Proof::WithState(state_proof as Box<_>)); + return ::engines::EpochChange::Yes(::engines::Proof::WithState(state_proof as Arc<_>)); } // otherwise, we're checking for logs. From b953f9b66aefc9c65c9202b6320d6627de4fa00a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 24 Aug 2017 15:17:48 +0200 Subject: [PATCH 09/14] glue for fetching epoch proofs from network --- ethcore/light/src/client/fetch.rs | 5 +- ethcore/light/src/client/mod.rs | 9 ++- ethcore/src/header.rs | 5 ++ parity/blockchain.rs | 4 +- parity/dapps.rs | 17 +++-- parity/informant.rs | 2 +- parity/light_helpers/epoch_fetch.rs | 89 +++++++++++++++++++++++++++ parity/light_helpers/mod.rs | 2 + parity/light_helpers/queue_cull.rs | 8 +-- parity/rpc_apis.rs | 9 +-- parity/run.rs | 17 +++-- rpc/src/v1/impls/light/eth.rs | 16 ++--- sync/src/chain.rs | 2 +- sync/src/light_sync/tests/test_net.rs | 13 +++- sync/src/tests/consensus.rs | 8 +-- 15 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 parity/light_helpers/epoch_fetch.rs diff --git a/ethcore/light/src/client/fetch.rs b/ethcore/light/src/client/fetch.rs index 2e125bb42..9f4a38dc4 100644 --- a/ethcore/light/src/client/fetch.rs +++ b/ethcore/light/src/client/fetch.rs @@ -18,6 +18,7 @@ use std::sync::Arc; +use ethcore::encoded; use ethcore::engines::{Engine, StateDependentProof}; use ethcore::header::Header; use ethcore::receipt::Receipt; @@ -30,7 +31,7 @@ pub trait ChainDataFetcher: Send + Sync + 'static { type Error: ::std::fmt::Debug; /// Future for fetching block body. - type Body: IntoFuture,Error=Self::Error>; + type Body: IntoFuture; /// Future for fetching block receipts. type Receipts: IntoFuture,Error=Self::Error>; /// Future for fetching epoch transition @@ -55,7 +56,7 @@ pub fn unavailable() -> Unavailable { Unavailable } impl ChainDataFetcher for Unavailable { type Error = &'static str; - type Body = Result, &'static str>; + type Body = Result; type Receipts = Result, &'static str>; type Transition = Result, &'static str>; diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 9c8e1da50..569acecc6 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -83,6 +83,9 @@ impl Default for Config { /// Trait for interacting with the header chain abstractly. pub trait LightChainClient: Send + Sync { + /// Adds a new `LightChainNotify` listener. + fn add_listener(&self, listener: Weak); + /// Get chain info. fn chain_info(&self) -> BlockChainInfo; @@ -481,7 +484,7 @@ impl Client { }; if let Some(b) = b { - block = Some(b.into_future().wait()?); + block = Some(b.into_future().wait()?.into_inner()); } if let Some(r) = r { @@ -526,6 +529,10 @@ impl Client { } impl LightChainClient for Client { + fn add_listener(&self, listener: Weak) { + Client::add_listener(self, listener) + } + fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } fn queue_header(&self, header: Header) -> Result { diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index a9a4f948d..a256b5c87 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -259,6 +259,11 @@ impl Header { /// Get the SHA3 (Keccak) of this header, optionally `with_seal`. pub fn rlp_sha3(&self, with_seal: Seal) -> H256 { self.rlp(with_seal).sha3() } + + /// Encode the header, getting a type-safe wrapper around the RLP. + pub fn encoded(&self) -> ::encoded::Header { + ::encoded::Header::new(self.rlp(Seal::With)) + } } impl Decodable for Header { diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 426c8d447..d9821c5bb 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -204,7 +204,9 @@ fn execute_import_light(cmd: ImportBlockchain) -> Result<(), String> { config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024; config.queue.verifier_settings = cmd.verifier_settings; - let service = LightClientService::start(config, &spec, &client_path, cache) + // TODO: could epoch signals be avilable at the end of the file? + let fetch = ::light::client::fetch::unavailable(); + let service = LightClientService::start(config, &spec, fetch, &client_path, cache) .map_err(|e| format!("Failed to start client: {}", e))?; // free up the spec in memory. diff --git a/parity/dapps.rs b/parity/dapps.rs index fddd050f5..b7ca739ec 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -26,7 +26,7 @@ use futures_cpupool::CpuPool; use hash_fetch::fetch::Client as FetchClient; use hash_fetch::urlhint::ContractClient; use helpers::replace_home; -use light::client::Client as LightClient; +use light::client::LightChainClient; use light::on_demand::{self, OnDemand}; use rpc; use rpc_apis::SignerService; @@ -94,16 +94,16 @@ impl ContractClient for FullRegistrar { } /// Registrar implementation for the light client. -pub struct LightRegistrar { +pub struct LightRegistrar { /// The light client. - pub client: Arc, + pub client: Arc, /// Handle to the on-demand service. pub on_demand: Arc, /// Handle to the light network service. pub sync: Arc, } -impl ContractClient for LightRegistrar { +impl ContractClient for LightRegistrar { fn registrar(&self) -> Result { self.client.engine().additional_params().get("registrar") .ok_or_else(|| "Registrar not defined.".into()) @@ -113,7 +113,14 @@ impl ContractClient for LightRegistrar { } fn call(&self, address: Address, data: Bytes) -> BoxFuture { - let (header, env_info) = (self.client.best_block_header(), self.client.latest_env_info()); + let header = self.client.best_block_header(); + let env_info = self.client.env_info(BlockId::Hash(header.hash())) + .ok_or_else(|| format!("Cannot fetch env info for header {}", header.hash())); + + let env_info = match env_info { + Ok(x) => x, + Err(e) => return future::err(e).boxed(), + }; let maybe_future = self.sync.with_context(move |ctx| { self.on_demand diff --git a/parity/informant.rs b/parity/informant.rs index 1935ec9b6..2be83d566 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -22,7 +22,7 @@ use std::sync::{Arc}; use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::time::{Instant, Duration}; -use ethcore::client::*; +use ethcore::client::{BlockId, BlockChainClient, BlockChainInfo, BlockQueueInfo, ChainNotify, ClientReport, Client}; use ethcore::header::BlockNumber; use ethcore::service::ClientIoMessage; use ethcore::snapshot::{RestorationStatus, SnapshotService as SS}; diff --git a/parity/light_helpers/epoch_fetch.rs b/parity/light_helpers/epoch_fetch.rs new file mode 100644 index 000000000..d710abb9f --- /dev/null +++ b/parity/light_helpers/epoch_fetch.rs @@ -0,0 +1,89 @@ +// Copyright 2015-2017 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 . + +use std::sync::{Arc, Weak}; + +use ethcore::encoded; +use ethcore::engines::{Engine, StateDependentProof}; +use ethcore::header::Header; +use ethcore::receipt::Receipt; +use ethsync::LightSync; + +use futures::{future, Future, BoxFuture}; + +use light::client::fetch::ChainDataFetcher; +use light::on_demand::{request, OnDemand}; + +use util::{RwLock, H256}; + +const ALL_VALID_BACKREFS: &str = "no back-references, therefore all back-references valid; qed"; + +/// Allows on-demand fetch of data useful for the light client. +pub struct EpochFetch { + /// A handle to the sync service. + pub sync: Arc>>, + /// The on-demand request service. + pub on_demand: Arc, +} + +impl EpochFetch { + fn request(&self, req: T) -> BoxFuture + where T: Send + request::RequestAdapter + 'static, T::Out: Send + 'static + { + match self.sync.read().upgrade() { + Some(sync) => { + let on_demand = &self.on_demand; + let maybe_future = sync.with_context(move |ctx| { + on_demand.request(ctx, req).expect(ALL_VALID_BACKREFS) + }); + + match maybe_future { + Some(x) => x.map_err(|_| "Request canceled").boxed(), + None => future::err("Unable to access network.").boxed(), + } + } + None => future::err("Unable to access network").boxed(), + } + } +} + +impl ChainDataFetcher for EpochFetch { + type Error = &'static str; + + type Body = BoxFuture; + type Receipts = BoxFuture, &'static str>; + type Transition = BoxFuture, &'static str>; + + fn block_body(&self, header: &Header) -> Self::Body { + self.request(request::Body(header.encoded().into())) + } + + /// Fetch block receipts. + fn block_receipts(&self, header: &Header) -> Self::Receipts { + self.request(request::BlockReceipts(header.encoded().into())) + } + + /// Fetch epoch transition proof at given header. + fn epoch_transition(&self, hash: H256, engine: Arc, checker: Arc) + -> Self::Transition + { + self.request(request::Signal { + hash: hash, + engine: engine, + proof_check: checker, + }) + } +} diff --git a/parity/light_helpers/mod.rs b/parity/light_helpers/mod.rs index 488f970c2..5fc9c516b 100644 --- a/parity/light_helpers/mod.rs +++ b/parity/light_helpers/mod.rs @@ -16,6 +16,8 @@ //! Utilities and helpers for the light client. +mod epoch_fetch; mod queue_cull; +pub use self::epoch_fetch::EpochFetch; pub use self::queue_cull::QueueCull; diff --git a/parity/light_helpers/queue_cull.rs b/parity/light_helpers/queue_cull.rs index 982e69e48..34769b864 100644 --- a/parity/light_helpers/queue_cull.rs +++ b/parity/light_helpers/queue_cull.rs @@ -23,7 +23,7 @@ use ethcore::service::ClientIoMessage; use ethsync::LightSync; use io::{IoContext, IoHandler, TimerToken}; -use light::client::Client; +use light::client::LightChainClient; use light::on_demand::{request, OnDemand}; use light::TransactionQueue; @@ -41,9 +41,9 @@ const TIMEOUT_MS: u64 = 1000 * 60 * 10; const PURGE_TIMEOUT_MS: u64 = 1000 * 60 * 9; /// Periodically culls the transaction queue of mined transactions. -pub struct QueueCull { +pub struct QueueCull { /// A handle to the client, for getting the latest block header. - pub client: Arc, + pub client: Arc, /// A handle to the sync service. pub sync: Arc, /// The on-demand request service. @@ -54,7 +54,7 @@ pub struct QueueCull { pub remote: Remote, } -impl IoHandler for QueueCull { +impl IoHandler for QueueCull { fn initialize(&self, io: &IoContext) { io.register_timer(TOKEN, TIMEOUT_MS).expect("Error registering timer"); } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index dba69ae7e..bb1e1c377 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -34,6 +34,7 @@ use ethsync::{ManageNetwork, SyncProvider, LightSync}; use hash_fetch::fetch::Client as FetchClient; use jsonrpc_core::{self as core, MetaIoHandler}; use light::{TransactionQueue as LightTransactionQueue, Cache as LightDataCache}; +use light::client::LightChainClient; use updater::Updater; use util::{Mutex, RwLock}; use ethcore_logger::RotatingLogger; @@ -395,9 +396,9 @@ impl ActivityNotifier for LightClientNotifier { } /// RPC dependencies for a light client. -pub struct LightDependencies { +pub struct LightDependencies { pub signer_service: Arc, - pub client: Arc<::light::client::Client>, + pub client: Arc, pub sync: Arc, pub net: Arc, pub secret_store: Arc, @@ -415,7 +416,7 @@ pub struct LightDependencies { pub whisper_rpc: Option<::whisper::RpcFactory>, } -impl LightDependencies { +impl LightDependencies { fn extend_api>( &self, handler: &mut MetaIoHandler, @@ -563,7 +564,7 @@ impl LightDependencies { } } -impl Dependencies for LightDependencies { +impl Dependencies for LightDependencies { type Notifier = LightClientNotifier; fn activity_notifier(&self) -> Self::Notifier { LightClientNotifier } diff --git a/parity/run.rs b/parity/run.rs index 49624480c..277ee9025 100644 --- a/parity/run.rs +++ b/parity/run.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::Arc; +use std::sync::{Arc, Weak}; use std::net::{TcpListener}; use ctrlc::CtrlC; @@ -217,7 +217,16 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024; config.queue.verifier_settings = cmd.verifier_settings; - let service = light_client::Service::start(config, &spec, &db_dirs.client_path(algorithm), cache.clone()) + // start on_demand service. + let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone())); + + let sync_handle = Arc::new(RwLock::new(Weak::new())); + let fetch = ::light_helpers::EpochFetch { + on_demand: on_demand.clone(), + sync: sync_handle.clone(), + }; + + let service = light_client::Service::start(config, &spec, fetch, &db_dirs.client_path(algorithm), cache.clone()) .map_err(|e| format!("Error starting light client: {}", e))?; let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default())); let provider = ::light::provider::LightProvider::new(service.client().clone(), txq.clone()); @@ -229,9 +238,6 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> net_conf.boot_nodes = spec.nodes.clone(); } - // start on_demand service. - let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone())); - let mut attached_protos = Vec::new(); let whisper_factory = if cmd.whisper.enabled { let (whisper_net, whisper_factory) = ::whisper::setup(cmd.whisper.target_message_pool_size) @@ -255,6 +261,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> }; let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?; let light_sync = Arc::new(light_sync); + *sync_handle.write() = Arc::downgrade(&light_sync); // spin up event loop let event_loop = EventLoop::spawn(); diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index d33b5a06f..b515d7961 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -25,7 +25,7 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use light::cache::Cache as LightDataCache; -use light::client::{Client as LightClient, LightChainClient}; +use light::client::LightChainClient; use light::{cht, TransactionQueue}; use light::on_demand::{request, OnDemand}; @@ -62,9 +62,9 @@ use util::Address; const NO_INVALID_BACK_REFS: &'static str = "Fails only on invalid back-references; back-references here known to be valid; qed"; /// Light client `ETH` (and filter) RPC. -pub struct EthClient { +pub struct EthClient { sync: Arc, - client: Arc, + client: Arc, on_demand: Arc, transaction_queue: Arc>, accounts: Arc, @@ -72,7 +72,7 @@ pub struct EthClient { polls: Mutex>, } -impl Clone for EthClient { +impl Clone for EthClient { fn clone(&self) -> Self { // each instance should have its own poll manager. EthClient { @@ -88,12 +88,12 @@ impl Clone for EthClient { } -impl EthClient { +impl EthClient { /// Create a new `EthClient` with a handle to the light sync instance, client, /// and on-demand request service, which is assumed to be attached as a handler. pub fn new( sync: Arc, - client: Arc, + client: Arc, on_demand: Arc, transaction_queue: Arc>, accounts: Arc, @@ -208,7 +208,7 @@ impl EthClient { } } -impl Eth for EthClient { +impl Eth for EthClient { type Metadata = Metadata; fn protocol_version(&self) -> Result { @@ -465,7 +465,7 @@ impl Eth for EthClient { } // This trait implementation triggers a blanked impl of `EthFilter`. -impl Filterable for EthClient { +impl Filterable for EthClient { fn best_block_number(&self) -> u64 { self.client.chain_info().best_block_number } fn block_hash(&self, id: BlockId) -> Option { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 3a472c88f..a4708a12d 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2238,7 +2238,7 @@ mod tests { use super::{PeerInfo, PeerAsking}; use ethkey; use ethcore::header::*; - use ethcore::client::*; + use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; use ethcore::transaction::UnverifiedTransaction; use ethcore::miner::MinerService; diff --git a/sync/src/light_sync/tests/test_net.rs b/sync/src/light_sync/tests/test_net.rs index f6d5eddf0..51cb796de 100644 --- a/sync/src/light_sync/tests/test_net.rs +++ b/sync/src/light_sync/tests/test_net.rs @@ -25,7 +25,7 @@ use tests::helpers::{TestNet, Peer as PeerLike, TestPacket}; use ethcore::client::TestBlockChainClient; use ethcore::spec::Spec; use io::IoChannel; -use light::client::Client as LightClient; +use light::client::fetch::{self, Unavailable}; use light::net::{LightProtocol, IoContext, Capabilities, Params as LightParams}; use light::provider::LightProvider; use network::{NodeId, PeerId}; @@ -36,6 +36,8 @@ use light::cache::Cache; const NETWORK_ID: u64 = 0xcafebabe; +pub type LightClient = ::light::client::Client; + struct TestIoContext<'a> { queue: &'a RwLock>, sender: Option, @@ -216,7 +218,14 @@ impl TestNet { // skip full verification because the blocks are bad. config.verify_full = false; let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let client = LightClient::in_memory(config, &Spec::new_test(), IoChannel::disconnected(), cache); + let client = LightClient::in_memory( + config, + &Spec::new_test(), + fetch::unavailable(), // TODO: allow fetch from full nodes. + IoChannel::disconnected(), + cache + ); + peers.push(Arc::new(Peer::new_light(Arc::new(client)))) } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 9b01156b7..c31ea692f 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -69,8 +69,8 @@ fn authority_round() { // Push transaction to both clients. Only one of them gets lucky to produce a block. net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); - net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); - net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain) as _); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain) as _); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); // exchange statuses @@ -158,8 +158,8 @@ fn tendermint() { trace!(target: "poa", "Peer 0 is {}.", s0.address()); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); trace!(target: "poa", "Peer 1 is {}.", s1.address()); - net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); - net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain) as _); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain) as _); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); // Exhange statuses From c79ecee094190dd70a57eb097aed0f17aab011f7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 24 Aug 2017 16:29:31 +0200 Subject: [PATCH 10/14] only load ancestry from chain closure in engine --- ethcore/light/src/client/header_chain.rs | 4 ++-- ethcore/src/engines/authority_round/mod.rs | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 1f97e281b..123eaf653 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -411,8 +411,8 @@ impl HeaderChain { self.live_epoch_proofs.write().insert(hash, transition); } - let raw = ::rlp::encode(&header); - transaction.put(self.col, &hash[..], &*raw); + let raw = header.encoded().into_inner(); + transaction.put_vec(self.col, &hash[..], raw); let (best_num, is_new_best) = { let cur_best = self.best_block.read(); diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 33979e01c..44f3d801c 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -745,13 +745,18 @@ impl Engine for AuthorityRound { { if let Ok(finalized) = epoch_manager.finality_checker.push_hash(chain_head.hash(), *chain_head.author()) { let mut finalized = finalized.into_iter(); - while let Some(hash) = finalized.next() { - if let Some(pending) = transition_store(hash) { - let finality_proof = ::std::iter::once(hash) + while let Some(finalized_hash) = finalized.next() { + if let Some(pending) = transition_store(finalized_hash) { + let finality_proof = ::std::iter::once(finalized_hash) .chain(finalized) .chain(epoch_manager.finality_checker.unfinalized_hashes()) - .map(|hash| chain(hash) - .expect("these headers fetched before when constructing finality checker; qed")) + .map(|h| if h == chain_head.hash() { + // chain closure only stores ancestry, but the chain head is also + // unfinalized. + chain_head.clone() + } else { + chain(h).expect("these headers fetched before when constructing finality checker; qed") + }) .collect::>(); // this gives us the block number for `hash`, assuming it's ancestry. From ffae847b48a260789e265ca1e2bec83459101d26 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Sat, 2 Sep 2017 09:57:53 +0200 Subject: [PATCH 11/14] spacing around comas --- ethcore/light/src/client/fetch.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethcore/light/src/client/fetch.rs b/ethcore/light/src/client/fetch.rs index 9f4a38dc4..0aae7d31c 100644 --- a/ethcore/light/src/client/fetch.rs +++ b/ethcore/light/src/client/fetch.rs @@ -31,11 +31,11 @@ pub trait ChainDataFetcher: Send + Sync + 'static { type Error: ::std::fmt::Debug; /// Future for fetching block body. - type Body: IntoFuture; + type Body: IntoFuture; /// Future for fetching block receipts. - type Receipts: IntoFuture,Error=Self::Error>; + type Receipts: IntoFuture, Error=Self::Error>; /// Future for fetching epoch transition - type Transition: IntoFuture,Error=Self::Error>; + type Transition: IntoFuture, Error=Self::Error>; /// Fetch a block body. fn block_body(&self, header: &Header) -> Self::Body; From e69e8254db0169fa6dd8f803e176453478c430f8 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 4 Sep 2017 12:37:03 +0200 Subject: [PATCH 12/14] instantiate genesis epoch data if nonexistant in header_chain --- ethcore/light/src/client/header_chain.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index b134f3e25..810165c91 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -258,6 +258,8 @@ impl HeaderChain { let mut live_epoch_proofs = ::std::collections::HashMap::default(); let genesis = ::rlp::encode(&spec.genesis_header()).into_vec(); + let decoded_header = spec.genesis_header(); + let chain = if let Some(current) = db.get(col, CURRENT_KEY)? { let (best_number, highest_number) = { let rlp = Rlp::new(¤t); @@ -315,16 +317,6 @@ impl HeaderChain { cache: cache, } } else { - let decoded_header: Header = ::rlp::decode(&genesis); - let genesis_data = genesis_epoch_data(spec, &decoded_header)?; - - { - let mut batch = db.transaction(); - let data = encode_canonical_transition(&decoded_header, &genesis_data); - batch.put_vec(col, LAST_CANONICAL_TRANSITION, data); - db.write(batch)?; - } - HeaderChain { genesis_header: encoded::Header::new(genesis), best_block: RwLock::new(BlockDescriptor { @@ -340,6 +332,18 @@ impl HeaderChain { } }; + // instantiate genesis epoch data if it doesn't exist. + if let None = chain.db.get(col, LAST_CANONICAL_TRANSITION)? { + let genesis_data = genesis_epoch_data(spec, &decoded_header)?; + + { + let mut batch = chain.db.transaction(); + let data = encode_canonical_transition(&decoded_header, &genesis_data); + batch.put_vec(col, LAST_CANONICAL_TRANSITION, data); + chain.db.write(batch)?; + } + } + Ok(chain) } From ffde22e711e10cde1dcec688f22afb9a2fd500a0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 4 Sep 2017 12:46:32 +0200 Subject: [PATCH 13/14] move genesis_epoch_data function to spec --- ethcore/light/src/client/header_chain.rs | 59 +----------------------- ethcore/src/spec/spec.rs | 57 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 810165c91..4783b316a 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -179,63 +179,6 @@ pub struct PendingChanges { best_block: Option, // new best block. } -// initialize genesis epoch data, using in-memory database for -// constructor. -// TODO: move to `Spec`? -fn genesis_epoch_data(spec: &Spec, genesis: &Header) -> Result, String> { - use ethcore::state::backend::Basic as BasicBackend; - use ethcore::transaction::{Action, Transaction}; - use util::{journaldb, kvdb, Address}; - - let factories = Default::default(); - let mut db = journaldb::new( - Arc::new(kvdb::in_memory(0)), - journaldb::Algorithm::Archive, - None, - ); - - spec.ensure_db_good(BasicBackend(db.as_hashdb_mut()), &factories) - .map_err(|e| format!("Unable to initialize genesis state: {}", e))?; - - let call = |a, d| { - let mut db = db.boxed_clone(); - let env_info = ::evm::EnvInfo { - number: 0, - author: *genesis.author(), - timestamp: genesis.timestamp(), - difficulty: *genesis.difficulty(), - gas_limit: *genesis.gas_limit(), - last_hashes: Arc::new(Vec::new()), - gas_used: 0.into() - }; - - let from = Address::default(); - let tx = Transaction { - nonce: spec.engine.account_start_nonce(0), - action: Action::Call(a), - gas: U256::from(50_000_000), // TODO: share with client. - gas_price: U256::default(), - value: U256::default(), - data: d, - }.fake_sign(from); - - let res = ::ethcore::state::prove_transaction( - db.as_hashdb_mut(), - *genesis.state_root(), - &tx, - &*spec.engine, - &env_info, - factories.clone(), - true, - ); - - res.map(|(out, proof)| (out, proof.into_iter().map(|x| x.into_vec()).collect())) - .ok_or_else(|| "Failed to prove call: insufficient state".into()) - }; - - spec.engine.genesis_epoch_data(&genesis, &call) -} - /// Header chain. See module docs for more details. pub struct HeaderChain { genesis_header: encoded::Header, // special-case the genesis. @@ -334,7 +277,7 @@ impl HeaderChain { // instantiate genesis epoch data if it doesn't exist. if let None = chain.db.get(col, LAST_CANONICAL_TRANSITION)? { - let genesis_data = genesis_epoch_data(spec, &decoded_header)?; + let genesis_data = spec.genesis_epoch_data()?; { let mut batch = chain.db.transaction(); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 048e8dd03..cc98f4d4c 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -481,6 +481,63 @@ impl Spec { .and_then(|x| load_from(cache_dir, x).map_err(fmt)) } + /// initialize genesis epoch data, using in-memory database for + /// constructor. + pub fn genesis_epoch_data(&self) -> Result, String> { + use transaction::{Action, Transaction}; + use util::{journaldb, kvdb}; + + let genesis = self.genesis_header(); + + let factories = Default::default(); + let mut db = journaldb::new( + Arc::new(kvdb::in_memory(0)), + journaldb::Algorithm::Archive, + None, + ); + + self.ensure_db_good(BasicBackend(db.as_hashdb_mut()), &factories) + .map_err(|e| format!("Unable to initialize genesis state: {}", e))?; + + let call = |a, d| { + let mut db = db.boxed_clone(); + let env_info = ::evm::EnvInfo { + number: 0, + author: *genesis.author(), + timestamp: genesis.timestamp(), + difficulty: *genesis.difficulty(), + gas_limit: *genesis.gas_limit(), + last_hashes: Arc::new(Vec::new()), + gas_used: 0.into() + }; + + let from = Address::default(); + let tx = Transaction { + nonce: self.engine.account_start_nonce(0), + action: Action::Call(a), + gas: U256::from(50_000_000), // TODO: share with client. + gas_price: U256::default(), + value: U256::default(), + data: d, + }.fake_sign(from); + + let res = ::state::prove_transaction( + db.as_hashdb_mut(), + *genesis.state_root(), + &tx, + &*self.engine, + &env_info, + factories.clone(), + true, + ); + + res.map(|(out, proof)| (out, proof.into_iter().map(|x| x.into_vec()).collect())) + .ok_or_else(|| "Failed to prove call: insufficient state".into()) + }; + + self.engine.genesis_epoch_data(&genesis, &call) + } + /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus. pub fn new_test() -> Spec { load_bundled!("null_morden") } From 06e97d107bac62d726b4545f0c0df35ec0253fce Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 4 Sep 2017 14:52:39 +0200 Subject: [PATCH 14/14] fix rwlock import --- parity/light_helpers/epoch_fetch.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parity/light_helpers/epoch_fetch.rs b/parity/light_helpers/epoch_fetch.rs index d710abb9f..8bc879466 100644 --- a/parity/light_helpers/epoch_fetch.rs +++ b/parity/light_helpers/epoch_fetch.rs @@ -27,7 +27,8 @@ use futures::{future, Future, BoxFuture}; use light::client::fetch::ChainDataFetcher; use light::on_demand::{request, OnDemand}; -use util::{RwLock, H256}; +use parking_lot::RwLock; +use util::H256; const ALL_VALID_BACKREFS: &str = "no back-references, therefore all back-references valid; qed";