Properly handle check_epoch_end_signal errors (#10015)

* Make check_epoch_end_signal to only use immutable data

* Move check_epoch_end_signals out of commit_block

* Make check_epoch_end_signals possible to fail

* Actually return the error from check_epoch_end_signals

* Remove a clone

* Fix import error
This commit is contained in:
Wei Tang 2019-02-07 14:34:07 +01:00 committed by Afri Schoedon
parent e45ee6cd72
commit 8fa56add47
3 changed files with 49 additions and 28 deletions

View File

@ -61,7 +61,8 @@ use client::{
IoClient, BadBlocks, IoClient, BadBlocks,
}; };
use client::bad_blocks; use client::bad_blocks;
use engines::{EthEngine, EpochTransition, ForkChoice}; use engines::{EthEngine, EpochTransition, ForkChoice, EngineError};
use engines::epoch::PendingTransition;
use error::{ use error::{
ImportErrorKind, ExecutionError, CallError, BlockError, ImportErrorKind, ExecutionError, CallError, BlockError,
QueueError, QueueErrorKind, Error as EthcoreError, EthcoreResult, ErrorKind as EthcoreErrorKind QueueError, QueueErrorKind, Error as EthcoreError, EthcoreResult, ErrorKind as EthcoreErrorKind
@ -295,8 +296,8 @@ impl Importer {
continue; continue;
} }
match self.check_and_lock_block(block, client) { match self.check_and_lock_block(&bytes, block, client) {
Ok(closed_block) => { Ok((closed_block, pending)) => {
if self.engine.is_proposal(&header) { if self.engine.is_proposal(&header) {
self.block_queue.mark_as_good(&[hash]); self.block_queue.mark_as_good(&[hash]);
proposed_blocks.push(bytes); proposed_blocks.push(bytes);
@ -305,7 +306,7 @@ impl Importer {
let transactions_len = closed_block.transactions().len(); let transactions_len = closed_block.transactions().len();
let route = self.commit_block(closed_block, &header, encoded::Block::new(bytes), client); let route = self.commit_block(closed_block, &header, encoded::Block::new(bytes), pending, client);
import_results.push(route); import_results.push(route);
client.report.write().accrue_block(&header, transactions_len); client.report.write().accrue_block(&header, transactions_len);
@ -357,7 +358,7 @@ impl Importer {
imported imported
} }
fn check_and_lock_block(&self, block: PreverifiedBlock, client: &Client) -> EthcoreResult<LockedBlock> { fn check_and_lock_block(&self, bytes: &[u8], block: PreverifiedBlock, client: &Client) -> EthcoreResult<(LockedBlock, Option<PendingTransition>)> {
let engine = &*self.engine; let engine = &*self.engine;
let header = block.header.clone(); let header = block.header.clone();
@ -441,7 +442,15 @@ impl Importer {
bail!(e); bail!(e);
} }
Ok(locked_block) let pending = self.check_epoch_end_signal(
&header,
bytes,
locked_block.receipts(),
locked_block.state().db(),
client
)?;
Ok((locked_block, pending))
} }
/// Import a block with transaction receipts. /// Import a block with transaction receipts.
@ -474,7 +483,7 @@ impl Importer {
// //
// The header passed is from the original block data and is sealed. // The header passed is from the original block data and is sealed.
// TODO: should return an error if ImportRoute is none, issue #9910 // TODO: should return an error if ImportRoute is none, issue #9910
fn commit_block<B>(&self, block: B, header: &Header, block_data: encoded::Block, client: &Client) -> ImportRoute where B: Drain { fn commit_block<B>(&self, block: B, header: &Header, block_data: encoded::Block, pending: Option<PendingTransition>, client: &Client) -> ImportRoute where B: Drain {
let hash = &header.hash(); let hash = &header.hash();
let number = header.number(); let number = header.number();
let parent = header.parent_hash(); let parent = header.parent_hash();
@ -529,15 +538,9 @@ impl Importer {
// check epoch end signal, potentially generating a proof on the current // check epoch end signal, potentially generating a proof on the current
// state. // state.
self.check_epoch_end_signal( if let Some(pending) = pending {
&header, chain.insert_pending_transition(&mut batch, header.hash(), pending);
block_data.raw(), }
&receipts,
&state,
&chain,
&mut batch,
client
);
state.journal_under(&mut batch, number, hash).expect("DB commit failed"); state.journal_under(&mut batch, number, hash).expect("DB commit failed");
@ -592,10 +595,8 @@ impl Importer {
block_bytes: &[u8], block_bytes: &[u8],
receipts: &[Receipt], receipts: &[Receipt],
state_db: &StateDB, state_db: &StateDB,
chain: &BlockChain,
batch: &mut DBTransaction,
client: &Client, client: &Client,
) { ) -> EthcoreResult<Option<PendingTransition>> {
use engines::EpochChange; use engines::EpochChange;
let hash = header.hash(); let hash = header.hash();
@ -606,7 +607,6 @@ impl Importer {
match self.engine.signals_epoch_end(header, auxiliary) { match self.engine.signals_epoch_end(header, auxiliary) {
EpochChange::Yes(proof) => { EpochChange::Yes(proof) => {
use engines::epoch::PendingTransition;
use engines::Proof; use engines::Proof;
let proof = match proof { let proof = match proof {
@ -643,11 +643,9 @@ impl Importer {
.transact(&transaction, options); .transact(&transaction, options);
let res = match res { let res = match res {
Err(ExecutionError::Internal(e)) =>
Err(format!("Internal error: {}", e)),
Err(e) => { Err(e) => {
trace!(target: "client", "Proved call failed: {}", e); trace!(target: "client", "Proved call failed: {}", e);
Ok((Vec::new(), state.drop().1.extract_proof())) Err(e.to_string())
} }
Ok(res) => Ok((res.output, state.drop().1.extract_proof())), Ok(res) => Ok((res.output, state.drop().1.extract_proof())),
}; };
@ -660,7 +658,7 @@ impl Importer {
Err(e) => { Err(e) => {
warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e); warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e);
warn!(target: "client", "Snapshots produced by this client may be incomplete"); warn!(target: "client", "Snapshots produced by this client may be incomplete");
Vec::new() return Err(EngineError::FailedSystemCall(e).into())
} }
} }
} }
@ -668,13 +666,13 @@ impl Importer {
debug!(target: "client", "Block {} signals epoch end.", hash); debug!(target: "client", "Block {} signals epoch end.", hash);
let pending = PendingTransition { proof: proof }; Ok(Some(PendingTransition { proof: proof }))
chain.insert_pending_transition(batch, hash, pending);
}, },
EpochChange::No => {}, EpochChange::No => Ok(None),
EpochChange::Unsure(_) => { EpochChange::Unsure(_) => {
warn!(target: "client", "Detected invalid engine implementation."); warn!(target: "client", "Detected invalid engine implementation.");
warn!(target: "client", "Engine claims to require more block data, but everything provided."); warn!(target: "client", "Engine claims to require more block data, but everything provided.");
Err(EngineError::InvalidEngine.into())
} }
} }
} }
@ -2380,7 +2378,20 @@ impl ImportSealedBlock for Client {
let block_data = block.rlp_bytes(); let block_data = block.rlp_bytes();
let route = self.importer.commit_block(block, &header, encoded::Block::new(block_data), self); let pending = self.importer.check_epoch_end_signal(
&header,
&block_data,
block.receipts(),
block.state().db(),
self
)?;
let route = self.importer.commit_block(
block,
&header,
encoded::Block::new(block_data),
pending,
self
);
trace!(target: "client", "Imported sealed block #{} ({})", header.number(), hash); trace!(target: "client", "Imported sealed block #{} ({})", header.number(), hash);
self.state_db.write().sync_cache(&route.enacted, &route.retracted, false); self.state_db.write().sync_cache(&route.enacted, &route.retracted, false);
route route

View File

@ -81,6 +81,8 @@ pub enum EngineError {
MalformedMessage(String), MalformedMessage(String),
/// Requires client ref, but none registered. /// Requires client ref, but none registered.
RequiresClient, RequiresClient,
/// Invalid engine specification or implementation.
InvalidEngine,
} }
impl fmt::Display for EngineError { impl fmt::Display for EngineError {
@ -96,6 +98,7 @@ impl fmt::Display for EngineError {
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg), FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg), MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
RequiresClient => format!("Call requires client but none registered"), RequiresClient => format!("Call requires client but none registered"),
InvalidEngine => format!("Invalid engine specification or implementation"),
}; };
f.write_fmt(format_args!("Engine error ({})", msg)) f.write_fmt(format_args!("Engine error ({})", msg))

View File

@ -1284,6 +1284,13 @@ impl<B: Backend> fmt::Debug for State<B> {
} }
} }
impl State<StateDB> {
/// Get a reference to the underlying state DB.
pub fn db(&self) -> &StateDB {
&self.db
}
}
// TODO: cloning for `State` shouldn't be possible in general; Remove this and use // TODO: cloning for `State` shouldn't be possible in general; Remove this and use
// checkpoints where possible. // checkpoints where possible.
impl Clone for State<StateDB> { impl Clone for State<StateDB> {