Fix Temporarily Invalid blocks handling (#7613)

* Handle temporarily invalid blocks in sync.

* Fix tests.
This commit is contained in:
Tomasz Drwięga 2018-01-19 10:38:59 +01:00 committed by Marek Kotewicz
parent 58645d3908
commit ad44855a1b
3 changed files with 57 additions and 59 deletions

View File

@ -43,7 +43,6 @@ use ethereum_types::{H256, H520, Address, U128, U256};
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use unexpected::{Mismatch, OutOfBounds}; use unexpected::{Mismatch, OutOfBounds};
use bytes::Bytes;
mod finality; mod finality;
@ -289,9 +288,11 @@ struct EpochVerifier {
impl super::EpochVerifier<EthereumMachine> for EpochVerifier { impl super::EpochVerifier<EthereumMachine> for EpochVerifier {
fn verify_light(&self, header: &Header) -> Result<(), Error> { fn verify_light(&self, header: &Header) -> Result<(), Error> {
// Validate the timestamp
verify_timestamp(&*self.step, header_step(header)?)?;
// always check the seal since it's fast. // always check the seal since it's fast.
// nothing heavier to do. // nothing heavier to do.
verify_external(header, &self.subchain_validators, &*self.step, |_| {}) verify_external(header, &self.subchain_validators)
} }
fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> { fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
@ -315,7 +316,7 @@ impl super::EpochVerifier<EthereumMachine> for EpochVerifier {
// //
// `verify_external` checks that signature is correct and author == signer. // `verify_external` checks that signature is correct and author == signer.
if header.seal().len() != 2 { return None } if header.seal().len() != 2 { return None }
otry!(verify_external(header, &self.subchain_validators, &*self.step, |_| {}).ok()); otry!(verify_external(header, &self.subchain_validators).ok());
let newly_finalized = otry!(finality_checker.push_hash(header.hash(), header.author().clone()).ok()); let newly_finalized = otry!(finality_checker.push_hash(header.hash(), header.author().clone()).ok());
finalized.extend(newly_finalized); finalized.extend(newly_finalized);
@ -325,16 +326,6 @@ impl super::EpochVerifier<EthereumMachine> for EpochVerifier {
} }
} }
// Report misbehavior
#[derive(Debug)]
#[allow(dead_code)]
enum Report {
// Malicious behavior
Malicious(Address, BlockNumber, Bytes),
// benign misbehavior
Benign(Address, BlockNumber),
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> { fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val() UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val()
} }
@ -353,34 +344,35 @@ fn is_step_proposer(validators: &ValidatorSet, bh: &H256, step: usize, address:
step_proposer(validators, bh, step) == *address step_proposer(validators, bh, step) == *address
} }
fn verify_external<F: Fn(Report)>(header: &Header, validators: &ValidatorSet, step: &Step, report: F) fn verify_timestamp(step: &Step, header_step: usize) -> Result<(), BlockError> {
-> Result<(), Error>
{
let header_step = header_step(header)?;
match step.check_future(header_step) { match step.check_future(header_step) {
Err(None) => { Err(None) => {
trace!(target: "engine", "verify_block_external: block from the future"); trace!(target: "engine", "verify_timestamp: block from the future");
report(Report::Benign(*header.author(), header.number())); Err(BlockError::InvalidSeal.into())
return Err(BlockError::InvalidSeal.into())
}, },
Err(Some(oob)) => { Err(Some(oob)) => {
trace!(target: "engine", "verify_block_external: block too early"); // NOTE This error might be returned only in early stage of verification (Stage 1).
return Err(BlockError::TemporarilyInvalid(oob).into()) // Returning it further won't recover the sync process.
trace!(target: "engine", "verify_timestamp: block too early");
Err(BlockError::TemporarilyInvalid(oob).into())
}, },
Ok(_) => { Ok(_) => Ok(()),
let proposer_signature = header_signature(header)?; }
let correct_proposer = validators.get(header.parent_hash(), header_step); }
let is_invalid_proposer = *header.author() != correct_proposer ||
!verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?;
if is_invalid_proposer { fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Error> {
trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step); let header_step = header_step(header)?;
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
} else { let proposer_signature = header_signature(header)?;
Ok(()) let correct_proposer = validators.get(header.parent_hash(), header_step);
} let is_invalid_proposer = *header.author() != correct_proposer ||
} !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?;
if is_invalid_proposer {
trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
} else {
Ok(())
} }
} }
@ -653,26 +645,38 @@ impl Engine<EthereumMachine> for AuthorityRound {
/// Check the number of seal fields. /// Check the number of seal fields.
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> { fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) { if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) {
Err(From::from(BlockError::DifficultyOutOfBounds( return Err(From::from(BlockError::DifficultyOutOfBounds(
OutOfBounds { min: None, max: Some(U256::from(U128::max_value())), found: *header.difficulty() } OutOfBounds { min: None, max: Some(U256::from(U128::max_value())), found: *header.difficulty() }
))) )));
} else { }
Ok(())
// TODO [ToDr] Should this go from epoch manager?
// If yes then probably benign reporting needs to be moved further in the verification.
let set_number = header.number();
match verify_timestamp(&*self.step, header_step(header)?) {
Err(BlockError::InvalidSeal) => {
self.validators.report_benign(header.author(), set_number, header.number());
Err(BlockError::InvalidSeal.into())
}
Err(e) => Err(e.into()),
Ok(()) => Ok(()),
} }
} }
/// Do the step and gas limit validation. /// Do the step and gas limit validation.
fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> { fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> {
let step = header_step(header)?; let step = header_step(header)?;
let parent_step = header_step(parent)?; let parent_step = header_step(parent)?;
// TODO [ToDr] Should this go from epoch manager?
let set_number = header.number();
// Ensure header is from the step after parent. // Ensure header is from the step after parent.
if step == parent_step if step == parent_step
|| (header.number() >= self.validate_step_transition && step <= parent_step) { || (header.number() >= self.validate_step_transition && step <= parent_step) {
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step); trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default()); self.validators.report_malicious(header.author(), set_number, header.number(), Default::default());
Err(EngineError::DoubleVote(header.author().clone()))?; Err(EngineError::DoubleVote(header.author().clone()))?;
} }
@ -685,7 +689,7 @@ impl Engine<EthereumMachine> for AuthorityRound {
let skipped_primary = step_proposer(&*self.validators, &parent.hash(), s); let skipped_primary = step_proposer(&*self.validators, &parent.hash(), s);
// Do not report this signer. // Do not report this signer.
if skipped_primary != me { if skipped_primary != me {
self.validators.report_benign(&skipped_primary, header.number(), header.number()); self.validators.report_benign(&skipped_primary, set_number, header.number());
} }
// Stop reporting once validators start repeating. // Stop reporting once validators start repeating.
if !reported.insert(skipped_primary) { break; } if !reported.insert(skipped_primary) { break; }
@ -700,9 +704,8 @@ impl Engine<EthereumMachine> for AuthorityRound {
// fetch correct validator set for current epoch, taking into account // fetch correct validator set for current epoch, taking into account
// finality of previous transitions. // finality of previous transitions.
let active_set; let active_set;
let validators = if self.immediate_transitions {
let (validators, set_number) = if self.immediate_transitions { &*self.validators
(&*self.validators, header.number())
} else { } else {
// get correct validator set for epoch. // get correct validator set for epoch.
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
@ -720,21 +723,12 @@ impl Engine<EthereumMachine> for AuthorityRound {
} }
active_set = epoch_manager.validators().clone(); active_set = epoch_manager.validators().clone();
(&active_set as &_, epoch_manager.epoch_transition_number) &active_set as &_
};
// always report with "self.validators" so that the report actually gets
// to the contract.
let report = |report| match report {
Report::Benign(address, block_number) =>
self.validators.report_benign(&address, set_number, block_number),
Report::Malicious(address, block_number, proof) =>
self.validators.report_malicious(&address, set_number, block_number, proof),
}; };
// verify signature against fixed list, but reports should go to the // verify signature against fixed list, but reports should go to the
// contract itself. // contract itself.
verify_external(header, validators, &*self.step, report) verify_external(header, validators)
} }
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> { fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
@ -1056,8 +1050,7 @@ mod tests {
assert!(engine.verify_block_family(&header, &parent_header).is_ok()); assert!(engine.verify_block_family(&header, &parent_header).is_ok());
assert!(engine.verify_block_external(&header).is_ok()); assert!(engine.verify_block_external(&header).is_ok());
header.set_seal(vec![encode(&5usize).into_vec(), encode(&(&*signature as &[u8])).into_vec()]); header.set_seal(vec![encode(&5usize).into_vec(), encode(&(&*signature as &[u8])).into_vec()]);
assert!(engine.verify_block_family(&header, &parent_header).is_ok()); assert!(engine.verify_block_basic(&header).is_err());
assert!(engine.verify_block_external(&header).is_err());
} }
#[test] #[test]
@ -1197,3 +1190,4 @@ mod tests {
AuthorityRound::new(params, machine).unwrap(); AuthorityRound::new(params, machine).unwrap();
} }
} }

