checking for signals in the light client

This commit is contained in:
Robert Habermeier 2017-08-03 18:18:19 +02:00
parent 2bd5c3dba7
commit 0abf2abc81
5 changed files with 190 additions and 21 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<Item=Vec<u8>,Error=Self::Error>;
/// Future for fetching block receipts.
type Receipts: IntoFuture<Item=Vec<Receipt>,Error=Self::Error>;
/// Future for fetching epoch transition
type Transition: IntoFuture<Item=Vec<u8>,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<Vec<u8>, &'static str>;
type Receipts = Result<Vec<Receipt>, &'static str>;
type Transition = Result<Vec<u8>, &'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")
}
}

View File

@ -20,7 +20,7 @@ use std::sync::{Weak, Arc};
use ethcore::block_status::BlockStatus; use ethcore::block_status::BlockStatus;
use ethcore::client::{ClientReport, EnvInfo}; use ethcore::client::{ClientReport, EnvInfo};
use ethcore::engines::Engine; use ethcore::engines::{Engine, EpochChange, Proof, Unsure};
use ethcore::error::BlockImportError; use ethcore::error::BlockImportError;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::header::Header; use ethcore::header::Header;
@ -31,9 +31,12 @@ use ethcore::service::ClientIoMessage;
use ethcore::encoded; use ethcore::encoded;
use io::IoChannel; use io::IoChannel;
use futures::{IntoFuture, Future};
use util::{H256, U256, Mutex, RwLock}; use util::{H256, U256, Mutex, RwLock};
use util::kvdb::{KeyValueDB, CompactionProfile}; use util::kvdb::{KeyValueDB, CompactionProfile};
use self::fetch::ChainDataFetcher;
use self::header_chain::{AncestryIter, HeaderChain}; use self::header_chain::{AncestryIter, HeaderChain};
use cache::Cache; use cache::Cache;
@ -43,6 +46,8 @@ pub use self::service::Service;
mod header_chain; mod header_chain;
mod service; mod service;
pub mod fetch;
/// Configuration for the light client. /// Configuration for the light client.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
@ -154,7 +159,7 @@ impl<T: LightChainClient> AsLightClient for T {
} }
/// Light client implementation. /// Light client implementation.
pub struct Client { pub struct Client<T> {
queue: HeaderQueue, queue: HeaderQueue,
engine: Arc<Engine>, engine: Arc<Engine>,
chain: HeaderChain, chain: HeaderChain,
@ -162,12 +167,21 @@ pub struct Client {
import_lock: Mutex<()>, import_lock: Mutex<()>,
db: Arc<KeyValueDB>, db: Arc<KeyValueDB>,
listeners: RwLock<Vec<Weak<LightChainNotify>>>, listeners: RwLock<Vec<Weak<LightChainNotify>>>,
fetcher: T,
verify_full: bool, verify_full: bool,
} }
impl Client { impl<T: ChainDataFetcher> Client<T> {
/// Create a new `Client`. /// Create a new `Client`.
pub fn new(config: Config, db: Arc<KeyValueDB>, chain_col: Option<u32>, spec: &Spec, io_channel: IoChannel<ClientIoMessage>, cache: Arc<Mutex<Cache>>) -> Result<Self, String> { pub fn new(
config: Config,
db: Arc<KeyValueDB>,
chain_col: Option<u32>,
spec: &Spec,
fetcher: T,
io_channel: IoChannel<ClientIoMessage>,
cache: Arc<Mutex<Cache>>
) -> Result<Self, String> {
let gh = ::rlp::encode(&spec.genesis_header()); let gh = ::rlp::encode(&spec.genesis_header());
Ok(Client { Ok(Client {
@ -178,6 +192,7 @@ impl Client {
import_lock: Mutex::new(()), import_lock: Mutex::new(()),
db: db, db: db,
listeners: RwLock::new(vec![]), listeners: RwLock::new(vec![]),
fetcher: fetcher,
verify_full: config.verify_full, verify_full: config.verify_full,
}) })
} }
@ -189,10 +204,24 @@ impl Client {
/// Create a new `Client` backed purely in-memory. /// Create a new `Client` backed purely in-memory.
/// This will ignore all database options in the configuration. /// This will ignore all database options in the configuration.
pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>, cache: Arc<Mutex<Cache>>) -> Self { pub fn in_memory(
config: Config,
spec: &Spec,
fetcher: T,
io_channel: IoChannel<ClientIoMessage>,
cache: Arc<Mutex<Cache>>
) -> Self {
let db = ::util::kvdb::in_memory(0); 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. /// Import a header to the queue for additional verification.
@ -291,9 +320,14 @@ impl Client {
continue continue
} }
// TODO: `epoch_end_signal`, `is_epoch_end`. let _write_proof_result = match self.check_epoch_signal(&verified_header) {
// proofs we get from the network would be _complete_, whereas we need Ok(Some(proof)) => self.write_pending_proof(&verified_header, proof),
// _incomplete_ signals Ok(None) => Ok(()),
Err(e) =>
panic!("Unable to fetch epoch transition proof: {:?}", e),
};
// TODO: check epoch end.
let mut tx = self.db.transaction(); 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) {
@ -419,9 +453,71 @@ impl Client {
true true
} }
fn check_epoch_signal(&self, verified_header: &Header) -> Result<Option<Proof>, 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<T: ChainDataFetcher> LightChainClient for Client<T> {
fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) }
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError> { fn queue_header(&self, header: Header) -> Result<H256, BlockImportError> {

View File

@ -30,7 +30,7 @@ use util::kvdb::{Database, DatabaseConfig};
use cache::Cache; use cache::Cache;
use util::Mutex; use util::Mutex;
use super::{Client, Config as ClientConfig}; use super::{ChainDataFetcher, Client, Config as ClientConfig};
/// Errors on service initialization. /// Errors on service initialization.
#[derive(Debug)] #[derive(Debug)]
@ -51,14 +51,14 @@ impl fmt::Display for Error {
} }
/// Light client service. /// Light client service.
pub struct Service { pub struct Service<T> {
client: Arc<Client>, client: Arc<Client<T>>,
io_service: IoService<ClientIoMessage>, io_service: IoService<ClientIoMessage>,
} }
impl Service { impl<T: ChainDataFetcher> Service<T> {
/// Start the service: initialize I/O workers and client itself. /// Start the service: initialize I/O workers and client itself.
pub fn start(config: ClientConfig, spec: &Spec, path: &Path, cache: Arc<Mutex<Cache>>) -> Result<Self, Error> { pub fn start(config: ClientConfig, spec: &Spec, fetcher: T, path: &Path, cache: Arc<Mutex<Cache>>) -> Result<Self, Error> {
// initialize database. // initialize database.
let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS); let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS);
@ -81,6 +81,7 @@ impl Service {
db, db,
db::COL_LIGHT_CHAIN, db::COL_LIGHT_CHAIN,
spec, spec,
fetcher,
io_service.channel(), io_service.channel(),
cache, cache,
).map_err(Error::Database)?); ).map_err(Error::Database)?);
@ -97,14 +98,14 @@ impl Service {
} }
/// Get a handle to the client. /// Get a handle to the client.
pub fn client(&self) -> &Arc<Client> { pub fn client(&self) -> &Arc<Client<T>> {
&self.client &self.client
} }
} }
struct ImportBlocks(Arc<Client>); struct ImportBlocks<T>(Arc<Client<T>>);
impl IoHandler<ClientIoMessage> for ImportBlocks { impl<T: ChainDataFetcher> IoHandler<ClientIoMessage> for ImportBlocks<T> {
fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) { fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) {
if let ClientIoMessage::BlockVerified = *message { if let ClientIoMessage::BlockVerified = *message {
self.0.import_verified(); self.0.import_verified();
@ -117,9 +118,10 @@ mod tests {
use super::Service; use super::Service;
use devtools::RandomTempPath; use devtools::RandomTempPath;
use ethcore::spec::Spec; use ethcore::spec::Spec;
use std::sync::Arc; use std::sync::Arc;
use cache::Cache; use cache::Cache;
use client::fetch;
use time::Duration; use time::Duration;
use util::Mutex; use util::Mutex;
@ -129,6 +131,6 @@ mod tests {
let temp_path = RandomTempPath::new(); let temp_path = RandomTempPath::new();
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); 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();
} }
} }

View File

@ -666,6 +666,8 @@ impl Engine for AuthorityRound {
(&active_set as &_, epoch_manager.epoch_transition_number) (&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 { let report = |report| match report {
Report::Benign(address, block_number) => Report::Benign(address, block_number) =>
self.validators.report_benign(&address, set_number, block_number), self.validators.report_benign(&address, set_number, block_number),

View File

@ -132,7 +132,7 @@ pub trait StateDependentProof {
/// Proof generated on epoch change. /// Proof generated on epoch change.
pub enum Proof { pub enum Proof {
/// Known proof (exctracted from signal) /// Known proof (extracted from signal)
Known(Vec<u8>), Known(Vec<u8>),
/// State dependent proof. /// State dependent proof.
WithState(Box<StateDependentProof>), WithState(Box<StateDependentProof>),