From 0abf2abc813767f0c76528fe8a5e31aeefc020db Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 3 Aug 2017 18:18:19 +0200 Subject: [PATCH] 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),