|
|
|
|
@@ -44,7 +44,8 @@ use bigint::hash::{H256, H520};
|
|
|
|
|
use semantic_version::SemanticVersion;
|
|
|
|
|
use parking_lot::{Mutex, RwLock};
|
|
|
|
|
use unexpected::{Mismatch, OutOfBounds};
|
|
|
|
|
use util::Address;
|
|
|
|
|
use util::*;
|
|
|
|
|
use bytes::Bytes;
|
|
|
|
|
|
|
|
|
|
mod finality;
|
|
|
|
|
|
|
|
|
|
@@ -290,11 +291,9 @@ struct EpochVerifier {
|
|
|
|
|
|
|
|
|
|
impl super::EpochVerifier<EthereumMachine> for EpochVerifier {
|
|
|
|
|
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.
|
|
|
|
|
// nothing heavier to do.
|
|
|
|
|
verify_external(header, &self.subchain_validators)
|
|
|
|
|
verify_external(header, &self.subchain_validators, &*self.step, |_| {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
|
|
|
|
|
@@ -318,7 +317,7 @@ impl super::EpochVerifier<EthereumMachine> for EpochVerifier {
|
|
|
|
|
//
|
|
|
|
|
// `verify_external` checks that signature is correct and author == signer.
|
|
|
|
|
if header.seal().len() != 2 { return None }
|
|
|
|
|
otry!(verify_external(header, &self.subchain_validators).ok());
|
|
|
|
|
otry!(verify_external(header, &self.subchain_validators, &*self.step, |_| {}).ok());
|
|
|
|
|
|
|
|
|
|
let newly_finalized = otry!(finality_checker.push_hash(header.hash(), header.author().clone()).ok());
|
|
|
|
|
finalized.extend(newly_finalized);
|
|
|
|
|
@@ -328,6 +327,16 @@ 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> {
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
@@ -346,35 +355,34 @@ fn is_step_proposer(validators: &ValidatorSet, bh: &H256, step: usize, address:
|
|
|
|
|
step_proposer(validators, bh, step) == *address
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_timestamp(step: &Step, header_step: usize) -> Result<(), BlockError> {
|
|
|
|
|
match step.check_future(header_step) {
|
|
|
|
|
Err(None) => {
|
|
|
|
|
trace!(target: "engine", "verify_timestamp: block from the future");
|
|
|
|
|
Err(BlockError::InvalidSeal.into())
|
|
|
|
|
},
|
|
|
|
|
Err(Some(oob)) => {
|
|
|
|
|
// NOTE This error might be returned only in early stage of verification (Stage 1).
|
|
|
|
|
// Returning it further won't recover the sync process.
|
|
|
|
|
trace!(target: "engine", "verify_timestamp: block too early");
|
|
|
|
|
Err(BlockError::TemporarilyInvalid(oob).into())
|
|
|
|
|
},
|
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Error> {
|
|
|
|
|
fn verify_external<F: Fn(Report)>(header: &Header, validators: &ValidatorSet, step: &Step, report: F)
|
|
|
|
|
-> Result<(), Error>
|
|
|
|
|
{
|
|
|
|
|
let header_step = header_step(header)?;
|
|
|
|
|
|
|
|
|
|
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())?;
|
|
|
|
|
match step.check_future(header_step) {
|
|
|
|
|
Err(None) => {
|
|
|
|
|
trace!(target: "engine", "verify_block_external: block from the future");
|
|
|
|
|
report(Report::Benign(*header.author(), header.number()));
|
|
|
|
|
return Err(BlockError::InvalidSeal.into())
|
|
|
|
|
},
|
|
|
|
|
Err(Some(oob)) => {
|
|
|
|
|
trace!(target: "engine", "verify_block_external: block too early");
|
|
|
|
|
return Err(BlockError::TemporarilyInvalid(oob).into())
|
|
|
|
|
},
|
|
|
|
|
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 {
|
|
|
|
|
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(())
|
|
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -647,38 +655,26 @@ impl Engine<EthereumMachine> for AuthorityRound {
|
|
|
|
|
/// Check the number of seal fields.
|
|
|
|
|
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
|
|
|
|
|
if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) {
|
|
|
|
|
return Err(From::from(BlockError::DifficultyOutOfBounds(
|
|
|
|
|
Err(From::from(BlockError::DifficultyOutOfBounds(
|
|
|
|
|
OutOfBounds { min: None, max: Some(U256::from(U128::max_value())), found: *header.difficulty() }
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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(()),
|
|
|
|
|
)))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Do the step and gas limit validation.
|
|
|
|
|
fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> {
|
|
|
|
|
let step = header_step(header)?;
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
if step == parent_step
|
|
|
|
|
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
|
|
|
|
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
|
|
|
|
|
|
|
|
|
self.validators.report_malicious(header.author(), set_number, header.number(), Default::default());
|
|
|
|
|
self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default());
|
|
|
|
|
Err(EngineError::DoubleVote(header.author().clone()))?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -691,7 +687,7 @@ impl Engine<EthereumMachine> for AuthorityRound {
|
|
|
|
|
let skipped_primary = step_proposer(&*self.validators, &parent.hash(), s);
|
|
|
|
|
// Do not report this signer.
|
|
|
|
|
if skipped_primary != me {
|
|
|
|
|
self.validators.report_benign(&skipped_primary, set_number, header.number());
|
|
|
|
|
self.validators.report_benign(&skipped_primary, header.number(), header.number());
|
|
|
|
|
}
|
|
|
|
|
// Stop reporting once validators start repeating.
|
|
|
|
|
if !reported.insert(skipped_primary) { break; }
|
|
|
|
|
@@ -706,8 +702,9 @@ impl Engine<EthereumMachine> for AuthorityRound {
|
|
|
|
|
// fetch correct validator set for current epoch, taking into account
|
|
|
|
|
// finality of previous transitions.
|
|
|
|
|
let active_set;
|
|
|
|
|
let validators = if self.immediate_transitions {
|
|
|
|
|
&*self.validators
|
|
|
|
|
|
|
|
|
|
let (validators, set_number) = if self.immediate_transitions {
|
|
|
|
|
(&*self.validators, header.number())
|
|
|
|
|
} else {
|
|
|
|
|
// get correct validator set for epoch.
|
|
|
|
|
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
|
|
|
|
|
@@ -725,12 +722,21 @@ impl Engine<EthereumMachine> for AuthorityRound {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
active_set = epoch_manager.validators().clone();
|
|
|
|
|
&active_set as &_
|
|
|
|
|
(&active_set as &_, epoch_manager.epoch_transition_number)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// contract itself.
|
|
|
|
|
verify_external(header, validators)
|
|
|
|
|
verify_external(header, validators, &*self.step, report)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
|
|
|
|
|
@@ -1053,7 +1059,8 @@ mod tests {
|
|
|
|
|
assert!(engine.verify_block_family(&header, &parent_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()]);
|
|
|
|
|
assert!(engine.verify_block_basic(&header).is_err());
|
|
|
|
|
assert!(engine.verify_block_family(&header, &parent_header).is_ok());
|
|
|
|
|
assert!(engine.verify_block_external(&header).is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
@@ -1193,4 +1200,3 @@ mod tests {
|
|
|
|
|
AuthorityRound::new(params, machine).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|