diff --git a/polkadot/consensus/src/offline_tracker.rs b/polkadot/consensus/src/offline_tracker.rs index efb317ea5c..eb6c2480c4 100644 --- a/polkadot/consensus/src/offline_tracker.rs +++ b/polkadot/consensus/src/offline_tracker.rs @@ -21,14 +21,18 @@ use polkadot_primitives::AccountId; use std::collections::HashMap; use std::time::{Instant, Duration}; -// time before we report a validator. -const REPORT_TIME: Duration = Duration::from_secs(60 * 5); - struct Observed { last_round_end: Instant, offline_since: Instant, } +#[derive(Eq, PartialEq)] +enum Activity { + Offline, + StillOffline(Duration), + Online, +} + impl Observed { fn new() -> Observed { let now = Instant::now(); @@ -38,31 +42,32 @@ impl Observed { } } - fn note_round_end(&mut self, was_online: bool) { - let now = Instant::now(); - + fn note_round_end(&mut self, now: Instant, was_online: Option) { self.last_round_end = now; - if was_online { + if let Some(false) = was_online { self.offline_since = now; } } - fn is_active(&self) -> bool { + /// Returns what we have observed about the online/offline state of the validator. + fn activity(&self) -> Activity { // can happen if clocks are not monotonic - if self.offline_since > self.last_round_end { return true } - self.last_round_end.duration_since(self.offline_since) < REPORT_TIME + if self.offline_since > self.last_round_end { return Activity::Online } + if self.offline_since == self.last_round_end { return Activity::Offline } + Activity::StillOffline(self.last_round_end.duration_since(self.offline_since)) } } /// Tracks offline validators and can issue a report for those offline. pub struct OfflineTracker { observed: HashMap, + block_instant: Instant, } impl OfflineTracker { /// Create a new tracker. pub fn new() -> Self { - OfflineTracker { observed: HashMap::new() } + OfflineTracker { observed: HashMap::new(), block_instant: Instant::now() } } /// Note new consensus is starting with the given set of validators. @@ -71,23 +76,33 @@ impl OfflineTracker { let set: HashSet<_> = validators.iter().cloned().collect(); self.observed.retain(|k, _| set.contains(k)); + + self.block_instant = Instant::now(); } /// Note that a round has ended. pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) { - self.observed.entry(validator) - .or_insert_with(Observed::new) - .note_round_end(was_online); + self.observed.entry(validator).or_insert_with(Observed::new); + for (val, obs) in self.observed.iter_mut() { + obs.note_round_end( + self.block_instant, + if val == &validator { + Some(was_online) + } else { + None + } + ) + } } /// Generate a vector of indices for offline account IDs. pub fn reports(&self, validators: &[AccountId]) -> Vec { validators.iter() .enumerate() - .filter_map(|(i, v)| if self.is_online(v) { - None - } else { + .filter_map(|(i, v)| if self.is_known_offline_now(v) { Some(i as u32) + } else { + None }) .collect() } @@ -101,13 +116,15 @@ impl OfflineTracker { }; // we must think all validators reported externally are offline. - let thinks_online = self.is_online(v); - !thinks_online + self.is_known_offline_now(v) }) } - fn is_online(&self, v: &AccountId) -> bool { - self.observed.get(v).map(Observed::is_active).unwrap_or(true) + /// Rwturns true only if we have seen the validator miss the last round. For further + /// rounds where we can't say for sure that they're still offline, we give them the + /// benefit of the doubt. + fn is_known_offline_now(&self, v: &AccountId) -> bool { + self.observed.get(v).map(|o| o.activity() == Activity::Offline).unwrap_or(false) } } @@ -121,17 +138,30 @@ mod tests { let v = [0; 32].into(); let v2 = [1; 32].into(); let v3 = [2; 32].into(); + tracker.note_new_block(&[v, v2, v3]); tracker.note_round_end(v, true); tracker.note_round_end(v2, true); tracker.note_round_end(v3, true); + assert_eq!(tracker.reports(&[v, v2, v3]), vec![0u32; 0]); - let slash_time = REPORT_TIME + Duration::from_secs(5); - tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time; - tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time; + tracker.note_new_block(&[v, v2, v3]); + tracker.note_round_end(v, true); + tracker.note_round_end(v2, false); + tracker.note_round_end(v3, true); + assert_eq!(tracker.reports(&[v, v2, v3]), vec![1]); - assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]); + tracker.note_new_block(&[v, v2, v3]); + tracker.note_round_end(v, false); + assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]); - tracker.note_new_block(&[v, v3]); + tracker.note_new_block(&[v, v2, v3]); + tracker.note_round_end(v, false); + tracker.note_round_end(v2, true); + tracker.note_round_end(v3, false); + assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 2]); + + tracker.note_new_block(&[v, v2]); + tracker.note_round_end(v, false); assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]); } } diff --git a/polkadot/runtime/src/checked_block.rs b/polkadot/runtime/src/checked_block.rs index d193d26963..6ed0cee176 100644 --- a/polkadot/runtime/src/checked_block.rs +++ b/polkadot/runtime/src/checked_block.rs @@ -16,10 +16,10 @@ //! Typesafe block interaction. -use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_OFFLINE_POSITION}; +use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_MISSED_PROPOSAL_POSITION}; use timestamp::Call as TimestampCall; use parachains::Call as ParachainsCall; -use session::Call as SessionCall; +use staking::Call as StakingCall; use primitives::parachain::CandidateReceipt; /// Provides a type-safe wrapper around a structurally valid block. @@ -92,8 +92,8 @@ impl CheckedBlock { /// Extract the noted offline validator indices (if any) from the block. pub fn noted_offline(&self) -> &[u32] { - self.inner.extrinsics.get(NOTE_OFFLINE_POSITION as usize).and_then(|xt| match xt.extrinsic.function { - Call::Session(SessionCall::note_offline(ref x)) => Some(&x[..]), + self.inner.extrinsics.get(NOTE_MISSED_PROPOSAL_POSITION as usize).and_then(|xt| match xt.extrinsic.function { + Call::Staking(StakingCall::note_missed_proposal(ref x)) => Some(&x[..]), _ => None, }).unwrap_or(&[]) } diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index dfe8e61048..1762b78044 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -86,7 +86,7 @@ pub const TIMESTAMP_SET_POSITION: u32 = 0; /// The position of the parachains set extrinsic. pub const PARACHAINS_SET_POSITION: u32 = 1; /// The position of the offline nodes noting extrinsic. -pub const NOTE_OFFLINE_POSITION: u32 = 2; +pub const NOTE_MISSED_PROPOSAL_POSITION: u32 = 2; /// The address format for describing accounts. pub type Address = staking::Address; @@ -111,8 +111,8 @@ pub struct Concrete; pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: ver_str!("polkadot"), impl_name: ver_str!("parity-polkadot"), - authoring_version: 1, - spec_version: 3, + authoring_version: 2, + spec_version: 4, impl_version: 0, }; @@ -162,7 +162,7 @@ impl Convert for SessionKeyConversion { } impl session::Trait for Concrete { - const NOTE_OFFLINE_POSITION: u32 = NOTE_OFFLINE_POSITION; + const NOTE_MISSED_PROPOSAL_POSITION: u32 = NOTE_MISSED_PROPOSAL_POSITION; type ConvertAccountIdToSessionKey = SessionKeyConversion; type OnSessionChange = Staking; } diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index 16cdcdca33..8fdb2d01a7 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -265,7 +265,7 @@ mod tests { type Header = Header; } impl session::Trait for Test { - const NOTE_OFFLINE_POSITION: u32 = 1; + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = (); } diff --git a/polkadot/runtime/src/utils.rs b/polkadot/runtime/src/utils.rs index acef060925..d3bfe74494 100644 --- a/polkadot/runtime/src/utils.rs +++ b/polkadot/runtime/src/utils.rs @@ -21,7 +21,7 @@ use super::{Call, UncheckedExtrinsic, Extrinsic, Staking}; use runtime_primitives::traits::{Checkable, AuxLookup}; use timestamp::Call as TimestampCall; use parachains::Call as ParachainsCall; -use session::Call as SessionCall; +use staking::Call as StakingCall; /// Produces the list of inherent extrinsics. pub fn inherent_extrinsics(data: ::primitives::InherentData) -> Vec { @@ -41,7 +41,7 @@ pub fn inherent_extrinsics(data: ::primitives::InherentData) -> Vec