diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index e34c4ea30b8..f724a656b83 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -545,6 +545,8 @@ pub struct AuthorityRound { posdao_transition: Option, /// The addresses of a contracts that determine the block gas limit. block_gas_limit_contract_transitions: BTreeMap, + /// History of step hashes recently received from peers. + received_step_hashes: RwLock>, } // header-chain validator. @@ -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 { 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() @@ -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. @@ -1425,6 +1428,25 @@ impl Engine for AuthorityRound { /// Make calls to the randomness and validator set contracts. fn on_prepare_block(&self, block: &ExecutedBlock) -> Result, 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"); @@ -1437,12 +1459,6 @@ impl Engine 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| { @@ -1527,6 +1543,15 @@ impl Engine 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.