mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 02:21:14 +00:00
New slashing mechanism (#554)
* Slashing improvements - unstake when balance too low - unstake after N slashes according to val prefs - don't early-terminate session/era unless unstaked - offline grace period before punishment * Fix warning * Cleanups and ensure slash_count decays * Bump authoring version and introduce needed authoring stub * Rename * Fix offline tracker * Fix offline tracker * Renames * Add test * Tests * Tests.
This commit is contained in:
@@ -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<bool>) {
|
||||
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.
|
||||
@@ -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<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()
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(&[])
|
||||
}
|
||||
|
||||
@@ -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<Concrete>;
|
||||
@@ -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<AccountId, SessionKey> 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;
|
||||
}
|
||||
|
||||
@@ -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 = ();
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
@@ -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_missed_proposal(data.offline_indices))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user