View File

@ -190,7 +190,7 @@ mod tests {
header.set_number(2); header.set_number(2);
header.set_parent_hash(client.chain_info().best_block_hash); header.set_parent_hash(client.chain_info().best_block_hash);
// `reportBenign` when the designated proposer releases block from the future (bad clock). // `reportBenign` when the designated proposer releases block from the future (bad clock).
assert!(client.engine().verify_block_external(&header).is_err()); assert!(client.engine().verify_block_basic(&header).is_err());
// Seal a block. // Seal a block.
client.engine().step(); client.engine().step();
assert_eq!(client.chain_info().best_block_number, 1); assert_eq!(client.chain_info().best_block_number, 1);

View File

@ -522,6 +522,10 @@ impl BlockDownloader {
trace!(target: "sync", "Unknown new block parent, restarting sync"); trace!(target: "sync", "Unknown new block parent, restarting sync");
break; break;
}, },
Err(BlockImportError::Block(BlockError::TemporarilyInvalid(_))) => {
debug!(target: "sync", "Block temporarily invalid, restarting sync");
break;
},
Err(e) => { Err(e) => {
debug!(target: "sync", "Bad block {:?} : {:?}", h, e); debug!(target: "sync", "Bad block {:?} : {:?}", h, e);
bad = true; bad = true;