Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions ethcore/src/engines/authority_round/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ pub struct AuthorityRound {
posdao_transition: Option<BlockNumber>,
/// The addresses of a contracts that determine the block gas limit.
block_gas_limit_contract_transitions: BTreeMap<u64, Address>,
/// History of step hashes recently received from peers.
received_step_hashes: RwLock<BTreeMap<(u64, Address), H256>>,
}

// header-chain validator.
Expand Down Expand Up @@ -635,7 +637,7 @@ fn header_expected_seal_fields(header: &Header, empty_steps_transition: u64) ->

fn header_step(header: &Header, empty_steps_transition: u64) -> Result<u64, ::rlp::DecoderError> {
Rlp::new(&header.seal().get(0).unwrap_or_else(||
panic!("was either checked with verify_block_basic or is genesis; has {} fields; qed (Make sure the spec
panic!("was either checked with verify_block_basic or is genesis; has {} fields; qed (Make sure the spec \
file has a correct genesis seal)", header_expected_seal_fields(header, empty_steps_transition))
))
.as_val()
Expand Down Expand Up @@ -819,6 +821,7 @@ impl AuthorityRound {
randomness_contract_address: our_params.randomness_contract_address,
posdao_transition: our_params.posdao_transition,
block_gas_limit_contract_transitions: our_params.block_gas_limit_contract_transitions,
received_step_hashes: RwLock::new(Default::default()),
});

// Do not initialize timeouts for tests.
Expand Down Expand Up @@ -1425,6 +1428,25 @@ impl Engine<EthereumMachine> for AuthorityRound {

/// Make calls to the randomness and validator set contracts.
fn on_prepare_block(&self, block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
const SIBLING_MALICE_DETECTION_PERIOD: u64 = 100;
let client = self.client.read().as_ref().and_then(|weak| weak.upgrade()).ok_or_else(|| {
debug!(target: "engine", "Unable to prepare block: missing client ref.");
EngineError::RequiresClient
})?;
// Remove older step hash records.
if block.header.number() > 0 {
let parent = client.block_header(::client::BlockId::Hash(*block.header.parent_hash()))
.expect("Block number > 0, so parent header must exist. QED")
.decode()?;
let parent_step = header_step(&parent, self.empty_steps_transition)?;
let oldest_step = parent_step.saturating_sub(SIBLING_MALICE_DETECTION_PERIOD);
let mut rsh = self.received_step_hashes.write();
let new_rsh = rsh.split_off(&(oldest_step, Address::zero()));
*rsh = new_rsh;
}
let full_client = client.as_full_client()
.ok_or_else(|| EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string()))?;

// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
if self.posdao_transition.map_or(true, |block_num| block.header.number() < block_num) {
trace!(target: "engine", "Skipping calls to POSDAO randomness and validator set contracts");
Expand All @@ -1437,12 +1459,6 @@ impl Engine<EthereumMachine> for AuthorityRound {
None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts.
};
let our_addr = signer.address();
let client = self.client.read().as_ref().and_then(|weak| weak.upgrade()).ok_or_else(|| {
debug!(target: "engine", "Unable to prepare block: missing client ref.");
EngineError::RequiresClient
})?;
let full_client = client.as_full_client()
.ok_or_else(|| EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string()))?;

// Makes a constant contract call.
let mut call = |to: Address, data: Bytes| {
Expand Down Expand Up @@ -1527,6 +1543,15 @@ impl Engine<EthereumMachine> for AuthorityRound {
self.validators.report_malicious(header.author(), set_number, header.number(), Default::default());
Err(EngineError::DoubleVote(*header.author()))?;
}
// Report malice if the validator produced other sibling blocks in the same step.
let received_step_key = (step, *header.author());
let new_hash = header.hash();
if self.received_step_hashes.read().get(&received_step_key).into_iter().any(|h| *h != new_hash) {
trace!(target: "engine", "Validator {} produced sibling blocks", header.author());
self.validators.report_malicious(header.author(), set_number, header.number(), Default::default());
} else {
self.received_step_hashes.write().insert(received_step_key, new_hash);
}

// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
Expand Down