Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

New slashing mechanism #554

Merged
merged 11 commits into from
Aug 15, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 56 additions & 26 deletions polkadot/consensus/src/offline_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<bool>) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we provide now in order to ensure that all rounds in the same block are considered contemporaneous, which lets us group which validators were offline for the previous block specifically.

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<AccountId, Observed>,
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.
Expand All @@ -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() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all validators are updated with the current time so that we can see which of them were inactive in specifially the previous block.

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<u32> {
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()
}
Expand All @@ -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)
}
}

Expand All @@ -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]);

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]);

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, false);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);

assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
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, v3]);
tracker.note_new_block(&[v, v2]);
tracker.note_round_end(v, false);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
}
}
4 changes: 2 additions & 2 deletions polkadot/runtime/src/checked_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_OFFLINE_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.
Expand Down Expand Up @@ -93,7 +93,7 @@ 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[..]),
Call::Staking(StakingCall::note_offline(ref x)) => Some(&x[..]),
_ => None,
}).unwrap_or(&[])
}
Expand Down
4 changes: 2 additions & 2 deletions polkadot/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
4 changes: 2 additions & 2 deletions polkadot/runtime/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UncheckedExtrinsic> {
Expand All @@ -41,7 +41,7 @@ pub fn inherent_extrinsics(data: ::primitives::InherentData) -> Vec<UncheckedExt

if !data.offline_indices.is_empty() {
inherent.push(make_inherent(
Call::Session(SessionCall::note_offline(data.offline_indices))
Call::Staking(StakingCall::note_offline(data.offline_indices))
));
}

Expand Down
5 changes: 4 additions & 1 deletion substrate/runtime/primitives/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ use codec::{Codec, Encode};
pub use integer_sqrt::IntegerSquareRoot;
pub use num_traits::{Zero, One, Bounded};
pub use num_traits::ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv};
use rstd::ops::{Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign, RemAssign};
use rstd::ops::{Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign,
RemAssign, Shl, Shr};

/// A lazy value.
pub trait Lazy<T: ?Sized> {
Expand Down Expand Up @@ -132,6 +133,7 @@ pub trait SimpleArithmetic:
Mul<Self, Output = Self> + MulAssign<Self> +
Div<Self, Output = Self> + DivAssign<Self> +
Rem<Self, Output = Self> + RemAssign<Self> +
Shl<u32, Output = Self> + Shr<u32, Output = Self> +
CheckedAdd +
CheckedSub +
CheckedMul +
Expand All @@ -145,6 +147,7 @@ impl<T:
Mul<Self, Output = Self> + MulAssign<Self> +
Div<Self, Output = Self> + DivAssign<Self> +
Rem<Self, Output = Self> + RemAssign<Self> +
Shl<u32, Output = Self> + Shr<u32, Output = Self> +
CheckedAdd +
CheckedSub +
CheckedMul +
Expand Down
26 changes: 13 additions & 13 deletions substrate/runtime/session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,21 @@ use runtime_support::{StorageValue, StorageMap};
use runtime_support::dispatch::Result;

/// A session has changed.
pub trait OnSessionChange<T, A> {
pub trait OnSessionChange<T> {
/// Session has changed.
fn on_session_change(time_elapsed: T, bad_validators: Vec<A>);
fn on_session_change(time_elapsed: T, should_reward: bool);
}

impl<T, A> OnSessionChange<T, A> for () {
fn on_session_change(_: T, _: Vec<A>) {}
impl<T> OnSessionChange<T> for () {
fn on_session_change(_: T, _: bool) {}
}

pub trait Trait: timestamp::Trait {
// the position of the required timestamp-set extrinsic.
const NOTE_OFFLINE_POSITION: u32;

type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Self::SessionKey>;
type OnSessionChange: OnSessionChange<Self::Moment, Self::AccountId>;
type OnSessionChange: OnSessionChange<Self::Moment>;
}

decl_module! {
Expand All @@ -80,7 +80,7 @@ decl_module! {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum PrivCall {
fn set_length(new: T::BlockNumber) -> Result = 0;
fn force_new_session(normal_rotation: bool) -> Result = 1;
fn force_new_session(apply_rewards: bool) -> Result = 1;
}
}

Expand Down Expand Up @@ -139,8 +139,8 @@ impl<T: Trait> Module<T> {
}

/// Forces a new session.
pub fn force_new_session(normal_rotation: bool) -> Result {
<ForcingNewSession<T>>::put(normal_rotation);
pub fn force_new_session(apply_rewards: bool) -> Result {
<ForcingNewSession<T>>::put(apply_rewards);
Ok(())
}

Expand Down Expand Up @@ -178,15 +178,15 @@ impl<T: Trait> Module<T> {
// check block number and call next_session if necessary.
let block_number = <system::Module<T>>::block_number();
let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero();
let bad_validators = <BadValidators<T>>::take().unwrap_or_default();
let should_end_session = <ForcingNewSession<T>>::take().is_some() || !bad_validators.is_empty() || is_final_block;
let (should_end_session, apply_rewards) = <ForcingNewSession<T>>::take()
.map_or((is_final_block, is_final_block), |apply_rewards| (true, apply_rewards));
if should_end_session {
Self::rotate_session(is_final_block, bad_validators);
Self::rotate_session(is_final_block, apply_rewards);
}
}

/// Move onto next session: register the new authority set.
pub fn rotate_session(is_final_block: bool, bad_validators: Vec<T::AccountId>) {
pub fn rotate_session(is_final_block: bool, apply_rewards: bool) {
let now = <timestamp::Module<T>>::get();
let time_elapsed = now.clone() - Self::current_start();

Expand All @@ -206,7 +206,7 @@ impl<T: Trait> Module<T> {
<LastLengthChange<T>>::put(block_number);
}

T::OnSessionChange::on_session_change(time_elapsed, bad_validators);
T::OnSessionChange::on_session_change(time_elapsed, apply_rewards);

// Update any changes in session keys.
Self::validators().iter().enumerate().for_each(|(i, v)| {
Expand Down
Loading