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

Commit e9bd41b

Browse files
andresilvaascjones
authored andcommitted
Fixes for misbehavior reporting in AuthorityRound (#8998)
* aura: only report after checking for repeated skipped primaries * aura: refactor duplicate code for getting epoch validator set * aura: verify_external: report on validator set contract instance * aura: use correct validator set epoch number when reporting * aura: use epoch set when verifying blocks * aura: report skipped primaries when generating seal * aura: handle immediate transitions * aura: don't report skipped steps from genesis to first block * aura: fix reporting test * aura: refactor duplicate code to handle immediate_transitions * aura: let reporting fail on verify_block_basic * aura: add comment about possible failure of reporting
1 parent aa67bd5 commit e9bd41b

File tree

3 files changed

+124
-82
lines changed

3 files changed

+124
-82
lines changed

ethcore/src/engines/authority_round/mod.rs

Lines changed: 117 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
//! A blockchain engine that supports a non-instant BFT proof-of-authority.
1818
19+
use std::collections::{BTreeMap, HashSet};
1920
use std::fmt;
21+
use std::iter::FromIterator;
22+
use std::ops::Deref;
2023
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
2124
use std::sync::{Weak, Arc};
2225
use std::time::{UNIX_EPOCH, SystemTime, Duration};
23-
use std::collections::{BTreeMap, HashSet};
24-
use std::iter::FromIterator;
2526

2627
use account_provider::AccountProvider;
2728
use block::*;
2829
use client::EngineClient;
2930
use engines::{Engine, Seal, EngineError, ConstructedVerifier};
3031
use engines::block_reward;
3132
use engines::block_reward::{BlockRewardContract, RewardKind};
32-
use error::{Error, BlockError};
33+
use error::{Error, ErrorKind, BlockError};
3334
use ethjson;
3435
use machine::{AuxiliaryData, Call, EthereumMachine};
3536
use hash::keccak;
@@ -572,7 +573,6 @@ fn verify_external(header: &Header, validators: &ValidatorSet, empty_steps_trans
572573

573574
if is_invalid_proposer {
574575
trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step);
575-
validators.report_benign(header.author(), header.number(), header.number());
576576
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
577577
} else {
578578
Ok(())
@@ -604,6 +604,23 @@ impl AsMillis for Duration {
604604
}
605605
}
606606

607+
// A type for storing owned or borrowed data that has a common type.
608+
// Useful for returning either a borrow or owned data from a function.
609+
enum CowLike<'a, A: 'a + ?Sized, B> {
610+
Borrowed(&'a A),
611+
Owned(B),
612+
}
613+
614+
impl<'a, A: ?Sized, B> Deref for CowLike<'a, A, B> where B: AsRef<A> {
615+
type Target = A;
616+
fn deref(&self) -> &A {
617+
match self {
618+
CowLike::Borrowed(b) => b,
619+
CowLike::Owned(o) => o.as_ref(),
620+
}
621+
}
622+
}
623+
607624
impl AuthorityRound {
608625
/// Create a new instance of AuthorityRound engine.
609626
pub fn new(our_params: AuthorityRoundParams, machine: EthereumMachine) -> Result<Arc<Self>, Error> {
@@ -653,6 +670,30 @@ impl AuthorityRound {
653670
Ok(engine)
654671
}
655672

673+
// fetch correct validator set for epoch at header, taking into account
674+
// finality of previous transitions.
675+
fn epoch_set<'a>(&'a self, header: &Header) -> Result<(CowLike<ValidatorSet, SimpleList>, BlockNumber), Error> {
676+
Ok(if self.immediate_transitions {
677+
(CowLike::Borrowed(&*self.validators), header.number())
678+
} else {
679+
let mut epoch_manager = self.epoch_manager.lock();
680+
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
681+
Some(client) => client,
682+
None => {
683+
debug!(target: "engine", "Unable to verify sig: missing client ref.");
684+
return Err(EngineError::RequiresClient.into())
685+
}
686+
};
687+
688+
if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, header) {
689+
debug!(target: "engine", "Unable to zoom to epoch.");
690+
return Err(EngineError::RequiresClient.into())
691+
}
692+
693+
(CowLike::Owned(epoch_manager.validators().clone()), epoch_manager.epoch_transition_number)
694+
})
695+
}
696+
656697
fn empty_steps(&self, from_step: U256, to_step: U256, parent_hash: H256) -> Vec<EmptyStep> {
657698
self.empty_steps.lock().iter().filter(|e| {
658699
U256::from(e.step) > from_step &&
@@ -697,6 +738,28 @@ impl AuthorityRound {
697738
}
698739
}
699740
}
741+
742+
fn report_skipped(&self, header: &Header, current_step: usize, parent_step: usize, validators: &ValidatorSet, set_number: u64) {
743+
// we're building on top of the genesis block so don't report any skipped steps
744+
if header.number() == 1 {
745+
return;
746+
}
747+
748+
if let (true, Some(me)) = (current_step > parent_step + 1, self.signer.read().address()) {
749+
debug!(target: "engine", "Author {} built block with step gap. current step: {}, parent step: {}",
750+
header.author(), current_step, parent_step);
751+
let mut reported = HashSet::new();
752+
for step in parent_step + 1..current_step {
753+
let skipped_primary = step_proposer(validators, header.parent_hash(), step);
754+
// Do not report this signer.
755+
if skipped_primary != me {
756+
// Stop reporting once validators start repeating.
757+
if !reported.insert(skipped_primary) { break; }
758+
self.validators.report_benign(&skipped_primary, set_number, header.number());
759+
}
760+
}
761+
}
762+
}
700763
}
701764

702765
fn unix_now() -> Duration {
@@ -876,32 +939,15 @@ impl Engine<EthereumMachine> for AuthorityRound {
876939
return Seal::None;
877940
}
878941

879-
// fetch correct validator set for current epoch, taking into account
880-
// finality of previous transitions.
881-
let active_set;
882-
883-
let validators = if self.immediate_transitions {
884-
&*self.validators
885-
} else {
886-
let mut epoch_manager = self.epoch_manager.lock();
887-
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
888-
Some(client) => client,
889-
None => {
890-
warn!(target: "engine", "Unable to generate seal: missing client ref.");
891-
return Seal::None;
892-
}
893-
};
894-
895-
if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, header) {
896-
debug!(target: "engine", "Unable to zoom to epoch.");
942+
let (validators, set_number) = match self.epoch_set(header) {
943+
Err(err) => {
944+
warn!(target: "engine", "Unable to generate seal: {}", err);
897945
return Seal::None;
898-
}
899-
900-
active_set = epoch_manager.validators().clone();
901-
&active_set as &_
946+
},
947+
Ok(ok) => ok,
902948
};
903949

904-
if is_step_proposer(validators, header.parent_hash(), step, header.author()) {
950+
if is_step_proposer(&*validators, header.parent_hash(), step, header.author()) {
905951
// this is guarded against by `can_propose` unless the block was signed
906952
// on the same step (implies same key) and on a different node.
907953
if parent_step == step.into() {
@@ -932,9 +978,15 @@ impl Engine<EthereumMachine> for AuthorityRound {
932978

933979
// only issue the seal if we were the first to reach the compare_and_swap.
934980
if self.step.can_propose.compare_and_swap(true, false, AtomicOrdering::SeqCst) {
935-
981+
// we can drop all accumulated empty step messages that are
982+
// older than the parent step since we're including them in
983+
// the seal
936984
self.clear_empty_steps(parent_step);
937985

986+
// report any skipped primaries between the parent block and
987+
// the block we're sealing
988+
self.report_skipped(header, step, u64::from(parent_step) as usize, &*validators, set_number);
989+
938990
let mut fields = vec![
939991
encode(&step).into_vec(),
940992
encode(&(&H520::from(signature) as &[u8])).into_vec(),
@@ -1057,13 +1109,21 @@ impl Engine<EthereumMachine> for AuthorityRound {
10571109
)));
10581110
}
10591111

1060-
// TODO [ToDr] Should this go from epoch manager?
1061-
// If yes then probably benign reporting needs to be moved further in the verification.
1062-
let set_number = header.number();
1063-
10641112
match verify_timestamp(&self.step.inner, header_step(header, self.empty_steps_transition)?) {
10651113
Err(BlockError::InvalidSeal) => {
1066-
self.validators.report_benign(header.author(), set_number, header.number());
1114+
// This check runs in Phase 1 where there is no guarantee that the parent block is
1115+
// already imported, therefore the call to `epoch_set` may fail. In that case we
1116+
// won't report the misbehavior but this is not a concern because:
1117+
// - Only authorities can report and it's expected that they'll be up-to-date and
1118+
// importing, therefore the parent header will most likely be available
1119+
// - Even if you are an authority that is syncing the chain, the contract will most
1120+
// likely ignore old reports
1121+
// - This specific check is only relevant if you're importing (since it checks
1122+
// against wall clock)
1123+
if let Ok((_, set_number)) = self.epoch_set(header) {
1124+
self.validators.report_benign(header.author(), set_number, header.number());
1125+
}
1126+
10671127
Err(BlockError::InvalidSeal.into())
10681128
}
10691129
Err(e) => Err(e.into()),
@@ -1075,8 +1135,8 @@ impl Engine<EthereumMachine> for AuthorityRound {
10751135
fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> {
10761136
let step = header_step(header, self.empty_steps_transition)?;
10771137
let parent_step = header_step(parent, self.empty_steps_transition)?;
1078-
// TODO [ToDr] Should this go from epoch manager?
1079-
let set_number = header.number();
1138+
1139+
let (validators, set_number) = self.epoch_set(header)?;
10801140

10811141
// Ensure header is from the step after parent.
10821142
if step == parent_step
@@ -1103,7 +1163,7 @@ impl Engine<EthereumMachine> for AuthorityRound {
11031163
format!("empty step proof for invalid parent hash: {:?}", empty_step.parent_hash)))?;
11041164
}
11051165

1106-
if !empty_step.verify(&*self.validators).unwrap_or(false) {
1166+
if !empty_step.verify(&*validators).unwrap_or(false) {
11071167
Err(EngineError::InsufficientProof(
11081168
format!("invalid empty step proof: {:?}", empty_step)))?;
11091169
}
@@ -1117,59 +1177,29 @@ impl Engine<EthereumMachine> for AuthorityRound {
11171177
}
11181178

11191179
} else {
1120-
// Report skipped primaries.
1121-
if let (true, Some(me)) = (step > parent_step + 1, self.signer.read().address()) {
1122-
debug!(target: "engine", "Author {} built block with step gap. current step: {}, parent step: {}",
1123-
header.author(), step, parent_step);
1124-
let mut reported = HashSet::new();
1125-
for s in parent_step + 1..step {
1126-
let skipped_primary = step_proposer(&*self.validators, &parent.hash(), s);
1127-
// Do not report this signer.
1128-
if skipped_primary != me {
1129-
self.validators.report_benign(&skipped_primary, set_number, header.number());
1130-
// Stop reporting once validators start repeating.
1131-
if !reported.insert(skipped_primary) { break; }
1132-
}
1133-
}
1134-
}
1180+
self.report_skipped(header, step, parent_step, &*validators, set_number);
11351181
}
11361182

11371183
Ok(())
11381184
}
11391185

11401186
// Check the validators.
11411187
fn verify_block_external(&self, header: &Header) -> Result<(), Error> {
1142-
// fetch correct validator set for current epoch, taking into account
1143-
// finality of previous transitions.
1144-
let active_set;
1145-
let validators = if self.immediate_transitions {
1146-
&*self.validators
1147-
} else {
1148-
// get correct validator set for epoch.
1149-
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
1150-
Some(client) => client,
1151-
None => {
1152-
debug!(target: "engine", "Unable to verify sig: missing client ref.");
1153-
return Err(EngineError::RequiresClient.into())
1154-
}
1155-
};
1156-
1157-
let mut epoch_manager = self.epoch_manager.lock();
1158-
if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, header) {
1159-
debug!(target: "engine", "Unable to zoom to epoch.");
1160-
return Err(EngineError::RequiresClient.into())
1161-
}
1162-
1163-
active_set = epoch_manager.validators().clone();
1164-
&active_set as &_
1165-
};
1188+
let (validators, set_number) = self.epoch_set(header)?;
11661189

11671190
// verify signature against fixed list, but reports should go to the
11681191
// contract itself.
1169-
let res = verify_external(header, validators, self.empty_steps_transition);
1170-
if res.is_ok() {
1171-
let header_step = header_step(header, self.empty_steps_transition)?;
1172-
self.clear_empty_steps(header_step.into());
1192+
let res = verify_external(header, &*validators, self.empty_steps_transition);
1193+
match res {
1194+
Err(Error(ErrorKind::Engine(EngineError::NotProposer(_)), _)) => {
1195+
self.validators.report_benign(header.author(), set_number, header.number());
1196+
},
1197+
Ok(_) => {
1198+
// we can drop all accumulated empty step messages that are older than this header's step
1199+
let header_step = header_step(header, self.empty_steps_transition)?;
1200+
self.clear_empty_steps(header_step.into());
1201+
},
1202+
_ => {},
11731203
}
11741204
res
11751205
}
@@ -1578,7 +1608,6 @@ mod tests {
15781608
parent_header.set_seal(vec![encode(&1usize).into_vec()]);
15791609
parent_header.set_gas_limit("222222".parse::<U256>().unwrap());
15801610
let mut header: Header = Header::default();
1581-
header.set_number(1);
15821611
header.set_gas_limit("222222".parse::<U256>().unwrap());
15831612
header.set_seal(vec![encode(&3usize).into_vec()]);
15841613

@@ -1588,8 +1617,15 @@ mod tests {
15881617

15891618
aura.set_signer(Arc::new(AccountProvider::transient_provider()), Default::default(), "".into());
15901619

1620+
// Do not report on steps skipped between genesis and first block.
1621+
header.set_number(1);
1622+
assert!(aura.verify_block_family(&header, &parent_header).is_ok());
1623+
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 0);
1624+
1625+
// Report on skipped steps otherwise.
1626+
header.set_number(2);
15911627
assert!(aura.verify_block_family(&header, &parent_header).is_ok());
1592-
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 1);
1628+
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 2);
15931629
}
15941630

15951631
#[test]

ethcore/src/engines/validator_set/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
5353
}
5454

5555
/// A validator set.
56-
pub trait ValidatorSet: Send + Sync {
56+
pub trait ValidatorSet: Send + Sync + 'static {
5757
/// Get the default "Call" helper, for use in general operation.
5858
// TODO [keorn]: this is a hack intended to migrate off of
5959
// a strict dependency on state always being available.

ethcore/src/engines/validator_set/simple_list.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ impl ValidatorSet for SimpleList {
104104
}
105105
}
106106

107+
impl AsRef<ValidatorSet> for SimpleList {
108+
fn as_ref(&self) -> &ValidatorSet {
109+
self
110+
}
111+
}
112+
107113
#[cfg(test)]
108114
mod tests {
109115
use std::str::FromStr;

0 commit comments

Comments
 (0)