Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Properly handle check_epoch_end_signal errors (#10015)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sorpaas authored and 5chdn committed Feb 12, 2019
1 parent fe1be5f commit 12f8d10
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 28 deletions.
70 changes: 42 additions & 28 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ use std::time::{Instant, Duration};

use blockchain::{BlockReceipts, BlockChain, BlockChainDB, BlockProvider, TreeRoute, ImportRoute, TransactionAddress, ExtrasInsert};
use bytes::Bytes;
use call_contract::{CallContract, RegistryInfo};
use ethcore_miner::pool::VerifiedTransaction;
use ethcore_miner::service_transaction_checker::ServiceTransactionChecker;
use ethereum_types::{H256, Address, U256};
use evm::Schedule;
use hash::keccak;
Expand Down Expand Up @@ -59,7 +61,8 @@ use client::{
IoClient, BadBlocks,
};
use client::bad_blocks;
use engines::{EthEngine, EpochTransition, ForkChoice};
use engines::{EthEngine, EpochTransition, ForkChoice, EngineError};
use engines::epoch::PendingTransition;
use error::{
ImportErrorKind, ExecutionError, CallError, BlockError,
QueueError, QueueErrorKind, Error as EthcoreError, EthcoreResult, ErrorKind as EthcoreErrorKind
Expand Down Expand Up @@ -291,8 +294,8 @@ impl Importer {
continue;
}

match self.check_and_lock_block(block, client) {
Ok(closed_block) => {
match self.check_and_lock_block(&bytes, block, client) {
Ok((closed_block, pending)) => {
if self.engine.is_proposal(&header) {
self.block_queue.mark_as_good(&[hash]);
proposed_blocks.push(bytes);
Expand All @@ -301,7 +304,7 @@ impl Importer {

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);

client.report.write().accrue_block(&header, transactions_len);
Expand Down Expand Up @@ -353,7 +356,7 @@ impl Importer {
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 header = block.header.clone();

Expand Down Expand Up @@ -437,7 +440,15 @@ impl Importer {
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.
Expand Down Expand Up @@ -469,7 +480,8 @@ impl Importer {
// it is for reconstructing the state transition.
//
// The header passed is from the original block data and is sealed.
fn commit_block<B>(&self, block: B, header: &Header, block_data: encoded::Block, client: &Client) -> ImportRoute where B: Drain {
// TODO: should return an error if ImportRoute is none, issue #9910
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 number = header.number();
let parent = header.parent_hash();
Expand Down Expand Up @@ -524,15 +536,9 @@ impl Importer {

// check epoch end signal, potentially generating a proof on the current
// state.
self.check_epoch_end_signal(
&header,
block_data.raw(),
&receipts,
&state,
&chain,
&mut batch,
client
);
if let Some(pending) = pending {
chain.insert_pending_transition(&mut batch, header.hash(), pending);
}

state.journal_under(&mut batch, number, hash).expect("DB commit failed");

Expand Down Expand Up @@ -587,10 +593,8 @@ impl Importer {
block_bytes: &[u8],
receipts: &[Receipt],
state_db: &StateDB,
chain: &BlockChain,
batch: &mut DBTransaction,
client: &Client,
) {
) -> EthcoreResult<Option<PendingTransition>> {
use engines::EpochChange;

let hash = header.hash();
Expand All @@ -601,7 +605,6 @@ impl Importer {

match self.engine.signals_epoch_end(header, auxiliary) {
EpochChange::Yes(proof) => {
use engines::epoch::PendingTransition;
use engines::Proof;

let proof = match proof {
Expand Down Expand Up @@ -638,11 +641,9 @@ impl Importer {
.transact(&transaction, options);

let res = match res {
Err(ExecutionError::Internal(e)) =>
Err(format!("Internal error: {}", e)),
Err(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())),
};
Expand All @@ -655,21 +656,21 @@ impl Importer {
Err(e) => {
warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e);
warn!(target: "client", "Snapshots produced by this client may be incomplete");
Vec::new()
return Err(EngineError::FailedSystemCall(e).into())
}
}
}
};

debug!(target: "client", "Block {} signals epoch end.", hash);

let pending = PendingTransition { proof: proof };
chain.insert_pending_transition(batch, hash, pending);
Ok(Some(PendingTransition { proof: proof }))
},
EpochChange::No => {},
EpochChange::No => Ok(None),
EpochChange::Unsure(_) => {
warn!(target: "client", "Detected invalid engine implementation.");
warn!(target: "client", "Engine claims to require more block data, but everything provided.");
Err(EngineError::InvalidEngine.into())
}
}
}
Expand Down Expand Up @@ -2328,7 +2329,20 @@ impl ImportSealedBlock for Client {

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);
self.state_db.write().sync_cache(&route.enacted, &route.retracted, false);
route
Expand Down
3 changes: 3 additions & 0 deletions ethcore/src/engines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub enum EngineError {
MalformedMessage(String),
/// Requires client ref, but none registered.
RequiresClient,
/// Invalid engine specification or implementation.
InvalidEngine,
}

impl fmt::Display for EngineError {
Expand All @@ -96,6 +98,7 @@ impl fmt::Display for EngineError {
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
RequiresClient => format!("Call requires client but none registered"),
InvalidEngine => format!("Invalid engine specification or implementation"),
};

f.write_fmt(format_args!("Engine error ({})", msg))
Expand Down
7 changes: 7 additions & 0 deletions ethcore/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,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
// checkpoints where possible.
impl Clone for State<StateDB> {
Expand Down

0 comments on commit 12f8d10

Please sign in to comment.