From aed14673b9c0d3641aa1c25e5920288d12f24a32 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 10 Jul 2019 11:36:28 +0200 Subject: [PATCH 01/66] very ugly draft --- Cargo.lock | 1 + core/consensus/babe/Cargo.toml | 1 + core/consensus/babe/src/lib.rs | 26 +++++- core/consensus/babe/src/slash.rs | 143 +++++++++++++++++++++++++++++++ srml/staking/src/lib.rs | 13 ++- srml/support/src/traits.rs | 18 ++++ 6 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 core/consensus/babe/src/slash.rs diff --git a/Cargo.lock b/Cargo.lock index 08cfbc63ae3a8..fde7364806990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4227,6 +4227,7 @@ dependencies = [ "sr-version 2.0.0", "srml-babe 2.0.0", "srml-support 2.0.0", + "srml-system 2.0.0", "substrate-client 2.0.0", "substrate-consensus-babe-primitives 2.0.0", "substrate-consensus-common 2.0.0", diff --git a/core/consensus/babe/Cargo.toml b/core/consensus/babe/Cargo.toml index 4954a7c5526fd..ad9e5b160c77d 100644 --- a/core/consensus/babe/Cargo.toml +++ b/core/consensus/babe/Cargo.toml @@ -15,6 +15,7 @@ runtime_io = { package = "sr-io", path = "../../sr-io" } inherents = { package = "substrate-inherents", path = "../../inherents" } substrate-telemetry = { path = "../../telemetry" } srml-babe = { path = "../../../srml/babe" } +srml-system = { path = "../../../srml/system" } client = { package = "substrate-client", path = "../../client" } consensus_common = { package = "substrate-consensus-common", path = "../common" } slots = { package = "substrate-consensus-slots", path = "../slots" } diff --git a/core/consensus/babe/src/lib.rs b/core/consensus/babe/src/lib.rs index 853b1c91bf1b6..c095f4068d1d7 100644 --- a/core/consensus/babe/src/lib.rs +++ b/core/consensus/babe/src/lib.rs @@ -23,10 +23,11 @@ //! This crate is highly unstable and experimental. Breaking changes may //! happen at any point. This crate is also missing features, such as banning //! of malicious validators, that are essential for a production network. -#![forbid(unsafe_code, missing_docs, unused_must_use)] -#![cfg_attr(not(test), forbid(dead_code))] +// #![forbid(unsafe_code, missing_docs, unused_must_use)] +// #![cfg_attr(not(test), forbid(dead_code))] extern crate core; mod digest; +mod slash; use digest::CompatibleDigestItem; pub use digest::{BabePreDigest, BABE_VRF_PREFIX}; pub use babe_primitives::*; @@ -87,6 +88,7 @@ use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible, SignedDuration}; pub use babe_primitives::AuthorityId; + /// A slot duration. Create with `get_or_compute`. // FIXME: Once Rust has higher-kinded types, the duplication between this // and `super::babe::Config` can be eliminated. @@ -121,6 +123,7 @@ impl Config { } } + impl SlotCompatible for BabeLink { fn extract_timestamp_and_slot( &self, @@ -502,6 +505,25 @@ fn check_header( &header, author, ).map_err(|e| e.to_string())? { + + + // @niklasad1: temp just to check the everything fits together + //.... + //..... + // This will report a misconduct and slash + { + let _e = slash::EquivocationProof::new( + equivocation_proof.fst_header().hash(), + equivocation_proof.fst_header().hash(), + Default::default(), + 0, + ); + + // T: type that implements system::Trait + // slash::BabeSlashReporter::::report_equivocation(e); + } + + info!( "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}", author, diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs new file mode 100644 index 0000000000000..de361259e1516 --- /dev/null +++ b/core/consensus/babe/src/slash.rs @@ -0,0 +1,143 @@ +//! Niklas temp file + +use parity_codec::{Decode, Encode}; +use primitives::sr25519; +use runtime_support::{ + StorageValue, dispatch::Result, decl_module, decl_storage, decl_event, + traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, MisbehaviorKind} +}; +use runtime_primitives::traits::Hash; +use std::marker::PhantomData; + +// tmp +type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; +type Key = sr25519::Public; + +/// Trait for reporting slashes +pub trait Trait: srml_system::Trait { + /// Key owner + type KeyOwner: KeyOwnerProofSystem; + /// Type of slash + type EquivocationSlash: ReportSlash>; +} + + +/// Represents an Babe equivocation proof. +#[derive(Debug, Clone, Encode, Decode, PartialEq)] +pub struct EquivocationProof { + first_header: H, + second_header: H, + author: sr25519::Public, + membership_proof: Proof, +} + +impl EquivocationProof { + /// Create a new Babe equivocation proof. + pub fn new( + first_header: H, + second_header: H, + author: sr25519::Public, + membership_proof: P, + ) -> Self { + Self { + first_header, + second_header, + author, + membership_proof, + } + } +} + +/// Babe slash reporter +pub struct BabeSlashReporter(PhantomData); + +impl BabeSlashReporter { + + /// Report an equivocation + pub fn report_equivocation( + proof: EquivocationProof< + T::Hash, + <::KeyOwner as KeyOwnerProofSystem>::Proof + >, + ) { + let identification = T::KeyOwner::check_proof( + proof.author.clone(), + proof.membership_proof + ).expect("fixme"); + + //check headers + + let hash = T::Hashing::hash_of(&proof.author); + T::EquivocationSlash::slash(hash, identification); + } +} + +decl_storage! { + trait Store for RollingMisconduct as Rolling { + /// Misbehavior reports + MisconductReports: Vec<(u64, u64)>; + } +} + +decl_module! { + pub struct RollingMisconduct for enum Call where origin: T::Origin { } +} + + +impl RollingMisconduct { + // On startup make sure no misconducts are reported + fn on_initialize() { + MisconductReports::mutate(|mr| { + mr.clear(); + }); + } + + // Remove items that doesn't fit into the rolling window + fn on_finalize() { + const DUMMY_WINDOW_LENGTH: usize = 100; + + MisconductReports::mutate(|mr| { + if mr.len() > DUMMY_WINDOW_LENGTH { + let remove = mr.len() - DUMMY_WINDOW_LENGTH; + mr.drain(..remove); + } + }); + } + + // note a misbehavior of `kind` and return the number of times it's happened + // (including now) in the rolling window. + fn report_misbehavior(base: u64, kind: u64) -> u64 { + MisconductReports::mutate(|mr| { + mr.push((base, kind)); + mr.iter().filter(|(_, k)| k == &kind).count() as u64 + }) + } +} + +struct MyMisconduct { + _t: PhantomData, + ds: PhantomData, +} + +impl MyMisconduct { + fn kind() -> u64 { + 0 + } + + fn base_severity() -> u64 { + 0 + } +} + +impl ReportSlash for MyMisconduct +where + DS: DoSlash +{ + fn slash(_footprint: T::Hash, who: Who) { + let kind = Self::kind(); + let base_seve = Self::base_severity(); + // do something special with severity + let severity = RollingMisconduct::::report_misbehavior(base_seve, kind); + DS::do_slash(who, severity); + } +} diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 71164c0a9c782..0aeee380a25f5 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -278,7 +278,7 @@ use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, - WithdrawReasons, OnUnbalanced, Imbalance, Get + WithdrawReasons, OnUnbalanced, Imbalance, Get, DoSlash } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; @@ -1349,3 +1349,14 @@ impl SelectInitialValidators for Module { >::select_validators().1 } } + + +/// StakingSlasher +pub struct StakingSlasher(rstd::marker::PhantomData); + + +impl DoSlash)> for StakingSlasher { + fn do_slash(severity: Severity, (who, exposure): (T::AccountId, Exposure)) { + // implement + } +} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index ae33aeff464d4..88fadf0591447 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -630,3 +630,21 @@ bitmask! { } } +/// A generic trait for reporting slashing violations. +pub trait ReportSlash { + fn slash(misbehavior_footprint: Hash, identfication: Identification); +} + +/// A generic trait for enacting slashes. +pub trait DoSlash { + fn do_slash(severity: Severity, identification: Identification); +} + +/// temp, obviosly not a trait +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +pub enum MisbehaviorKind { + /// .. + Equivocation, + /// .. + InvalidBlock, +} From 89f127c8ede5b9e81fb066c954453c968052330c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 10 Jul 2019 15:33:23 +0200 Subject: [PATCH 02/66] progress --- core/consensus/babe/src/slash.rs | 173 ++++++++++++++++++++++++------- srml/support/src/traits.rs | 8 +- 2 files changed, 140 insertions(+), 41 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index de361259e1516..24c54922cb080 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -2,26 +2,33 @@ use parity_codec::{Decode, Encode}; use primitives::sr25519; + use runtime_support::{ - StorageValue, dispatch::Result, decl_module, decl_storage, decl_event, + StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result, decl_module, decl_storage, decl_event, traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, MisbehaviorKind} }; -use runtime_primitives::traits::Hash; +use runtime_primitives::{Perbill, traits::Hash}; use std::marker::PhantomData; // tmp type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; type Key = sr25519::Public; +const DEFAULT_WINDOW_LENGTH: u64 = 100; + /// Trait for reporting slashes pub trait Trait: srml_system::Trait { - /// Key owner + /// Key that identifies the owner type KeyOwner: KeyOwnerProofSystem; - /// Type of slash + /// Type of slashing + /// + /// FullId - is the full identification of the entity to slash + /// which should in most cases be (AccountId, Exposure) + /// + /// type EquivocationSlash: ReportSlash>; } - /// Represents an Babe equivocation proof. #[derive(Debug, Clone, Encode, Decode, PartialEq)] pub struct EquivocationProof { @@ -72,10 +79,31 @@ impl BabeSlashReporter { } } +type Kind = u64; +type Counter = u64; + decl_storage! { - trait Store for RollingMisconduct as Rolling { + trait Store for RollingMisconduct as R { /// Misbehavior reports - MisconductReports: Vec<(u64, u64)>; + /// + // TODO(niklasad1): This is not very good because it doesn't + // _really_ works porportially w.r.t. to the session it happend in + // just the window will be shrinked when it exceeds to threshold + // + // On the other hand, one could have + // Kind -> Vec but then the problem is that + // `SessionIndex` is a counter that is wrapping and might be + // hard to determine which is `fresh` + MisconductReports get(kind): linked_map Kind => Counter; + + /// Rolling window length (we need different windows for different kinds) + WindowLength get(w) config(): u64 = DEFAULT_WINDOW_LENGTH; + + /// Some misconduct need to keep track of duplicate misconducts + /// Such as only one is counted in the same session and so on. + /// + // TODO(niklasad1): implement... + SlashDeduplicator get(sd) config(): u64 = DEFAULT_WINDOW_LENGTH; } } @@ -85,47 +113,42 @@ decl_module! { impl RollingMisconduct { - // On startup make sure no misconducts are reported - fn on_initialize() { - MisconductReports::mutate(|mr| { - mr.clear(); - }); - } + /// On startup make sure no misconducts are reported + pub fn on_initialize() {} - // Remove items that doesn't fit into the rolling window - fn on_finalize() { - const DUMMY_WINDOW_LENGTH: usize = 100; + /// Set rolling window length + pub fn set_window_length(len: u64) { + WindowLength::put(len); + } + + /// Remove items that doesn't fit into the rolling window + pub fn on_session_end() { + let max_window = WindowLength::get(); - MisconductReports::mutate(|mr| { - if mr.len() > DUMMY_WINDOW_LENGTH { - let remove = mr.len() - DUMMY_WINDOW_LENGTH; - mr.drain(..remove); - } - }); + // dummy thing lets assume that exactly 20 kinds + for kind in 0..20 { + MisconductReports::mutate(kind, |w| std::cmp::min(max_window, *w)); + } } - // note a misbehavior of `kind` and return the number of times it's happened - // (including now) in the rolling window. - fn report_misbehavior(base: u64, kind: u64) -> u64 { - MisconductReports::mutate(|mr| { - mr.push((base, kind)); - mr.iter().filter(|(_, k)| k == &kind).count() as u64 - }) + /// Report misbehaviour for a misconduct kind + /// + /// Return number of misbehavior in the current window + pub fn report_misbehavior(kind: u64) -> u64 { + MisconductReports::mutate(kind, |w| w.saturating_add(1)); + MisconductReports::get(kind) } } -struct MyMisconduct { - _t: PhantomData, - ds: PhantomData, -} +pub struct MyMisconduct((PhantomData, PhantomData)); impl MyMisconduct { fn kind() -> u64 { 0 } - fn base_severity() -> u64 { - 0 + fn base_severity() -> Perbill { + Perbill::from_rational_approximation(1_u32, 100_u32) } } @@ -137,7 +160,83 @@ where let kind = Self::kind(); let base_seve = Self::base_severity(); // do something special with severity - let severity = RollingMisconduct::::report_misbehavior(base_seve, kind); - DS::do_slash(who, severity); + // such as + // if severity > BIG then slash something like 100% + // else if severity > SMALL then compute some severity level and slash + // if severity < LESS -> slash little + panic!("time to slash"); + let severity = RollingMisconduct::::report_misbehavior(kind); + DS::do_slash(who, 0); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use srml_system as system; + use runtime_primitives::traits::{BlakeTwo256, IdentityLookup}; + use runtime_primitives::testing::{H256, Header}; + use runtime_support::impl_outer_origin; + + pub type AccountId = u64; + pub type Exposure = u64; + + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct Test; + + impl_outer_origin!{ + pub enum Origin for Test {} + } + + impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + } + + impl Trait for Test { + type KeyOwner = FakeSlasher; + type EquivocationSlash = MyMisconduct>; + } + + pub struct FakeSlasher(PhantomData); + + impl DoSlash<(T::AccountId, Exposure), u64> for FakeSlasher { + fn do_slash(_: (T::AccountId, Exposure), _severity: u64) { + // does nothing ... + } + } + + impl KeyOwnerProofSystem for FakeSlasher { + type Proof = Vec; + type FullIdentification = (T::AccountId, u64); + + fn prove(key: Key) -> Option { + Some(Vec::new()) + } + + fn check_proof(key: Key, proof: Self::Proof) -> Option { + Some((Default::default(), 0)) + } + } + + #[test] + fn foo() { + + let eq = EquivocationProof::new( + H256::default(), + H256::default(), + Default::default(), + Vec::new(), + ); + + BabeSlashReporter::::report_equivocation(eq); } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 88fadf0591447..e2340316af633 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -631,13 +631,13 @@ bitmask! { } /// A generic trait for reporting slashing violations. -pub trait ReportSlash { - fn slash(misbehavior_footprint: Hash, identfication: Identification); +pub trait ReportSlash { + fn slash(identfication: Identification, footprint: Hash); } /// A generic trait for enacting slashes. -pub trait DoSlash { - fn do_slash(severity: Severity, identification: Identification); +pub trait DoSlash { + fn do_slash(identification: Identification, severity: Severity); } /// temp, obviosly not a trait From 0f153e56fb76ddb9a9b69200543a895bff7924c2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 11 Jul 2019 00:04:58 +0200 Subject: [PATCH 03/66] progress --- core/consensus/babe/src/slash.rs | 89 +++++++++++++++++++------------- srml/support/src/traits.rs | 4 +- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index 24c54922cb080..2f1fe16bcab28 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -7,14 +7,14 @@ use runtime_support::{ StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result, decl_module, decl_storage, decl_event, traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, MisbehaviorKind} }; -use runtime_primitives::{Perbill, traits::Hash}; +use runtime_primitives::{Perbill, traits::{Hash, Header}}; use std::marker::PhantomData; // tmp type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; type Key = sr25519::Public; -const DEFAULT_WINDOW_LENGTH: u64 = 100; +const DEFAULT_WINDOW_LENGTH: u32 = 100; /// Trait for reporting slashes pub trait Trait: srml_system::Trait { @@ -70,7 +70,7 @@ impl BabeSlashReporter { let identification = T::KeyOwner::check_proof( proof.author.clone(), proof.membership_proof - ).expect("fixme"); + ).expect("mocked will always succeed; qed"); //check headers @@ -86,24 +86,23 @@ decl_storage! { trait Store for RollingMisconduct as R { /// Misbehavior reports /// - // TODO(niklasad1): This is not very good because it doesn't - // _really_ works porportially w.r.t. to the session it happend in - // just the window will be shrinked when it exceeds to threshold - // - // On the other hand, one could have - // Kind -> Vec but then the problem is that - // `SessionIndex` is a counter that is wrapping and might be - // hard to determine which is `fresh` - MisconductReports get(kind): linked_map Kind => Counter; - - /// Rolling window length (we need different windows for different kinds) - WindowLength get(w) config(): u64 = DEFAULT_WINDOW_LENGTH; - - /// Some misconduct need to keep track of duplicate misconducts - /// Such as only one is counted in the same session and so on. + /// This have a weird edge-case when because `SessionIndex` + /// is currently an u32 and may wrap around in that case. + /// + /// Basically we have can detect that by checking if all + /// session_indexes are bigger than the current session index + MisconductReports get(kind): linked_map Kind => Vec; + + /// Rolling window length for different kinds + WindowLength get(w) config(): map Kind => u32; + + /// Some misbehaviours may need to keep track concurrent misbehaviours + /// by the same entity in the same session which is not possible currently + /// + /// Because then we need to store `who` /// // TODO(niklasad1): implement... - SlashDeduplicator get(sd) config(): u64 = DEFAULT_WINDOW_LENGTH; + SlashDeduplicator get(sd) config(): linked_map Kind => u32; } } @@ -114,29 +113,44 @@ decl_module! { impl RollingMisconduct { /// On startup make sure no misconducts are reported - pub fn on_initialize() {} - - /// Set rolling window length - pub fn set_window_length(len: u64) { - WindowLength::put(len); + pub fn on_initialize() { + for kind in 0..2 { + WindowLength::insert(kind, DEFAULT_WINDOW_LENGTH); + } } /// Remove items that doesn't fit into the rolling window - pub fn on_session_end() { - let max_window = WindowLength::get(); - - // dummy thing lets assume that exactly 20 kinds - for kind in 0..20 { - MisconductReports::mutate(kind, |w| std::cmp::min(max_window, *w)); + pub fn on_session_end(session_index: u32) { + + // dummy thing to iterate over possible enum variants + for kind in 0..2 { + MisconductReports::mutate(kind, |window| { + let max_window = WindowLength::get(kind); + + // edge-case session_index has wrapped-around + if window.iter().all(|s| *s > session_index) { + if let Some(idx) = window.iter().rev().position(|s| { + let ss = u32::max_value() - s; + ss - session_index + 1 > max_window as u32 + }) { + window.drain(..window.len() - idx); + } + } else { + if let Some(idx) = window.iter().position(|s| *s > session_index) { + window.drain(..idx); + } + } + }); } - } + } /// Report misbehaviour for a misconduct kind /// - /// Return number of misbehavior in the current window - pub fn report_misbehavior(kind: u64) -> u64 { - MisconductReports::mutate(kind, |w| w.saturating_add(1)); - MisconductReports::get(kind) + /// Return number of misbehaviors in the current session + pub fn report_misbehavior(kind: u64, session_index: u32) -> u64 { + let window_length = WindowLength::get(kind); + MisconductReports::mutate(kind, |w| w.push(session_index + window_length)); + MisconductReports::get(kind).len() as u64 } } @@ -165,8 +179,9 @@ where // else if severity > SMALL then compute some severity level and slash // if severity < LESS -> slash little panic!("time to slash"); - let severity = RollingMisconduct::::report_misbehavior(kind); - DS::do_slash(who, 0); + let dummy_session_index = 0; + let number_misbehaviours = RollingMisconduct::::report_misbehavior(kind, dummy_session_index); + DS::do_slash(who, number_misbehaviours); } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index e2340316af633..7aa5069097368 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -644,7 +644,7 @@ pub trait DoSlash { #[derive(Copy, Clone, Eq, Hash, PartialEq)] pub enum MisbehaviorKind { /// .. - Equivocation, + Equivocation = 0, /// .. - InvalidBlock, + InvalidBlock = 1, } From 76173fe023f83fe39ef35bd19f16b64bc14934de Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 11 Jul 2019 13:34:57 +0200 Subject: [PATCH 04/66] progress --- Cargo.lock | 18 ++++ Cargo.toml | 1 + core/consensus/babe/Cargo.toml | 1 + core/consensus/babe/src/lib.rs | 4 +- core/consensus/babe/src/slash.rs | 158 ++++++++++--------------------- srml/staking/src/lib.rs | 2 +- srml/support/src/traits.rs | 38 ++++---- 7 files changed, 90 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97b6cac48ad46..df98bc1c00277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3807,6 +3807,23 @@ dependencies = [ "substrate-primitives 2.0.0", ] +[[package]] +name = "srml-rolling-window" +version = "2.0.0" +dependencies = [ + "parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 2.0.0", + "sr-primitives 2.0.0", + "srml-balances 2.0.0", + "srml-session 2.0.0", + "srml-staking 2.0.0", + "srml-support 2.0.0", + "srml-system 2.0.0", + "srml-timestamp 2.0.0", + "substrate-primitives 2.0.0", +] + [[package]] name = "srml-session" version = "2.0.0" @@ -4243,6 +4260,7 @@ dependencies = [ "sr-primitives 2.0.0", "sr-version 2.0.0", "srml-babe 2.0.0", + "srml-rolling-window 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", "substrate-client 2.0.0", diff --git a/Cargo.toml b/Cargo.toml index 7790c4127aaef..0222f508e7f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ members = [ "srml/staking", "srml/sudo", "srml/system", + "srml/rolling-window", "srml/timestamp", "srml/treasury", "node/cli", diff --git a/core/consensus/babe/Cargo.toml b/core/consensus/babe/Cargo.toml index ad9e5b160c77d..46f9ad223d6c1 100644 --- a/core/consensus/babe/Cargo.toml +++ b/core/consensus/babe/Cargo.toml @@ -16,6 +16,7 @@ inherents = { package = "substrate-inherents", path = "../../inherents" } substrate-telemetry = { path = "../../telemetry" } srml-babe = { path = "../../../srml/babe" } srml-system = { path = "../../../srml/system" } +srml-rolling-window = { path = "../../../srml/rolling-window" } client = { package = "substrate-client", path = "../../client" } consensus_common = { package = "substrate-consensus-common", path = "../common" } slots = { package = "substrate-consensus-slots", path = "../slots" } diff --git a/core/consensus/babe/src/lib.rs b/core/consensus/babe/src/lib.rs index a89605c58c40d..c7b32b1e9b4c3 100644 --- a/core/consensus/babe/src/lib.rs +++ b/core/consensus/babe/src/lib.rs @@ -513,8 +513,8 @@ fn check_header( // This will report a misconduct and slash { let _e = slash::EquivocationProof::new( - equivocation_proof.fst_header().hash(), - equivocation_proof.fst_header().hash(), + equivocation_proof.fst_header().clone(), + equivocation_proof.snd_header().clone(), Default::default(), 0, ); diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index 2f1fe16bcab28..cee2e3ec4ad65 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -3,30 +3,24 @@ use parity_codec::{Decode, Encode}; use primitives::sr25519; -use runtime_support::{ - StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result, decl_module, decl_storage, decl_event, - traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, MisbehaviorKind} -}; -use runtime_primitives::{Perbill, traits::{Hash, Header}}; +use srml_rolling_window::Module as RollingWindow; use std::marker::PhantomData; +use runtime_support::traits::{KeyOwnerProofSystem, ReportSlash, DoSlash}; +use runtime_primitives::{Perbill, traits::Hash}; -// tmp -type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; -type Key = sr25519::Public; - -const DEFAULT_WINDOW_LENGTH: u32 = 100; +type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; /// Trait for reporting slashes pub trait Trait: srml_system::Trait { /// Key that identifies the owner - type KeyOwner: KeyOwnerProofSystem; + type KeyOwner: KeyOwnerProofSystem; /// Type of slashing /// /// FullId - is the full identification of the entity to slash /// which should in most cases be (AccountId, Exposure) /// /// - type EquivocationSlash: ReportSlash>; + type EquivocationSlash: ReportSlash>; } /// Represents an Babe equivocation proof. @@ -39,7 +33,7 @@ pub struct EquivocationProof { } impl EquivocationProof { - /// Create a new Babe equivocation proof. + /// Create a new Babe equivocation proof pub fn new( first_header: H, second_header: H, @@ -74,90 +68,20 @@ impl BabeSlashReporter { //check headers + // Assumption: author in the context __is__ the validator/authority that has voted/signed + // two blocks in the same round let hash = T::Hashing::hash_of(&proof.author); - T::EquivocationSlash::slash(hash, identification); - } -} - -type Kind = u64; -type Counter = u64; - -decl_storage! { - trait Store for RollingMisconduct as R { - /// Misbehavior reports - /// - /// This have a weird edge-case when because `SessionIndex` - /// is currently an u32 and may wrap around in that case. - /// - /// Basically we have can detect that by checking if all - /// session_indexes are bigger than the current session index - MisconductReports get(kind): linked_map Kind => Vec; - - /// Rolling window length for different kinds - WindowLength get(w) config(): map Kind => u32; - - /// Some misbehaviours may need to keep track concurrent misbehaviours - /// by the same entity in the same session which is not possible currently - /// - /// Because then we need to store `who` - /// - // TODO(niklasad1): implement... - SlashDeduplicator get(sd) config(): linked_map Kind => u32; - } -} - -decl_module! { - pub struct RollingMisconduct for enum Call where origin: T::Origin { } -} - -impl RollingMisconduct { - /// On startup make sure no misconducts are reported - pub fn on_initialize() { - for kind in 0..2 { - WindowLength::insert(kind, DEFAULT_WINDOW_LENGTH); - } - } - - /// Remove items that doesn't fit into the rolling window - pub fn on_session_end(session_index: u32) { - - // dummy thing to iterate over possible enum variants - for kind in 0..2 { - MisconductReports::mutate(kind, |window| { - let max_window = WindowLength::get(kind); - - // edge-case session_index has wrapped-around - if window.iter().all(|s| *s > session_index) { - if let Some(idx) = window.iter().rev().position(|s| { - let ss = u32::max_value() - s; - ss - session_index + 1 > max_window as u32 - }) { - window.drain(..window.len() - idx); - } - } else { - if let Some(idx) = window.iter().position(|s| *s > session_index) { - window.drain(..idx); - } - } - }); - } + T::EquivocationSlash::slash(hash, identification); } - - /// Report misbehaviour for a misconduct kind - /// - /// Return number of misbehaviors in the current session - pub fn report_misbehavior(kind: u64, session_index: u32) -> u64 { - let window_length = WindowLength::get(kind); - MisconductReports::mutate(kind, |w| w.push(session_index + window_length)); - MisconductReports::get(kind).len() as u64 - } } pub struct MyMisconduct((PhantomData, PhantomData)); +// do this more elegant with some macro +// preferable with some linkage to the `misbehavior kind` ideally impl MyMisconduct { - fn kind() -> u64 { + fn kind() -> u32 { 0 } @@ -166,33 +90,40 @@ impl MyMisconduct { } } -impl ReportSlash for MyMisconduct +impl ReportSlash for MyMisconduct where - DS: DoSlash + T: Trait + srml_rolling_window::Trait, + DS: DoSlash, { - fn slash(_footprint: T::Hash, who: Who) { + fn slash(footprint: T::Hash, who: Who) { let kind = Self::kind(); let base_seve = Self::base_severity(); - // do something special with severity - // such as - // if severity > BIG then slash something like 100% - // else if severity > SMALL then compute some severity level and slash - // if severity < LESS -> slash little - panic!("time to slash"); - let dummy_session_index = 0; - let number_misbehaviours = RollingMisconduct::::report_misbehavior(kind, dummy_session_index); - DS::do_slash(who, number_misbehaviours); + let num_violations = RollingWindow::::report_misbehavior(kind, footprint); + // number of validators + let n = 50; + + // example how to estimate severity + let severity = if num_violations < 10 { + base_seve + } else if num_violations < 1000 { + // 3k / n^2 + // ignore base severity because `Permill` doesn't provide addition, e.g. (base + estimate) + Perbill::from_rational_approximation(3 * num_violations, n*n) + } else { + Perbill::one() + }; + + DS::do_slash(who, severity); } } - #[cfg(test)] mod tests { use super::*; use srml_system as system; use runtime_primitives::traits::{BlakeTwo256, IdentityLookup}; use runtime_primitives::testing::{H256, Header}; - use runtime_support::impl_outer_origin; + use runtime_support::{impl_outer_origin, parameter_types}; pub type AccountId = u64; pub type Exposure = u64; @@ -200,10 +131,16 @@ mod tests { #[derive(Clone, PartialEq, Eq, Debug)] pub struct Test; + parameter_types! { + pub const BlockHashCount: u64 = 250; + } + impl_outer_origin!{ pub enum Origin for Test {} } + impl srml_rolling_window::Trait for Test {} + impl system::Trait for Test { type Origin = Origin; type Index = u64; @@ -214,6 +151,7 @@ mod tests { type Lookup = IdentityLookup; type Header = Header; type Event = (); + type BlockHashCount = BlockHashCount; } impl Trait for Test { @@ -223,21 +161,21 @@ mod tests { pub struct FakeSlasher(PhantomData); - impl DoSlash<(T::AccountId, Exposure), u64> for FakeSlasher { - fn do_slash(_: (T::AccountId, Exposure), _severity: u64) { + impl DoSlash<(T::AccountId, Exposure), Perbill> for FakeSlasher { + fn do_slash(_: (T::AccountId, Exposure), _severity: Perbill) { // does nothing ... } } impl KeyOwnerProofSystem for FakeSlasher { type Proof = Vec; - type FullIdentification = (T::AccountId, u64); + type FullIdentification = (T::AccountId, Exposure); - fn prove(key: Key) -> Option { + fn prove(key: sr25519::Public) -> Option { Some(Vec::new()) } - fn check_proof(key: Key, proof: Self::Proof) -> Option { + fn check_proof(key: sr25519::Public, proof: Self::Proof) -> Option { Some((Default::default(), 0)) } } @@ -246,8 +184,8 @@ mod tests { fn foo() { let eq = EquivocationProof::new( - H256::default(), - H256::default(), + Default::default(), + Default::default(), Default::default(), Vec::new(), ); diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 9a49211fad66a..6394f9f9e57a7 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -278,7 +278,7 @@ use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, - WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get + WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, DoSlash } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 8e3b848e89af7..02ee1dac44ba6 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -630,25 +630,6 @@ bitmask! { } } -/// A generic trait for reporting slashing violations. -pub trait ReportSlash { - fn slash(identfication: Identification, footprint: Hash); -} - -/// A generic trait for enacting slashes. -pub trait DoSlash { - fn do_slash(identification: Identification, severity: Severity); -} - -/// temp, obviosly not a trait -#[derive(Copy, Clone, Eq, Hash, PartialEq)] -pub enum MisbehaviorKind { - /// .. - Equivocation = 0, - /// .. - InvalidBlock = 1, -} - impl WithdrawReasons { /// Choose all variants except for `one`. pub fn except(one: WithdrawReason) -> WithdrawReasons { @@ -668,3 +649,22 @@ pub trait ChangeMembers { impl ChangeMembers for () { fn change_members(_incoming: &[T], _outgoing: &[T], _new_set: &[T]) {} } + +/// A generic trait for reporting slashing violations. +pub trait ReportSlash { + fn slash(identfication: Identification, footprint: Hash); +} + +/// A generic trait for enacting slashes. +pub trait DoSlash { + fn do_slash(identification: Identification, severity: Severity); +} + +/// temp, obviosly not a trait +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +pub enum MisbehaviorKind { + /// .. + Equivocation = 0, + /// .. + InvalidBlock = 1, +} From 0288d17c4ff5f3560525001fc4ac7d9342e6e6a8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 11 Jul 2019 20:51:57 +0200 Subject: [PATCH 05/66] add `srml-rolling-window` --- srml/rolling-window/Cargo.toml | 32 ++++ srml/rolling-window/src/lib.rs | 176 +++++++++++++++++++ srml/rolling-window/src/mock.rs | 288 ++++++++++++++++++++++++++++++++ 3 files changed, 496 insertions(+) create mode 100644 srml/rolling-window/Cargo.toml create mode 100644 srml/rolling-window/src/lib.rs create mode 100644 srml/rolling-window/src/mock.rs diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml new file mode 100644 index 0000000000000..0b7a4ad65ec85 --- /dev/null +++ b/srml/rolling-window/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "srml-rolling-window" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +serde = { version = "1.0", optional = true } +parity-codec = { version = "4.1.1", default-features = false } +srml-support = { path = "../support", default-features = false } +system = { package = "srml-system", path = "../system", default-features = false } +balances = { package = "srml-balances", path = "../balances", default-features = false } +sr-primitives = { path = "../../core/sr-primitives", default-features = false } + +[dev-dependencies] +balances = { package = "srml-balances", path = "../balances" } +runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } +session = { package = "srml-session", path = "../session", default-features = false } +srml-staking = { package = "srml-staking", path = "../staking" } +substrate-primitives = { path = "../../core/primitives" } +timestamp = { package = "srml-timestamp", path = "../timestamp" } + +[features] +default = ["std"] +std = [ + "serde", + "parity-codec/std", + "sr-primitives/std", + "srml-support/std", + "system/std", + "balances/std", +] diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs new file mode 100644 index 0000000000000..72280e19776d9 --- /dev/null +++ b/srml/rolling-window/src/lib.rs @@ -0,0 +1,176 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! # noooo docs +//! + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +use srml_support::{ + StorageValue, StorageMap, decl_module, decl_storage, +}; + +type Kind = u32; +type Window = u32; + +pub trait Trait: system::Trait {} + +decl_storage! { + trait Store for Module as Example { + /// Misbehavior reports + MisconductReports get(kind): map Kind => Vec<(u32, T::Hash)>; + + /// Rolling window length for different kinds + WindowLength get(w) config(): map Kind => Window; + + /// Session index + pub SessionIndex get(s) config(): u32 = 0; + + /// The number of different kinds + NumberOfKinds get(k) config(): u32 = 0; + } +} + +decl_module! { + /// Simple declaration of the `Module` type. Lets the macro know what its working on. + pub struct Module for enum Call where origin: T::Origin {} +} + +impl Module { + /// On startup make sure no misconducts are reported + pub fn on_initialize + Copy>(kinds: &[(K, u32)]) { + NumberOfKinds::put(kinds.len() as u32); + + for (kind, window_length) in kinds { + WindowLength::insert(kind.clone().into(), window_length); + } + } + + /// Remove items that doesn't fit into the rolling window + pub fn on_session_end() { + let mut session_wrapped = false; + + let session = match SessionIndex::get().checked_add(1) { + Some(v) => v, + None => { + session_wrapped = true; + 0 + } + }; + + for kind in 0..NumberOfKinds::get() { + >::mutate(kind, |window| { + let w = WindowLength::get(kind); + + // it is guaranteed that `s` happend before `session` + window.retain(|(s, _)| { + + let window_wrapped = s.checked_add(w).is_none(); + let x = s.wrapping_add(w); + + match (session_wrapped, window_wrapped) { + (false, true) => true, + (true, false) => false, + (true, true) | (false, false) if x > session => true, + _ => false + } + }); + }); + } + + SessionIndex::put(session); + } + + /// Report misbehaviour for a misconduct kind + /// + /// Return number of misbehaviors in the current window which + /// may include duplicated misbehaviour from a validator + pub fn report_misbehavior>(kind: K, footprint: T::Hash) -> u64 { + let session = SessionIndex::get(); + let kind: u32 = kind.into(); + + if >::exists(kind) { + >::mutate(kind, |w| w.push((session, footprint))); + } else { + >::insert(kind, vec![(session, footprint)]); + } + Self::get_misbehavior(kind) + } + + + pub fn get_misbehavior>(kind: K) -> u64 { + >::get(kind.into()).len() as u64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use crate::mock::*; + use substrate_primitives::H256; + + type RollingWindow = Module; + + #[test] + fn it_works() { + with_externalities(&mut ExtBuilder::default() + .validator_count(50) + .num_validators(50) + .build(), + || { + + let kinds = vec![(0, 4), (1, 3), (2, 1)]; + + RollingWindow::on_initialize(&kinds); + + for i in 1..=10 { + let hash = H256::random(); + assert_eq!(i, RollingWindow::report_misbehavior(0, hash)); + } + + for i in 1..=4 { + let hash = H256::random(); + assert_eq!(i, RollingWindow::report_misbehavior(1, hash)); + } + + RollingWindow::on_session_end(); + // session = 1 + assert_eq!(RollingWindow::get_misbehavior(0), 10); + assert_eq!(RollingWindow::get_misbehavior(1), 4); + + RollingWindow::on_session_end(); + // session = 2 + assert_eq!(RollingWindow::get_misbehavior(0), 10); + assert_eq!(RollingWindow::get_misbehavior(1), 4); + + RollingWindow::on_session_end(); + // session = 3 + assert_eq!(RollingWindow::get_misbehavior(0), 10); + assert_eq!(RollingWindow::get_misbehavior(1), 0); + + RollingWindow::on_session_end(); + // session = 4 + assert_eq!(RollingWindow::get_misbehavior(0), 0); + assert_eq!(RollingWindow::get_misbehavior(1), 0); + + }); + } +} diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs new file mode 100644 index 0000000000000..75b9d06be3c27 --- /dev/null +++ b/srml/rolling-window/src/mock.rs @@ -0,0 +1,288 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +#![allow(unused)] + +use super::*; +use sr_primitives::{Perbill, traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; +use substrate_primitives::{Blake2Hasher, H256}; +use srml_staking::{GenesisConfig, StakerStatus}; +use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get}}; +use std::{marker::PhantomData, cell::RefCell}; +use std::collections::{HashSet, HashMap}; + +pub type AccountId = u64; +pub type Exposure = u64; +pub type BlockNumber = u64; +pub type Severity = u64; +pub type Balance = u64; +pub type Balances = balances::Module; +pub type Session = session::Module; +pub type Staking = srml_staking::Module; +pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type ExtendedBalance = u128; + +pub struct CurrencyToVoteHandler; + +thread_local! { + static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); +} + +impl Convert for CurrencyToVoteHandler { + fn convert(x: u64) -> u64 { x } +} +impl Convert for CurrencyToVoteHandler { + fn convert(x: u128) -> u64 { + x as u64 + } +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { + 0 + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Test; + +impl_outer_origin!{ + pub enum Origin for Test {} +} + +impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; +} + +impl balances::Trait for Test { + type Balance = Balance; + type OnFreeBalanceZero = Staking; + type OnNewAccount = (); + type Event = (); + type TransactionPayment = (); + type TransferPayment = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; + type CreationFee = CreationFee; + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; +} + +impl srml_staking::Trait for Test { + type Currency = balances::Module; + type CurrencyToVote = CurrencyToVoteHandler; + type OnRewardMinted = (); + type Event = (); + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SessionInterface = Self; +} + +impl session::Trait for Test { + type SelectInitialValidators = Staking; + type OnSessionEnding = Staking; + type Keys = UintAuthorityId; + type ShouldEndSession = session::PeriodicSessions; + type SessionHandler = (); + type Event = (); + type ValidatorId = AccountId; + type ValidatorIdOf = srml_staking::StashOf; +} + +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); +} + +impl Trait for Test {} + +impl session::historical::Trait for Test { + type FullIdentification = srml_staking::Exposure; + type FullIdentificationOf = srml_staking::ExposureOf; +} + +parameter_types! { + pub const SessionsPerEra: session::SessionIndex = 3; + pub const BondingDuration: srml_staking::EraIndex = 3; +} + +parameter_types! { + pub const Period: BlockNumber = 1; + pub const Offset: BlockNumber = 0; +} + +parameter_types! { + pub const TransferFee: u64 = 0; + pub const CreationFee: u64 = 0; + pub const TransactionBaseFee: u64 = 0; + pub const TransactionByteFee: u64 = 0; +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +pub struct ExtBuilder { + existential_deposit: u64, + reward: u64, + validator_pool: bool, + nominate: bool, + validator_count: u32, + minimum_validator_count: u32, + fair: bool, + num_validators: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 0, + reward: 10, + validator_pool: false, + nominate: true, + validator_count: 2, + minimum_validator_count: 0, + fair: true, + num_validators: None, + } + } +} + +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn validator_pool(mut self, validator_pool: bool) -> Self { + self.validator_pool = validator_pool; + self + } + pub fn nominate(mut self, nominate: bool) -> Self { + self.nominate = nominate; + self + } + pub fn validator_count(mut self, count: u32) -> Self { + self.validator_count = count; + self + } + pub fn minimum_validator_count(mut self, count: u32) -> Self { + self.minimum_validator_count = count; + self + } + pub fn fair(mut self, is_fair: bool) -> Self { + self.fair = is_fair; + self + } + pub fn num_validators(mut self, num_validators: u32) -> Self { + self.num_validators = Some(num_validators); + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn build(self) -> runtime_io::TestExternalities { + self.set_associated_consts(); + let (mut t, mut c) = system::GenesisConfig::default().build_storage::().unwrap(); + let balance_factor = if self.existential_deposit > 0 { + 256 + } else { + 1 + }; + + let num_validators = self.num_validators.unwrap_or(self.validator_count); + let validators = (0..num_validators) + .map(|x| ((x + 1) * 10) as u64) + .collect::>(); + + let _ = balances::GenesisConfig::{ + balances: vec![ + (1, 10 * balance_factor), + (2, 20 * balance_factor), + (3, 300 * balance_factor), + (4, 400 * balance_factor), + (10, balance_factor), + (11, balance_factor * 1000), + (20, balance_factor), + (21, balance_factor * 2000), + (30, balance_factor), + (31, balance_factor * 2000), + (40, balance_factor), + (41, balance_factor * 2000), + (100, 2000 * balance_factor), + (101, 2000 * balance_factor), + ], + vesting: vec![], + }.assimilate_storage(&mut t, &mut c); + + let stake_21 = if self.fair { 1000 } else { 2000 }; + let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; + let status_41 = if self.validator_pool { + StakerStatus::::Validator + } else { + StakerStatus::::Idle + }; + let nominated = if self.nominate { vec![11, 21] } else { vec![] }; + let _ = GenesisConfig::{ + current_era: 0, + stakers: vec![ + (11, 10, balance_factor * 1000, StakerStatus::::Validator), + (21, 20, stake_21, StakerStatus::::Validator), + (31, 30, stake_31, StakerStatus::::Validator), + (41, 40, balance_factor * 1000, status_41), + // nominator + (101, 100, balance_factor * 500, StakerStatus::::Nominator(nominated)) + ], + validator_count: self.validator_count, + minimum_validator_count: self.minimum_validator_count, + session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32), + offline_slash: Perbill::from_percent(5), + current_session_reward: self.reward, + offline_slash_grace: 0, + invulnerables: vec![], + }.assimilate_storage(&mut t, &mut c); + + let _ = timestamp::GenesisConfig::{ + minimum_period: 5, + }.assimilate_storage(&mut t, &mut c); + + let _ = session::GenesisConfig:: { + keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), + }.assimilate_storage(&mut t, &mut c); + + let mut ext = t.into(); + runtime_io::with_externalities(&mut ext, || { + let validators = Session::validators(); + SESSION.with(|x| + *x.borrow_mut() = (validators.clone(), HashSet::new()) + ); + }); + ext + } +} From 47fdfa1c9273972fe5d11f874fc3132818b776f6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 11 Jul 2019 21:02:13 +0200 Subject: [PATCH 06/66] nits --- srml/rolling-window/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 72280e19776d9..1a48fc941ccea 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -33,7 +33,7 @@ type Window = u32; pub trait Trait: system::Trait {} decl_storage! { - trait Store for Module as Example { + trait Store for Module as RollingWindow { /// Misbehavior reports MisconductReports get(kind): map Kind => Vec<(u32, T::Hash)>; @@ -49,12 +49,12 @@ decl_storage! { } decl_module! { - /// Simple declaration of the `Module` type. Lets the macro know what its working on. + /// Rolling Window module pub struct Module for enum Call where origin: T::Origin {} } impl Module { - /// On startup make sure no misconducts are reported + /// On startup initialize all kinds pub fn on_initialize + Copy>(kinds: &[(K, u32)]) { NumberOfKinds::put(kinds.len() as u32); @@ -102,6 +102,10 @@ impl Module { /// /// Return number of misbehaviors in the current window which /// may include duplicated misbehaviour from a validator + /// + // TODO(niklasad1): it would probably be useful to report the number of duplicates + // in each round too. Because, a couple of algorithms ignores concurrent misconducts + // in the same session/era. pub fn report_misbehavior>(kind: K, footprint: T::Hash) -> u64 { let session = SessionIndex::get(); let kind: u32 = kind.into(); From 60c3ff14a2d837bba11bd50289a05265b7ab9bd6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 12 Jul 2019 13:14:44 +0200 Subject: [PATCH 07/66] polish --- Cargo.lock | 1 + core/consensus/babe/src/slash.rs | 18 ++-- srml/rolling-window/Cargo.toml | 12 ++- srml/rolling-window/src/lib.rs | 174 +++++++++++++++++++++++-------- srml/rolling-window/src/mock.rs | 13 ++- srml/support/src/traits.rs | 10 +- 6 files changed, 170 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df98bc1c00277..39d4b4364730d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3815,6 +3815,7 @@ dependencies = [ "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", "sr-primitives 2.0.0", + "sr-std 2.0.0", "srml-balances 2.0.0", "srml-session 2.0.0", "srml-staking 2.0.0", diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index cee2e3ec4ad65..6bce27b1b5bf2 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -5,7 +5,7 @@ use primitives::sr25519; use srml_rolling_window::Module as RollingWindow; use std::marker::PhantomData; -use runtime_support::traits::{KeyOwnerProofSystem, ReportSlash, DoSlash}; +use runtime_support::traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, Misbehavior}; use runtime_primitives::{Perbill, traits::Hash}; type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; @@ -81,8 +81,8 @@ pub struct MyMisconduct((PhantomData, PhantomData)); // do this more elegant with some macro // preferable with some linkage to the `misbehavior kind` ideally impl MyMisconduct { - fn kind() -> u32 { - 0 + fn kind() -> Misbehavior { + Misbehavior::Equivocation } fn base_severity() -> Perbill { @@ -92,13 +92,17 @@ impl MyMisconduct { impl ReportSlash for MyMisconduct where - T: Trait + srml_rolling_window::Trait, + T: Trait + srml_rolling_window::Trait, DS: DoSlash, { fn slash(footprint: T::Hash, who: Who) { let kind = Self::kind(); let base_seve = Self::base_severity(); - let num_violations = RollingWindow::::report_misbehavior(kind, footprint); + RollingWindow::::report_misbehavior(kind, footprint); + + // use `RollingWindow::get_misbehaved_uniq` if you want the number of unique misconduct in each session + let num_violations = RollingWindow::::get_misbehaved(kind); + // number of validators let n = 50; @@ -139,7 +143,9 @@ mod tests { pub enum Origin for Test {} } - impl srml_rolling_window::Trait for Test {} + impl srml_rolling_window::Trait for Test { + type Kind = Misbehavior; + } impl system::Trait for Test { type Origin = Origin; diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index 0b7a4ad65ec85..a6d9311fa8985 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -5,12 +5,13 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -serde = { version = "1.0", optional = true } +balances = { package = "srml-balances", path = "../balances", default-features = false } parity-codec = { version = "4.1.1", default-features = false } +rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } +serde = { version = "1.0", optional = true } +sr-primitives = { path = "../../core/sr-primitives", default-features = false } srml-support = { path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } -balances = { package = "srml-balances", path = "../balances", default-features = false } -sr-primitives = { path = "../../core/sr-primitives", default-features = false } [dev-dependencies] balances = { package = "srml-balances", path = "../balances" } @@ -23,10 +24,11 @@ timestamp = { package = "srml-timestamp", path = "../timestamp" } [features] default = ["std"] std = [ - "serde", + "balances/std", "parity-codec/std", + "rstd/std", + "serde", "sr-primitives/std", "srml-support/std", "system/std", - "balances/std", ] diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 1a48fc941ccea..33906ac833a99 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -24,42 +24,55 @@ mod mock; use srml_support::{ - StorageValue, StorageMap, decl_module, decl_storage, + StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_storage, }; +use parity_codec::Codec; +use sr_primitives::traits::MaybeSerializeDebug; -type Kind = u32; type Window = u32; +type Session = u32; -pub trait Trait: system::Trait {} +pub trait Trait: system::Trait { + type Kind: Copy + Clone + Codec + MaybeSerializeDebug; +} decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports - MisconductReports get(kind): map Kind => Vec<(u32, T::Hash)>; + /// + /// It maps each kind into an unique hash and the session number for the misconduct + MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; /// Rolling window length for different kinds - WindowLength get(w) config(): map Kind => Window; + /// + /// Each kind has its own window length + WindowLength get(w) config(): linked_map T::Kind => Window; /// Session index - pub SessionIndex get(s) config(): u32 = 0; - - /// The number of different kinds - NumberOfKinds get(k) config(): u32 = 0; + SessionIndex get(s) config(): u32 = 0; } } decl_module! { /// Rolling Window module + /// + /// It is similar to a `simple moving average` except that it just + /// return the number of misbehaviors in the window instead of calculating the average pub struct Module for enum Call where origin: T::Origin {} } impl Module { /// On startup initialize all kinds - pub fn on_initialize + Copy>(kinds: &[(K, u32)]) { - NumberOfKinds::put(kinds.len() as u32); + /// + /// Make sure that all kinds is unique otherwise + /// a kind may be overwritten by another kind + /// + /// Panics if length is bigger than `u32::max_value()` + pub fn on_initialize(kinds: Vec<(T::Kind, Window)>) { + assert!(kinds.len() <= u32::max_value() as usize); for (kind, window_length) in kinds { - WindowLength::insert(kind.clone().into(), window_length); + >::insert(kind, window_length); } } @@ -75,9 +88,10 @@ impl Module { } }; - for kind in 0..NumberOfKinds::get() { + // fixme possible iterator invalidation + for (kind, _) in >::enumerate() { >::mutate(kind, |window| { - let w = WindowLength::get(kind); + let w = >::get(kind); // it is guaranteed that `s` happend before `session` window.retain(|(s, _)| { @@ -98,29 +112,50 @@ impl Module { SessionIndex::put(session); } - /// Report misbehaviour for a misconduct kind - /// - /// Return number of misbehaviors in the current window which - /// may include duplicated misbehaviour from a validator - /// - // TODO(niklasad1): it would probably be useful to report the number of duplicates - // in each round too. Because, a couple of algorithms ignores concurrent misconducts - // in the same session/era. - pub fn report_misbehavior>(kind: K, footprint: T::Hash) -> u64 { + /// Report misbehaviour for a kind + pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { let session = SessionIndex::get(); - let kind: u32 = kind.into(); if >::exists(kind) { >::mutate(kind, |w| w.push((session, footprint))); } else { >::insert(kind, vec![(session, footprint)]); } - Self::get_misbehavior(kind) + } + + /// Return number of misbehaviors in the current window which + /// may include duplicated misbehaviours + pub fn get_misbehaved(kind: T::Kind) -> u64 { + >::get(kind).len() as u64 } - pub fn get_misbehavior>(kind: K) -> u64 { - >::get(kind.into()).len() as u64 + /// Return number of misbehaviors in the current window which + /// may include duplicated misbehaviours + pub fn get_misbehaved_unique(kind: T::Kind) -> u64 { + let window = >::get(kind); + + let mut seen_ids = rstd::vec::Vec::new(); + let mut unique = 0; + // session can never be smaller than 0 + let mut last_session = 0; + + for (session, id) in window { + // new session reset `seem_ids` + if session > last_session { + seen_ids.clear(); + } + + // Unfortunately O(n) + if !seen_ids.contains(&id) { + unique += 1; + seen_ids.push(id); + } + + last_session = session; + } + + unique } } @@ -133,48 +168,101 @@ mod tests { type RollingWindow = Module; + #[test] fn it_works() { with_externalities(&mut ExtBuilder::default() - .validator_count(50) - .num_validators(50) .build(), || { - let kinds = vec![(0, 4), (1, 3), (2, 1)]; + let kinds = vec![(Kind::One, 4), (Kind::Two, 3)]; - RollingWindow::on_initialize(&kinds); + RollingWindow::on_initialize(kinds); for i in 1..=10 { - let hash = H256::random(); - assert_eq!(i, RollingWindow::report_misbehavior(0, hash)); + RollingWindow::report_misbehavior(Kind::One, H256::zero()); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), i); } for i in 1..=4 { - let hash = H256::random(); - assert_eq!(i, RollingWindow::report_misbehavior(1, hash)); + RollingWindow::report_misbehavior(Kind::Two, H256::zero()); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), i); } RollingWindow::on_session_end(); // session = 1 - assert_eq!(RollingWindow::get_misbehavior(0), 10); - assert_eq!(RollingWindow::get_misbehavior(1), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); RollingWindow::on_session_end(); // session = 2 - assert_eq!(RollingWindow::get_misbehavior(0), 10); - assert_eq!(RollingWindow::get_misbehavior(1), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); RollingWindow::on_session_end(); // session = 3 - assert_eq!(RollingWindow::get_misbehavior(0), 10); - assert_eq!(RollingWindow::get_misbehavior(1), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); RollingWindow::on_session_end(); // session = 4 - assert_eq!(RollingWindow::get_misbehavior(0), 0); - assert_eq!(RollingWindow::get_misbehavior(1), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + + }); + } + + #[test] + fn unique() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let kinds = vec![(Kind::One, 3), (Kind::Two, 2)]; + RollingWindow::on_initialize(kinds); + + let one: H256 = [1_u8; 32].into(); + let two: H256 = [2_u8; 32].into(); + + for _ in 0..10 { + RollingWindow::report_misbehavior(Kind::One, H256::zero()); + } + + for _ in 0..33 { + RollingWindow::report_misbehavior(Kind::Two, H256::zero()); + } + + RollingWindow::report_misbehavior(Kind::One, one); + RollingWindow::report_misbehavior(Kind::One, two); + + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 12); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 3); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + RollingWindow::on_session_end(); + // session index = 1 + + for _ in 0..5 { + RollingWindow::report_misbehavior(Kind::One, H256::zero()); + } + + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + RollingWindow::on_session_end(); + // session index = 2 + // Kind::Two should have been expired + + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 0); + RollingWindow::on_session_end(); + // session index = 3 + // events that happend in session 0 should have been expired + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 5); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 1); }); } } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 75b9d06be3c27..64cbf17b900fc 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -17,12 +17,14 @@ #![allow(unused)] use super::*; +use serde::{Serialize, Deserialize}; use sr_primitives::{Perbill, traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; use substrate_primitives::{Blake2Hasher, H256}; use srml_staking::{GenesisConfig, StakerStatus}; use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get}}; use std::{marker::PhantomData, cell::RefCell}; use std::collections::{HashSet, HashMap}; +use parity_codec::{Encode, Decode, Codec}; pub type AccountId = u64; pub type Exposure = u64; @@ -58,6 +60,13 @@ impl Get for ExistentialDeposit { } } +#[derive(Debug, Copy, Clone, Encode, Decode, Serialize, Deserialize)] +pub enum Kind { + One, + Two, + Three, +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct Test; @@ -121,7 +130,9 @@ impl timestamp::Trait for Test { type OnTimestampSet = (); } -impl Trait for Test {} +impl Trait for Test { + type Kind = Kind; +} impl session::historical::Trait for Test { type FullIdentification = srml_staking::Exposure; diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 02ee1dac44ba6..e7fd7791a061a 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -28,6 +28,9 @@ use crate::runtime_primitives::ConsensusEngineId; use super::for_each_tuple; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + /// A trait for querying a single fixed value from a type. pub trait Get { /// Return a constant value. @@ -660,9 +663,10 @@ pub trait DoSlash { fn do_slash(identification: Identification, severity: Severity); } -/// temp, obviosly not a trait -#[derive(Copy, Clone, Eq, Hash, PartialEq)] -pub enum MisbehaviorKind { +/// Temp, obviously not a trait +#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub enum Misbehavior { /// .. Equivocation = 0, /// .. From 658d7857f4b9ba98f0583ef6cbc84ef4076ef36d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 12 Jul 2019 16:05:59 +0200 Subject: [PATCH 08/66] rolling_window: don't count unregistered kinds All misconduct kinds need to be registered at startup and only these kinds are possible to use in the rolling window. Added a simple test for it too. --- srml/rolling-window/src/lib.rs | 127 ++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 42 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 33906ac833a99..5b05bdd4b8788 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -64,12 +64,15 @@ decl_module! { impl Module { /// On startup initialize all kinds /// - /// Make sure that all kinds is unique otherwise - /// a kind may be overwritten by another kind - /// - /// Panics if length is bigger than `u32::max_value()` + /// WARNING: Make sure that all kinds is unique otherwise + /// a kind may be overwritten by another kind and cause + /// unexpected behaviour. In other words a window length + /// might be smaller or bigger than expected. pub fn on_initialize(kinds: Vec<(T::Kind, Window)>) { - assert!(kinds.len() <= u32::max_value() as usize); + // just be careful if this would be called other than on startup + // it may remove some existing items in the window but fetching window + // lengths would be faulty afterwards + Self::kill_storage(); for (kind, window_length) in kinds { >::insert(kind, window_length); @@ -88,12 +91,11 @@ impl Module { } }; - // fixme possible iterator invalidation for (kind, _) in >::enumerate() { >::mutate(kind, |window| { let w = >::get(kind); - // it is guaranteed that `s` happend before `session` + // it is guaranteed that `s` happened before `session` window.retain(|(s, _)| { let window_wrapped = s.checked_add(w).is_none(); @@ -113,26 +115,38 @@ impl Module { } /// Report misbehaviour for a kind + /// + /// If a non-existing kind is reported it is ignored pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { - let session = SessionIndex::get(); + if Self::is_registered_kind(kind) { + let session = SessionIndex::get(); - if >::exists(kind) { - >::mutate(kind, |w| w.push((session, footprint))); - } else { - >::insert(kind, vec![(session, footprint)]); + if >::exists(kind) { + >::mutate(kind, |w| w.push((session, footprint))); + } else { + >::insert(kind, vec![(session, footprint)]); + } } } /// Return number of misbehaviors in the current window which /// may include duplicated misbehaviours - pub fn get_misbehaved(kind: T::Kind) -> u64 { - >::get(kind).len() as u64 + pub fn get_misbehaved(kind: T::Kind) -> Option { + if !Self::is_registered_kind(kind) { + return None; + } else { + Some(>::get(kind).len() as u64) + } } /// Return number of misbehaviors in the current window which - /// may include duplicated misbehaviours - pub fn get_misbehaved_unique(kind: T::Kind) -> u64 { + /// ignores duplicated misbehaviours in each session + pub fn get_misbehaved_unique(kind: T::Kind) -> Option { + if !Self::is_registered_kind(kind) { + return None; + } + let window = >::get(kind); let mut seen_ids = rstd::vec::Vec::new(); @@ -141,7 +155,7 @@ impl Module { let mut last_session = 0; for (session, id) in window { - // new session reset `seem_ids` + // new session reset `seen_ids` if session > last_session { seen_ids.clear(); } @@ -155,10 +169,25 @@ impl Module { last_session = session; } - unique + Some(unique) + } + + fn is_registered_kind(kind: T::Kind) -> bool { + >::exists(kind) + } + + fn kill_storage() { + for (key, _) in >::enumerate() { + >::remove(key); + } + + for (key, _) in >::enumerate() { + >::remove(key); + } } } + #[cfg(test)] mod tests { use super::*; @@ -181,33 +210,33 @@ mod tests { for i in 1..=10 { RollingWindow::report_misbehavior(Kind::One, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::One), i); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), i); } for i in 1..=4 { RollingWindow::report_misbehavior(Kind::Two, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), i); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), i); } RollingWindow::on_session_end(); // session = 1 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 4); RollingWindow::on_session_end(); // session = 2 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 4); RollingWindow::on_session_end(); // session = 3 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); RollingWindow::on_session_end(); // session = 4 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); }); } @@ -234,10 +263,10 @@ mod tests { RollingWindow::report_misbehavior(Kind::One, one); RollingWindow::report_misbehavior(Kind::One, two); - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 12); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 3); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 12); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 3); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 1); RollingWindow::on_session_end(); // session index = 1 @@ -245,24 +274,38 @@ mod tests { RollingWindow::report_misbehavior(Kind::One, H256::zero()); } - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 1); RollingWindow::on_session_end(); // session index = 2 // Kind::Two should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 0); RollingWindow::on_session_end(); // session index = 3 // events that happend in session 0 should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 5); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 5); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 1); + }); + } + + #[test] + fn non_existing_kind() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + RollingWindow::on_initialize(vec![]); + + let one: H256 = [1_u8; 32].into(); + + RollingWindow::report_misbehavior(Kind::One, one); + assert!(RollingWindow::get_misbehaved(Kind::One).is_none()); }); } } From b3ae519c42e79a007f42519af7e7e79e9fe4b9e2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 12 Jul 2019 16:07:35 +0200 Subject: [PATCH 09/66] fix: add all static misbehaviours --- core/consensus/babe/src/slash.rs | 14 ++++++++------ srml/support/src/traits.rs | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index 6bce27b1b5bf2..91dad9208cf9f 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -17,9 +17,7 @@ pub trait Trait: srml_system::Trait { /// Type of slashing /// /// FullId - is the full identification of the entity to slash - /// which should in most cases be (AccountId, Exposure) - /// - /// + /// which may be (AccountId, Exposure) type EquivocationSlash: ReportSlash>; } @@ -82,7 +80,7 @@ pub struct MyMisconduct((PhantomData, PhantomData)); // preferable with some linkage to the `misbehavior kind` ideally impl MyMisconduct { fn kind() -> Misbehavior { - Misbehavior::Equivocation + Misbehavior::BabeEquivocation } fn base_severity() -> Perbill { @@ -100,8 +98,12 @@ where let base_seve = Self::base_severity(); RollingWindow::::report_misbehavior(kind, footprint); - // use `RollingWindow::get_misbehaved_uniq` if you want the number of unique misconduct in each session - let num_violations = RollingWindow::::get_misbehaved(kind); + let num_violations = match RollingWindow::::get_misbehaved_unique(kind) { + // if non-registered kind was registered (probably a bug) + // don't slash in that case... + None => return, + Some(v) => v, + }; // number of validators let n = 50; diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index e7fd7791a061a..e67fa9e9af658 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -667,8 +667,20 @@ pub trait DoSlash { #[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub enum Misbehavior { - /// .. - Equivocation = 0, - /// .. - InvalidBlock = 1, + /// Validator is not online + Unresponsiveness, + /// Unjustified vote + GrandpaUnjustifiedVote, + /// Rejecting set of votes + GranpaRejectSetVotes, + /// Equivocation + GrandpaEquivocation, + /// Invalid Vote + GrandpaInvalidVote, + /// Equivocation + BabeEquivocation, + /// Invalid block + BabeInvalidBlock, + /// Parachain Invalid validity statement + ParachainInvalidValidityStatement, } From 148848fac6c5730ade70c8e4faf67497881040f2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 12 Jul 2019 16:50:37 +0200 Subject: [PATCH 10/66] nits --- core/consensus/babe/src/slash.rs | 4 ++-- srml/rolling-window/src/lib.rs | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index 91dad9208cf9f..b5947af50c048 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -11,7 +11,7 @@ use runtime_primitives::{Perbill, traits::Hash}; type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; /// Trait for reporting slashes -pub trait Trait: srml_system::Trait { +pub trait Trait: srml_rolling_window::Trait { /// Key that identifies the owner type KeyOwner: KeyOwnerProofSystem; /// Type of slashing @@ -90,7 +90,7 @@ impl MyMisconduct { impl ReportSlash for MyMisconduct where - T: Trait + srml_rolling_window::Trait, + T: Trait, DS: DoSlash, { fn slash(footprint: T::Hash, who: Who) { diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 5b05bdd4b8788..869db631d32c1 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -14,11 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! # noooo docs +//! # Rolling Window module +//! +//! ## Overview +//! +//! The Rolling Window Module is similar to `simple moving average` except +//! that it just reports the number of occurrences in the window instead of +//! calculating the average. +//! +//! It is mainly implemented to keep track of misbehaviors and only the take +//! the last `sessions` of misbehaviors into account. //! // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs, rust_2018_idioms)] #[cfg(test)] mod mock; @@ -32,7 +42,9 @@ use sr_primitives::traits::MaybeSerializeDebug; type Window = u32; type Session = u32; +/// Rolling Window trait pub trait Trait: system::Trait { + /// Kind to report type Kind: Copy + Clone + Codec + MaybeSerializeDebug; } @@ -40,7 +52,7 @@ decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into an unique hash and the session number for the misconduct + /// It maps each kind into a hash and the session number when it occurred MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; /// Rolling window length for different kinds @@ -55,9 +67,6 @@ decl_storage! { decl_module! { /// Rolling Window module - /// - /// It is similar to a `simple moving average` except that it just - /// return the number of misbehaviors in the window instead of calculating the average pub struct Module for enum Call where origin: T::Origin {} } @@ -178,7 +187,7 @@ impl Module { fn kill_storage() { for (key, _) in >::enumerate() { - >::remove(key); + >::remove(key); } for (key, _) in >::enumerate() { From bf86c9d4d72d3d7c17408467e59030d6b9104e25 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 13 Jul 2019 00:00:53 +0200 Subject: [PATCH 11/66] feat: add implementation `DoSlash` for Staking --- srml/rolling-window/src/lib.rs | 2 +- srml/staking/src/lib.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 869db631d32c1..0edbaf65e498b 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -52,7 +52,7 @@ decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into a hash and the session number when it occurred + /// It maps each kind into a hash and session number when it occurred MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; /// Rolling window length for different kinds diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 6394f9f9e57a7..29a4e92d26712 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1385,9 +1385,12 @@ impl SelectInitialValidators for Module { /// StakingSlasher pub struct StakingSlasher(rstd::marker::PhantomData); +impl DoSlash>)> for StakingSlasher { + fn do_slash(severity: Perbill, (who, exposure): (T::AccountId, Exposure>)) { + T::Currency::slash(&who, severity * exposure.own); -impl DoSlash)> for StakingSlasher { - fn do_slash(severity: Severity, (who, exposure): (T::AccountId, Exposure)) { - // implement + for nominator in exposure.others { + T::Currency::slash(&who, severity * nominator.value); + } } } From 9919efc692b46342216115605b9b1498c62cf353 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 11:14:31 +0200 Subject: [PATCH 12/66] pass `window_len` instead of static lookup table --- core/consensus/babe/src/slash.rs | 19 ++-- srml/rolling-window/src/lib.rs | 168 ++++++++++--------------------- srml/rolling-window/src/mock.rs | 9 +- srml/support/src/traits.rs | 14 ++- 4 files changed, 74 insertions(+), 136 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index b5947af50c048..f08d04c36d99a 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -80,7 +80,11 @@ pub struct MyMisconduct((PhantomData, PhantomData)); // preferable with some linkage to the `misbehavior kind` ideally impl MyMisconduct { fn kind() -> Misbehavior { - Misbehavior::BabeEquivocation + Misbehavior::Equivocation + } + + fn window_length() -> u32 { + 10 } fn base_severity() -> Perbill { @@ -96,14 +100,11 @@ where fn slash(footprint: T::Hash, who: Who) { let kind = Self::kind(); let base_seve = Self::base_severity(); - RollingWindow::::report_misbehavior(kind, footprint); + let window_length = Self::window_length(); - let num_violations = match RollingWindow::::get_misbehaved_unique(kind) { - // if non-registered kind was registered (probably a bug) - // don't slash in that case... - None => return, - Some(v) => v, - }; + RollingWindow::::report_misbehavior(kind, window_length, footprint); + + let num_violations = RollingWindow::::get_misbehaved_unique(kind); // number of validators let n = 50; @@ -114,7 +115,7 @@ where } else if num_violations < 1000 { // 3k / n^2 // ignore base severity because `Permill` doesn't provide addition, e.g. (base + estimate) - Perbill::from_rational_approximation(3 * num_violations, n*n) + Perbill::from_rational_approximation(3 * num_violations, n * n) } else { Perbill::one() }; diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 0edbaf65e498b..ffc18556e021a 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -39,7 +39,7 @@ use srml_support::{ use parity_codec::Codec; use sr_primitives::traits::MaybeSerializeDebug; -type Window = u32; +type WindowLength = u32; type Session = u32; /// Rolling Window trait @@ -52,13 +52,8 @@ decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into a hash and session number when it occurred - MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; - - /// Rolling window length for different kinds - /// - /// Each kind has its own window length - WindowLength get(w) config(): linked_map T::Kind => Window; + /// It maps each kind into a hash (identity of reporter), window_length and session number when it occurred + MisconductReports get(kind): linked_map T::Kind => Vec<(Session, WindowLength, T::Hash)>; /// Session index SessionIndex get(s) config(): u32 = 0; @@ -71,23 +66,6 @@ decl_module! { } impl Module { - /// On startup initialize all kinds - /// - /// WARNING: Make sure that all kinds is unique otherwise - /// a kind may be overwritten by another kind and cause - /// unexpected behaviour. In other words a window length - /// might be smaller or bigger than expected. - pub fn on_initialize(kinds: Vec<(T::Kind, Window)>) { - // just be careful if this would be called other than on startup - // it may remove some existing items in the window but fetching window - // lengths would be faulty afterwards - Self::kill_storage(); - - for (kind, window_length) in kinds { - >::insert(kind, window_length); - } - } - /// Remove items that doesn't fit into the rolling window pub fn on_session_end() { let mut session_wrapped = false; @@ -102,13 +80,11 @@ impl Module { for (kind, _) in >::enumerate() { >::mutate(kind, |window| { - let w = >::get(kind); - // it is guaranteed that `s` happened before `session` - window.retain(|(s, _)| { + window.retain(|(s, w, _)| { - let window_wrapped = s.checked_add(w).is_none(); - let x = s.wrapping_add(w); + let window_wrapped = s.checked_add(*w).is_none(); + let x = s.wrapping_add(*w); match (session_wrapped, window_wrapped) { (false, true) => true, @@ -126,36 +102,26 @@ impl Module { /// Report misbehaviour for a kind /// /// If a non-existing kind is reported it is ignored - pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { - if Self::is_registered_kind(kind) { - let session = SessionIndex::get(); - - if >::exists(kind) { - >::mutate(kind, |w| w.push((session, footprint))); - } else { - >::insert(kind, vec![(session, footprint)]); - } + pub fn report_misbehavior(kind: T::Kind, window_length: u32, footprint: T::Hash) { + let session = SessionIndex::get(); + + if >::exists(kind) { + >::mutate(kind, |w| w.push((session, window_length, footprint))); + } else { + >::insert(kind, vec![(session, window_length, footprint)]); } } /// Return number of misbehaviors in the current window which /// may include duplicated misbehaviours - pub fn get_misbehaved(kind: T::Kind) -> Option { - if !Self::is_registered_kind(kind) { - return None; - } else { - Some(>::get(kind).len() as u64) - } + pub fn get_misbehaved(kind: T::Kind) -> u64 { + >::get(kind).len() as u64 } /// Return number of misbehaviors in the current window which /// ignores duplicated misbehaviours in each session - pub fn get_misbehaved_unique(kind: T::Kind) -> Option { - if !Self::is_registered_kind(kind) { - return None; - } - + pub fn get_misbehaved_unique(kind: T::Kind) -> u64 { let window = >::get(kind); let mut seen_ids = rstd::vec::Vec::new(); @@ -163,7 +129,7 @@ impl Module { // session can never be smaller than 0 let mut last_session = 0; - for (session, id) in window { + for (session, _, id) in window { // new session reset `seen_ids` if session > last_session { seen_ids.clear(); @@ -178,21 +144,7 @@ impl Module { last_session = session; } - Some(unique) - } - - fn is_registered_kind(kind: T::Kind) -> bool { - >::exists(kind) - } - - fn kill_storage() { - for (key, _) in >::enumerate() { - >::remove(key); - } - - for (key, _) in >::enumerate() { - >::remove(key); - } + unique } } @@ -213,39 +165,39 @@ mod tests { .build(), || { - let kinds = vec![(Kind::One, 4), (Kind::Two, 3)]; + const WINDOW_ONE: u32 = 4; + const WINDOW_TWO: u32 = 3; - RollingWindow::on_initialize(kinds); for i in 1..=10 { - RollingWindow::report_misbehavior(Kind::One, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), i); + RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), i); } for i in 1..=4 { - RollingWindow::report_misbehavior(Kind::Two, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), i); + RollingWindow::report_misbehavior(Kind::Two, WINDOW_TWO, H256::zero()); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), i); } RollingWindow::on_session_end(); // session = 1 - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); RollingWindow::on_session_end(); // session = 2 - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); RollingWindow::on_session_end(); // session = 3 - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); RollingWindow::on_session_end(); // session = 4 - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); }); } @@ -255,66 +207,52 @@ mod tests { with_externalities(&mut ExtBuilder::default() .build(), || { - let kinds = vec![(Kind::One, 3), (Kind::Two, 2)]; - RollingWindow::on_initialize(kinds); + const WINDOW_ONE: u32 = 3; + const WINDOW_TWO: u32 = 2; let one: H256 = [1_u8; 32].into(); let two: H256 = [2_u8; 32].into(); for _ in 0..10 { - RollingWindow::report_misbehavior(Kind::One, H256::zero()); + RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); } for _ in 0..33 { - RollingWindow::report_misbehavior(Kind::Two, H256::zero()); + RollingWindow::report_misbehavior(Kind::Two, WINDOW_TWO, H256::zero()); } - RollingWindow::report_misbehavior(Kind::One, one); - RollingWindow::report_misbehavior(Kind::One, two); + RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, one); + RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, two); - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 12); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 3); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 12); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 3); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); RollingWindow::on_session_end(); // session index = 1 for _ in 0..5 { - RollingWindow::report_misbehavior(Kind::One, H256::zero()); + RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); } - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); RollingWindow::on_session_end(); // session index = 2 // Kind::Two should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two).unwrap(), 0); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two).unwrap(), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 0); RollingWindow::on_session_end(); // session index = 3 // events that happend in session 0 should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One).unwrap(), 5); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One).unwrap(), 1); - }); - } - - #[test] - fn non_existing_kind() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - RollingWindow::on_initialize(vec![]); - - let one: H256 = [1_u8; 32].into(); - - RollingWindow::report_misbehavior(Kind::One, one); - assert!(RollingWindow::get_misbehaved(Kind::One).is_none()); + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 5); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 1); }); } } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 64cbf17b900fc..f96e434bb78d3 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -128,6 +128,7 @@ impl session::Trait for Test { impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; } impl Trait for Test { @@ -139,6 +140,10 @@ impl session::historical::Trait for Test { type FullIdentificationOf = srml_staking::ExposureOf; } +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} + parameter_types! { pub const SessionsPerEra: session::SessionIndex = 3; pub const BondingDuration: srml_staking::EraIndex = 3; @@ -279,10 +284,6 @@ impl ExtBuilder { invulnerables: vec![], }.assimilate_storage(&mut t, &mut c); - let _ = timestamp::GenesisConfig::{ - minimum_period: 5, - }.assimilate_storage(&mut t, &mut c); - let _ = session::GenesisConfig:: { keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), }.assimilate_storage(&mut t, &mut c); diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index e67fa9e9af658..8c710452615ab 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -670,17 +670,15 @@ pub enum Misbehavior { /// Validator is not online Unresponsiveness, /// Unjustified vote - GrandpaUnjustifiedVote, + UnjustifiedVote, /// Rejecting set of votes - GranpaRejectSetVotes, + RejectSetVotes, /// Equivocation - GrandpaEquivocation, + Equivocation, /// Invalid Vote - GrandpaInvalidVote, - /// Equivocation - BabeEquivocation, + InvalidVote, /// Invalid block - BabeInvalidBlock, + InvalidBlock, /// Parachain Invalid validity statement - ParachainInvalidValidityStatement, + ParachainInvalid, } From 32fc2711a9d050ea919c1372edbee0ef7da0efb1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 12:34:42 +0200 Subject: [PATCH 13/66] simplify code --- srml/rolling-window/src/lib.rs | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index ffc18556e021a..5c33e22c86f6c 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -68,30 +68,15 @@ decl_module! { impl Module { /// Remove items that doesn't fit into the rolling window pub fn on_session_end() { - let mut session_wrapped = false; - - let session = match SessionIndex::get().checked_add(1) { - Some(v) => v, - None => { - session_wrapped = true; - 0 - } - }; + let session = match SessionIndex::get().wrapping_add(1); for (kind, _) in >::enumerate() { >::mutate(kind, |window| { - // it is guaranteed that `s` happened before `session` - window.retain(|(s, w, _)| { - - let window_wrapped = s.checked_add(*w).is_none(); - let x = s.wrapping_add(*w); - - match (session_wrapped, window_wrapped) { - (false, true) => true, - (true, false) => false, - (true, true) | (false, false) if x > session => true, - _ => false - } + // it is guaranteed that `reported_session` happened before `session` + window.retain(|(reported_session, window_length, _)| { + + let diff = session.wrapping_sub(reported_session); + diff < window_length }); }); } From 166bc5c3e55270cc293e2c1f1d6edcb7eaca25a5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 13:03:03 +0200 Subject: [PATCH 14/66] nits --- srml/rolling-window/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 5c33e22c86f6c..a57ba48c21c76 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -68,15 +68,15 @@ decl_module! { impl Module { /// Remove items that doesn't fit into the rolling window pub fn on_session_end() { - let session = match SessionIndex::get().wrapping_add(1); + let session = SessionIndex::get().wrapping_add(1); for (kind, _) in >::enumerate() { >::mutate(kind, |window| { // it is guaranteed that `reported_session` happened before `session` window.retain(|(reported_session, window_length, _)| { - let diff = session.wrapping_sub(reported_session); - diff < window_length + let diff = session.wrapping_sub(*reported_session); + diff < *window_length }); }); } From 032c7ce9f58512059b213682722ff7c01821fa1d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 13:55:11 +0200 Subject: [PATCH 15/66] cleanup --- core/consensus/babe/src/slash.rs | 9 ++++----- srml/rolling-window/src/lib.rs | 7 ++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index f08d04c36d99a..f63ff2e601851 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -74,11 +74,9 @@ impl BabeSlashReporter { } } -pub struct MyMisconduct((PhantomData, PhantomData)); +pub struct BabeEquivocation((PhantomData, PhantomData)); -// do this more elegant with some macro -// preferable with some linkage to the `misbehavior kind` ideally -impl MyMisconduct { +impl BabeEquivocation { fn kind() -> Misbehavior { Misbehavior::Equivocation } @@ -92,7 +90,7 @@ impl MyMisconduct { } } -impl ReportSlash for MyMisconduct +impl ReportSlash for BabeEquivocation where T: Trait, DS: DoSlash, @@ -107,6 +105,7 @@ where let num_violations = RollingWindow::::get_misbehaved_unique(kind); // number of validators + // should probably be something like: `srml_staking::Module::validators().len()` let n = 50; // example how to estimate severity diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index a57ba48c21c76..3887052443ae1 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -74,7 +74,6 @@ impl Module { >::mutate(kind, |window| { // it is guaranteed that `reported_session` happened before `session` window.retain(|(reported_session, window_length, _)| { - let diff = session.wrapping_sub(*reported_session); diff < *window_length }); @@ -85,8 +84,6 @@ impl Module { } /// Report misbehaviour for a kind - /// - /// If a non-existing kind is reported it is ignored pub fn report_misbehavior(kind: T::Kind, window_length: u32, footprint: T::Hash) { let session = SessionIndex::get(); @@ -110,7 +107,7 @@ impl Module { let window = >::get(kind); let mut seen_ids = rstd::vec::Vec::new(); - let mut unique = 0; + let mut unique = 0_u64; // session can never be smaller than 0 let mut last_session = 0; @@ -122,7 +119,7 @@ impl Module { // Unfortunately O(n) if !seen_ids.contains(&id) { - unique += 1; + unique = unique.saturating_add(1); seen_ids.push(id); } From eaeb6c8113574c049543c8e4b83b98da2c648f3b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 15:08:16 +0200 Subject: [PATCH 16/66] impl `window_length` for Misbehavior type --- core/consensus/babe/src/slash.rs | 12 ++---- srml/rolling-window/src/lib.rs | 72 +++++++++++++++----------------- srml/rolling-window/src/mock.rs | 12 +++++- srml/support/src/traits.rs | 36 ++++++++++++---- 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs index f63ff2e601851..185ac32182d49 100644 --- a/core/consensus/babe/src/slash.rs +++ b/core/consensus/babe/src/slash.rs @@ -78,11 +78,8 @@ pub struct BabeEquivocation((PhantomData, PhantomData)); impl BabeEquivocation { fn kind() -> Misbehavior { - Misbehavior::Equivocation - } - - fn window_length() -> u32 { - 10 + // window length is 10 sessions + Misbehavior::Equivocation(10) } fn base_severity() -> Perbill { @@ -98,9 +95,8 @@ where fn slash(footprint: T::Hash, who: Who) { let kind = Self::kind(); let base_seve = Self::base_severity(); - let window_length = Self::window_length(); - RollingWindow::::report_misbehavior(kind, window_length, footprint); + RollingWindow::::report_misbehavior(kind, footprint); let num_violations = RollingWindow::::get_misbehaved_unique(kind); @@ -164,7 +160,7 @@ mod tests { impl Trait for Test { type KeyOwner = FakeSlasher; - type EquivocationSlash = MyMisconduct>; + type EquivocationSlash = BabeEquivocation>; } pub struct FakeSlasher(PhantomData); diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 3887052443ae1..a1ef56b306105 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -35,25 +35,25 @@ mod mock; use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_storage, + traits::WindowLength }; use parity_codec::Codec; use sr_primitives::traits::MaybeSerializeDebug; -type WindowLength = u32; type Session = u32; /// Rolling Window trait pub trait Trait: system::Trait { - /// Kind to report - type Kind: Copy + Clone + Codec + MaybeSerializeDebug; + /// Kind to report with window length + type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; } decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into a hash (identity of reporter), window_length and session number when it occurred - MisconductReports get(kind): linked_map T::Kind => Vec<(Session, WindowLength, T::Hash)>; + /// It maps each kind into a hash (identity of reporter), session number when it occurred + MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; /// Session index SessionIndex get(s) config(): u32 = 0; @@ -71,9 +71,10 @@ impl Module { let session = SessionIndex::get().wrapping_add(1); for (kind, _) in >::enumerate() { + let window_length = kind.window_length(); >::mutate(kind, |window| { // it is guaranteed that `reported_session` happened before `session` - window.retain(|(reported_session, window_length, _)| { + window.retain(|(reported_session, _)| { let diff = session.wrapping_sub(*reported_session); diff < *window_length }); @@ -84,13 +85,13 @@ impl Module { } /// Report misbehaviour for a kind - pub fn report_misbehavior(kind: T::Kind, window_length: u32, footprint: T::Hash) { + pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { let session = SessionIndex::get(); if >::exists(kind) { - >::mutate(kind, |w| w.push((session, window_length, footprint))); + >::mutate(kind, |w| w.push((session, footprint))); } else { - >::insert(kind, vec![(session, window_length, footprint)]); + >::insert(kind, vec![(session, footprint)]); } } @@ -111,7 +112,7 @@ impl Module { // session can never be smaller than 0 let mut last_session = 0; - for (session, _, id) in window { + for (session, id) in window { // new session reset `seen_ids` if session > last_session { seen_ids.clear(); @@ -147,17 +148,13 @@ mod tests { .build(), || { - const WINDOW_ONE: u32 = 4; - const WINDOW_TWO: u32 = 3; - - for i in 1..=10 { - RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); + RollingWindow::report_misbehavior(Kind::One, H256::zero()); assert_eq!(RollingWindow::get_misbehaved(Kind::One), i); } for i in 1..=4 { - RollingWindow::report_misbehavior(Kind::Two, WINDOW_TWO, H256::zero()); + RollingWindow::report_misbehavior(Kind::Two, H256::zero()); assert_eq!(RollingWindow::get_misbehaved(Kind::Two), i); } @@ -189,52 +186,49 @@ mod tests { with_externalities(&mut ExtBuilder::default() .build(), || { - const WINDOW_ONE: u32 = 3; - const WINDOW_TWO: u32 = 2; - let one: H256 = [1_u8; 32].into(); let two: H256 = [2_u8; 32].into(); for _ in 0..10 { - RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); + RollingWindow::report_misbehavior(Kind::Two, H256::zero()); } for _ in 0..33 { - RollingWindow::report_misbehavior(Kind::Two, WINDOW_TWO, H256::zero()); + RollingWindow::report_misbehavior(Kind::Three, H256::zero()); } - RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, one); - RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, two); + RollingWindow::report_misbehavior(Kind::Two, one); + RollingWindow::report_misbehavior(Kind::Two, two); - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 12); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 3); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 12); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 3); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 1); RollingWindow::on_session_end(); // session index = 1 for _ in 0..5 { - RollingWindow::report_misbehavior(Kind::One, WINDOW_ONE, H256::zero()); + RollingWindow::report_misbehavior(Kind::Two, H256::zero()); } - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 33); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 1); RollingWindow::on_session_end(); // session index = 2 - // Kind::Two should have been expired + // Kind::Three should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 17); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 4); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 0); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 0); RollingWindow::on_session_end(); // session index = 3 // events that happend in session 0 should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 5); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::One), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 5); + assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); }); } } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index f96e434bb78d3..fef78ed375cd8 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -21,7 +21,7 @@ use serde::{Serialize, Deserialize}; use sr_primitives::{Perbill, traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; use substrate_primitives::{Blake2Hasher, H256}; use srml_staking::{GenesisConfig, StakerStatus}; -use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get}}; +use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get, WindowLength}}; use std::{marker::PhantomData, cell::RefCell}; use std::collections::{HashSet, HashMap}; use parity_codec::{Encode, Decode, Codec}; @@ -67,6 +67,16 @@ pub enum Kind { Three, } +impl WindowLength for Kind { + fn window_length(&self) -> &u32 { + match self { + Kind::One => &4, + Kind::Two => &3, + Kind::Three => &2, + } + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct Test; diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 8c710452615ab..1a275474f55ee 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -663,22 +663,42 @@ pub trait DoSlash { fn do_slash(identification: Identification, severity: Severity); } -/// Temp, obviously not a trait +/// Trait for representing window length +pub trait WindowLength { + /// Fetch window length + fn window_length(&self) -> &T; +} + +/// Misbehavior type which takes window length as input #[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub enum Misbehavior { /// Validator is not online - Unresponsiveness, + Unresponsiveness(u32), /// Unjustified vote - UnjustifiedVote, + UnjustifiedVote(u32), /// Rejecting set of votes - RejectSetVotes, + RejectSetVotes(u32), /// Equivocation - Equivocation, + Equivocation(u32), /// Invalid Vote - InvalidVote, + InvalidVote(u32), /// Invalid block - InvalidBlock, + InvalidBlock(u32), /// Parachain Invalid validity statement - ParachainInvalid, + ParachainInvalidity(u32), +} + +impl WindowLength for Misbehavior { + fn window_length(&self) -> &u32 { + match self { + Misbehavior::Unresponsiveness(len) => len, + Misbehavior::UnjustifiedVote(len) => len, + Misbehavior::RejectSetVotes(len) => len, + Misbehavior::Equivocation(len) => len, + Misbehavior::InvalidVote(len) => len, + Misbehavior::InvalidBlock(len) => len, + Misbehavior::ParachainInvalidity(len) => len, + } + } } From 1ccf201557640515f76ceae48a42867565c2a51a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 15:38:45 +0200 Subject: [PATCH 17/66] clarify --- srml/support/src/traits.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 1a275474f55ee..749d88cc84c76 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -670,6 +670,8 @@ pub trait WindowLength { } /// Misbehavior type which takes window length as input +/// Each variant and its data is a seperate kind +/// For example `Unresponsiveness(0)` and `Unresponsiveness(1)` are different #[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub enum Misbehavior { From b08925828e213f12eb524edc8fcea6100e0a27b4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 16:03:31 +0200 Subject: [PATCH 18/66] make ready --- core/consensus/babe/Cargo.toml | 2 - core/consensus/babe/src/lib.rs | 26 +--- core/consensus/babe/src/slash.rs | 199 ------------------------------- node/runtime/src/lib.rs | 40 ++++++- srml/support/src/traits.rs | 36 ------ 5 files changed, 41 insertions(+), 262 deletions(-) delete mode 100644 core/consensus/babe/src/slash.rs diff --git a/core/consensus/babe/Cargo.toml b/core/consensus/babe/Cargo.toml index 4578ae5db8ceb..29c706dd132c4 100644 --- a/core/consensus/babe/Cargo.toml +++ b/core/consensus/babe/Cargo.toml @@ -15,8 +15,6 @@ runtime_io = { package = "sr-io", path = "../../sr-io" } inherents = { package = "substrate-inherents", path = "../../inherents" } substrate-telemetry = { path = "../../telemetry" } srml-babe = { path = "../../../srml/babe" } -srml-system = { path = "../../../srml/system" } -srml-rolling-window = { path = "../../../srml/rolling-window" } client = { package = "substrate-client", path = "../../client" } consensus_common = { package = "substrate-consensus-common", path = "../common" } slots = { package = "substrate-consensus-slots", path = "../slots" } diff --git a/core/consensus/babe/src/lib.rs b/core/consensus/babe/src/lib.rs index b015585602fc2..454b356f7c282 100644 --- a/core/consensus/babe/src/lib.rs +++ b/core/consensus/babe/src/lib.rs @@ -23,11 +23,10 @@ //! This crate is highly unstable and experimental. Breaking changes may //! happen at any point. This crate is also missing features, such as banning //! of malicious validators, that are essential for a production network. -// #![forbid(unsafe_code, missing_docs, unused_must_use)] -// #![cfg_attr(not(test), forbid(dead_code))] +#![forbid(unsafe_code, missing_docs, unused_must_use)] +#![cfg_attr(not(test), forbid(dead_code))] extern crate core; mod digest; -mod slash; use digest::CompatibleDigestItem; pub use digest::{BabePreDigest, BABE_VRF_PREFIX}; pub use babe_primitives::*; @@ -87,7 +86,6 @@ use slots::{SlotWorker, SlotData, SlotInfo, SlotCompatible, SignedDuration}; pub use babe_primitives::AuthorityId; - /// A slot duration. Create with `get_or_compute`. // FIXME: Once Rust has higher-kinded types, the duplication between this // and `super::babe::Config` can be eliminated. @@ -122,7 +120,6 @@ impl Config { } } - impl SlotCompatible for BabeLink { fn extract_timestamp_and_slot( &self, @@ -504,25 +501,6 @@ fn check_header( &header, author, ).map_err(|e| e.to_string())? { - - - // @niklasad1: temp just to check the everything fits together - //.... - //..... - // This will report a misconduct and slash - { - let _e = slash::EquivocationProof::new( - equivocation_proof.fst_header().clone(), - equivocation_proof.snd_header().clone(), - Default::default(), - 0, - ); - - // T: type that implements system::Trait - // slash::BabeSlashReporter::::report_equivocation(e); - } - - info!( "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}", author, diff --git a/core/consensus/babe/src/slash.rs b/core/consensus/babe/src/slash.rs deleted file mode 100644 index 185ac32182d49..0000000000000 --- a/core/consensus/babe/src/slash.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! Niklas temp file - -use parity_codec::{Decode, Encode}; -use primitives::sr25519; - -use srml_rolling_window::Module as RollingWindow; -use std::marker::PhantomData; -use runtime_support::traits::{KeyOwnerProofSystem, ReportSlash, DoSlash, Misbehavior}; -use runtime_primitives::{Perbill, traits::Hash}; - -type FullId = <::KeyOwner as KeyOwnerProofSystem>::FullIdentification; - -/// Trait for reporting slashes -pub trait Trait: srml_rolling_window::Trait { - /// Key that identifies the owner - type KeyOwner: KeyOwnerProofSystem; - /// Type of slashing - /// - /// FullId - is the full identification of the entity to slash - /// which may be (AccountId, Exposure) - type EquivocationSlash: ReportSlash>; -} - -/// Represents an Babe equivocation proof. -#[derive(Debug, Clone, Encode, Decode, PartialEq)] -pub struct EquivocationProof { - first_header: H, - second_header: H, - author: sr25519::Public, - membership_proof: Proof, -} - -impl EquivocationProof { - /// Create a new Babe equivocation proof - pub fn new( - first_header: H, - second_header: H, - author: sr25519::Public, - membership_proof: P, - ) -> Self { - Self { - first_header, - second_header, - author, - membership_proof, - } - } -} - -/// Babe slash reporter -pub struct BabeSlashReporter(PhantomData); - -impl BabeSlashReporter { - - /// Report an equivocation - pub fn report_equivocation( - proof: EquivocationProof< - T::Hash, - <::KeyOwner as KeyOwnerProofSystem>::Proof - >, - ) { - let identification = T::KeyOwner::check_proof( - proof.author.clone(), - proof.membership_proof - ).expect("mocked will always succeed; qed"); - - //check headers - - // Assumption: author in the context __is__ the validator/authority that has voted/signed - // two blocks in the same round - let hash = T::Hashing::hash_of(&proof.author); - - T::EquivocationSlash::slash(hash, identification); - } -} - -pub struct BabeEquivocation((PhantomData, PhantomData)); - -impl BabeEquivocation { - fn kind() -> Misbehavior { - // window length is 10 sessions - Misbehavior::Equivocation(10) - } - - fn base_severity() -> Perbill { - Perbill::from_rational_approximation(1_u32, 100_u32) - } -} - -impl ReportSlash for BabeEquivocation -where - T: Trait, - DS: DoSlash, -{ - fn slash(footprint: T::Hash, who: Who) { - let kind = Self::kind(); - let base_seve = Self::base_severity(); - - RollingWindow::::report_misbehavior(kind, footprint); - - let num_violations = RollingWindow::::get_misbehaved_unique(kind); - - // number of validators - // should probably be something like: `srml_staking::Module::validators().len()` - let n = 50; - - // example how to estimate severity - let severity = if num_violations < 10 { - base_seve - } else if num_violations < 1000 { - // 3k / n^2 - // ignore base severity because `Permill` doesn't provide addition, e.g. (base + estimate) - Perbill::from_rational_approximation(3 * num_violations, n * n) - } else { - Perbill::one() - }; - - DS::do_slash(who, severity); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use srml_system as system; - use runtime_primitives::traits::{BlakeTwo256, IdentityLookup}; - use runtime_primitives::testing::{H256, Header}; - use runtime_support::{impl_outer_origin, parameter_types}; - - pub type AccountId = u64; - pub type Exposure = u64; - - #[derive(Clone, PartialEq, Eq, Debug)] - pub struct Test; - - parameter_types! { - pub const BlockHashCount: u64 = 250; - } - - impl_outer_origin!{ - pub enum Origin for Test {} - } - - impl srml_rolling_window::Trait for Test { - type Kind = Misbehavior; - } - - impl system::Trait for Test { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - } - - impl Trait for Test { - type KeyOwner = FakeSlasher; - type EquivocationSlash = BabeEquivocation>; - } - - pub struct FakeSlasher(PhantomData); - - impl DoSlash<(T::AccountId, Exposure), Perbill> for FakeSlasher { - fn do_slash(_: (T::AccountId, Exposure), _severity: Perbill) { - // does nothing ... - } - } - - impl KeyOwnerProofSystem for FakeSlasher { - type Proof = Vec; - type FullIdentification = (T::AccountId, Exposure); - - fn prove(key: sr25519::Public) -> Option { - Some(Vec::new()) - } - - fn check_proof(key: sr25519::Public, proof: Self::Proof) -> Option { - Some((Default::default(), 0)) - } - } - - #[test] - fn foo() { - - let eq = EquivocationProof::new( - Default::default(), - Default::default(), - Default::default(), - Vec::new(), - ); - - BabeSlashReporter::::report_equivocation(eq); - } -} diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 72a746f20f46c..a57060262657b 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ use rstd::prelude::*; use support::{ - construct_runtime, parameter_types, traits::{SplitTwoWays, Currency, OnUnbalanced} + construct_runtime, parameter_types, traits::{SplitTwoWays, Currency, OnUnbalanced, WindowLength} }; use substrate_primitives::u32_trait::{_1, _2, _3, _4}; use node_primitives::{ @@ -43,6 +43,9 @@ use version::RuntimeVersion; use elections::VoteIndex; #[cfg(any(feature = "std", test))] use version::NativeVersion; +#[cfg(any(feature = "std", test))] +use serde::{Serialize, Deserialize}; +use parity_codec::{Encode, Decode}; use substrate_primitives::OpaqueMetadata; use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; use finality_tracker::{DEFAULT_REPORT_LATENCY, DEFAULT_WINDOW_SIZE}; @@ -518,3 +521,38 @@ impl_runtime_apis! { } } } + +/// Misbehavior type which takes window length as input +/// Each variant and its data is a seperate kind +#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub enum Misbehavior { + /// Validator is not online + Unresponsiveness(u32), + /// Unjustified vote + UnjustifiedVote(u32), + /// Rejecting set of votes + RejectSetVotes(u32), + /// Equivocation + Equivocation(u32), + /// Invalid Vote + InvalidVote(u32), + /// Invalid block + InvalidBlock(u32), + /// Parachain Invalid validity statement + ParachainInvalidity(u32), +} + +impl WindowLength for Misbehavior { + fn window_length(&self) -> &u32 { + match self { + Misbehavior::Unresponsiveness(len) => len, + Misbehavior::UnjustifiedVote(len) => len, + Misbehavior::RejectSetVotes(len) => len, + Misbehavior::Equivocation(len) => len, + Misbehavior::InvalidVote(len) => len, + Misbehavior::InvalidBlock(len) => len, + Misbehavior::ParachainInvalidity(len) => len, + } + } +} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 749d88cc84c76..66e94a23ce349 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -668,39 +668,3 @@ pub trait WindowLength { /// Fetch window length fn window_length(&self) -> &T; } - -/// Misbehavior type which takes window length as input -/// Each variant and its data is a seperate kind -/// For example `Unresponsiveness(0)` and `Unresponsiveness(1)` are different -#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub enum Misbehavior { - /// Validator is not online - Unresponsiveness(u32), - /// Unjustified vote - UnjustifiedVote(u32), - /// Rejecting set of votes - RejectSetVotes(u32), - /// Equivocation - Equivocation(u32), - /// Invalid Vote - InvalidVote(u32), - /// Invalid block - InvalidBlock(u32), - /// Parachain Invalid validity statement - ParachainInvalidity(u32), -} - -impl WindowLength for Misbehavior { - fn window_length(&self) -> &u32 { - match self { - Misbehavior::Unresponsiveness(len) => len, - Misbehavior::UnjustifiedVote(len) => len, - Misbehavior::RejectSetVotes(len) => len, - Misbehavior::Equivocation(len) => len, - Misbehavior::InvalidVote(len) => len, - Misbehavior::InvalidBlock(len) => len, - Misbehavior::ParachainInvalidity(len) => len, - } - } -} From f50f5fcc6c1f08ec18292d186366177c48947efe Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 15 Jul 2019 16:34:13 +0200 Subject: [PATCH 19/66] nits --- Cargo.lock | 2 -- srml/support/src/traits.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cc94e1bafcdd..076539ce7ac05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4358,9 +4358,7 @@ dependencies = [ "sr-primitives 2.0.0", "sr-version 2.0.0", "srml-babe 2.0.0", - "srml-rolling-window 2.0.0", "srml-support 2.0.0", - "srml-system 2.0.0", "substrate-client 2.0.0", "substrate-consensus-babe-primitives 2.0.0", "substrate-consensus-common 2.0.0", diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 66e94a23ce349..5ab25fc5e968b 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -28,9 +28,6 @@ use crate::runtime_primitives::ConsensusEngineId; use super::for_each_tuple; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; - /// A trait for querying a single fixed value from a type. pub trait Get { /// Return a constant value. From b383c4ba883cc3a08f71a7035f2d10d93e8485d8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 16 Jul 2019 21:15:58 +0200 Subject: [PATCH 20/66] feat: `impl_kind` and `impl_base_severity` macros --- srml/rolling-window/src/lib.rs | 65 +++++++++++++++++++++++++++++++++ srml/rolling-window/src/mock.rs | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index a1ef56b306105..b22e070ca9e8d 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -131,6 +131,51 @@ impl Module { } } +/// Macro for implement static `base_severity` for your misconduct type +#[macro_export] +macro_rules! impl_base_severity { + // type with type parameters + ($ty:ident < $( $N:ident $(: $b0:ident $(+$b:ident)* )? ),* >, $t: ty : $seve: expr) => { + impl< $( $N $(: $b0 $(+$b)* )? ),* > $ty< $( $N ),* > { + fn base_severity() -> $t { + $seve + } + } + }; + + // type without type parameters + ($ty:ident, $t: ty : $seve: expr) => { + impl $ty { + fn base_severity() -> $t { + $seve + } + } + }; +} + +/// Macro for implement static `kind of misconduct` for your misconduct type +/// which includes the +#[macro_export] +macro_rules! impl_kind { + // type with type parameters + ($ty:ident < $( $N:ident $(: $b0:ident $(+$b:ident)* )? ),* >, $t: ty : $kind: expr) => { + + impl< $( $N $(: $b0 $(+$b)* )? ),* > $ty< $( $N ),* > { + fn kind() -> $t { + $kind + } + } + }; + + // type without type parameters + ($ty:ident, $t: ty : $kind: expr) => { + impl $ty { + fn kind() -> $t { + $kind + } + } + }; +} #[cfg(test)] mod tests { @@ -231,4 +276,24 @@ mod tests { assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); }); } + + + #[test] + fn macros() { + use rstd::marker::PhantomData; + + struct Bar; + + struct Foo((PhantomData, PhantomData)); + + impl_base_severity!(Bar, usize: 1); + impl_base_severity!(Foo, usize: 1337); + impl_kind!(Bar, Kind: Kind::One); + impl_kind!(Foo, Kind: Kind::Two); + + assert_eq!(Bar::base_severity(), 1); + assert_eq!(Foo::::base_severity(), 1337); + assert_eq!(Bar::kind(), Kind::One); + assert_eq!(Foo::::kind(), Kind::Two); + } } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index fef78ed375cd8..4900284c172fa 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -60,7 +60,7 @@ impl Get for ExistentialDeposit { } } -#[derive(Debug, Copy, Clone, Encode, Decode, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Encode, Decode, Serialize, Deserialize, PartialEq)] pub enum Kind { One, Two, From 2e5519f0b163e569340762d0875936514bd6e179 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 09:12:45 +0200 Subject: [PATCH 21/66] clean --- srml/rolling-window/src/lib.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index b22e070ca9e8d..491f70706b076 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -52,7 +52,7 @@ decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into a hash (identity of reporter), session number when it occurred + /// It maps each kind into a hash (identity of reporter) and session number when it occurred MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; /// Session index @@ -72,9 +72,9 @@ impl Module { for (kind, _) in >::enumerate() { let window_length = kind.window_length(); - >::mutate(kind, |window| { + >::mutate(kind, |reports| { // it is guaranteed that `reported_session` happened before `session` - window.retain(|(reported_session, _)| { + reports.retain(|(reported_session, _)| { let diff = session.wrapping_sub(*reported_session); diff < *window_length }); @@ -109,12 +109,11 @@ impl Module { let mut seen_ids = rstd::vec::Vec::new(); let mut unique = 0_u64; - // session can never be smaller than 0 - let mut last_session = 0; + let mut last_seen_session = 0; for (session, id) in window { // new session reset `seen_ids` - if session > last_session { + if session > last_seen_session { seen_ids.clear(); } @@ -124,14 +123,14 @@ impl Module { seen_ids.push(id); } - last_session = session; + last_seen_session = session; } unique } } -/// Macro for implement static `base_severity` for your misconduct type +/// Macro for implement static `base_severity` which may be used for misconducts implementations #[macro_export] macro_rules! impl_base_severity { // type with type parameters @@ -142,7 +141,6 @@ macro_rules! impl_base_severity { } } }; - // type without type parameters ($ty:ident, $t: ty : $seve: expr) => { impl $ty { @@ -153,8 +151,8 @@ macro_rules! impl_base_severity { }; } -/// Macro for implement static `kind of misconduct` for your misconduct type -/// which includes the +/// Macro for implement static `misconduct kind` which may be used for misconducts implementations +/// Note, that the kind need to implement the `WindowLength` trait to work #[macro_export] macro_rules! impl_kind { // type with type parameters @@ -166,7 +164,6 @@ macro_rules! impl_kind { } } }; - // type without type parameters ($ty:ident, $t: ty : $kind: expr) => { impl $ty { From b41e7d81d7b042856c4702dd9726ecf5f564380d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 10:15:47 +0200 Subject: [PATCH 22/66] clean --- srml/rolling-window/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 491f70706b076..6636cbbd442cf 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -67,6 +67,7 @@ decl_module! { impl Module { /// Remove items that doesn't fit into the rolling window + /// Must be called when a session ends pub fn on_session_end() { let session = SessionIndex::get().wrapping_add(1); @@ -84,7 +85,7 @@ impl Module { SessionIndex::put(session); } - /// Report misbehaviour for a kind + /// Report misbehavior for a kind pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { let session = SessionIndex::get(); @@ -95,15 +96,15 @@ impl Module { } } - /// Return number of misbehaviors in the current window which - /// may include duplicated misbehaviours + /// Return number of misbehavior's in the current window which + /// may include duplicated misbehaviour's pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 } - /// Return number of misbehaviors in the current window which - /// ignores duplicated misbehaviours in each session + /// Return number of misbehavior's in the current window which + /// ignores duplicated misbehavior's in each session pub fn get_misbehaved_unique(kind: T::Kind) -> u64 { let window = >::get(kind); From 66c0a80ca2617c36116747594b21ef97e538b5e4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 11:10:03 +0200 Subject: [PATCH 23/66] bump runtime version --- node/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 01d28095b6d3f..7ebf8c6b42fc5 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -72,8 +72,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 113, - impl_version: 113, + spec_version: 114, + impl_version: 114, apis: RUNTIME_API_VERSIONS, }; From 750e05132a9ceea3a5cac0f9abd917a34e0eebee Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 17:23:37 +0200 Subject: [PATCH 24/66] fix grumble: include rewarder in `DoSlash` --- srml/staking/src/lib.rs | 18 ++++++++++++------ srml/support/src/traits.rs | 8 ++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 29a4e92d26712..c312f4134b237 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1381,16 +1381,22 @@ impl SelectInitialValidators for Module { } } - /// StakingSlasher pub struct StakingSlasher(rstd::marker::PhantomData); -impl DoSlash>)> for StakingSlasher { - fn do_slash(severity: Perbill, (who, exposure): (T::AccountId, Exposure>)) { - T::Currency::slash(&who, severity * exposure.own); +impl + DoSlash<(T::AccountId, Exposure>), RewardId, Perbill> + for StakingSlasher +{ + fn do_slash( + victim: (T::AccountId, Exposure>), + _r: RewardId, + severity: Perbill + ) { + T::Currency::slash(&victim.0, severity * victim.1.own); - for nominator in exposure.others { - T::Currency::slash(&who, severity * nominator.value); + for nominator in victim.1.others { + T::Currency::slash(&nominator.who, severity * nominator.value); } } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 5ab25fc5e968b..f325607df92e5 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -651,13 +651,13 @@ impl ChangeMembers for () { } /// A generic trait for reporting slashing violations. -pub trait ReportSlash { - fn slash(identfication: Identification, footprint: Hash); +pub trait ReportSlash { + fn slash(victim: VictimId, rewarder: RewardId, footprint: Hash) -> Result<(), ()>; } /// A generic trait for enacting slashes. -pub trait DoSlash { - fn do_slash(identification: Identification, severity: Severity); +pub trait DoSlash { + fn do_slash(victim: VictimId, rewarder: RewardId, severity: Severity); } /// Trait for representing window length From 5eb7bcfe8ad25c93e507e6c4b966a6900c48a47f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 17:31:26 +0200 Subject: [PATCH 25/66] grumbles: impl `OnSessionEnding` + BondingGuard --- srml/rolling-window/Cargo.toml | 3 +- srml/rolling-window/src/lib.rs | 180 ++++++++++---------------------- srml/rolling-window/src/mock.rs | 12 +-- 3 files changed, 66 insertions(+), 129 deletions(-) diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index a6d9311fa8985..86e9c82f6b209 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -11,12 +11,12 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals serde = { version = "1.0", optional = true } sr-primitives = { path = "../../core/sr-primitives", default-features = false } srml-support = { path = "../support", default-features = false } +srml-session = { path = "../session", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } [dev-dependencies] balances = { package = "srml-balances", path = "../balances" } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } -session = { package = "srml-session", path = "../session", default-features = false } srml-staking = { package = "srml-staking", path = "../staking" } substrate-primitives = { path = "../../core/primitives" } timestamp = { package = "srml-timestamp", path = "../timestamp" } @@ -29,6 +29,7 @@ std = [ "rstd/std", "serde", "sr-primitives/std", + "srml-session/std", "srml-support/std", "system/std", ] diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 6636cbbd442cf..bfe71ba75f76f 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -34,13 +34,12 @@ mod mock; use srml_support::{ - StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_storage, + StorageMap, EnumerableStorageMap, decl_module, decl_storage, traits::WindowLength }; use parity_codec::Codec; use sr_primitives::traits::MaybeSerializeDebug; - -type Session = u32; +use srml_session::SessionIndex; /// Rolling Window trait pub trait Trait: system::Trait { @@ -52,11 +51,14 @@ decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// - /// It maps each kind into a hash (identity of reporter) and session number when it occurred - MisconductReports get(kind): linked_map T::Kind => Vec<(Session, T::Hash)>; + /// It stores every unique misbehavior of a kind + // TODO(niklasad1): optimize how to shrink the window when sessions expire + MisconductReports get(kind): linked_map T::Kind => Vec; - /// Session index - SessionIndex get(s) config(): u32 = 0; + /// Bonding guard + /// + /// Keeps track of unique reported misconducts + BondingGuard get(uniq): linked_map (T::Kind, T::Hash) => (); } } @@ -66,34 +68,24 @@ decl_module! { } impl Module { - /// Remove items that doesn't fit into the rolling window - /// Must be called when a session ends - pub fn on_session_end() { - let session = SessionIndex::get().wrapping_add(1); - - for (kind, _) in >::enumerate() { - let window_length = kind.window_length(); - >::mutate(kind, |reports| { - // it is guaranteed that `reported_session` happened before `session` - reports.retain(|(reported_session, _)| { - let diff = session.wrapping_sub(*reported_session); - diff < *window_length - }); - }); + /// Report misbehavior for a kind + /// + /// If the misbehavior is not unique `Err` is returned otherwise the number of misbehaviors for the kind + /// is returned + pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash, current_session: SessionIndex) -> Result { + if >::exists((kind, footprint)) { + return Err(()); } - SessionIndex::put(session); - } - - /// Report misbehavior for a kind - pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash) { - let session = SessionIndex::get(); + >::insert((kind, footprint), ()); if >::exists(kind) { - >::mutate(kind, |w| w.push((session, footprint))); + >::mutate(kind, |entry| entry.push(current_session)); } else { - >::insert(kind, vec![(session, footprint)]); + >::insert(kind, vec![current_session]); } + + Ok(>::get(kind).len() as u64) } /// Return number of misbehavior's in the current window which @@ -101,33 +93,24 @@ impl Module { pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 } +} - /// Return number of misbehavior's in the current window which - /// ignores duplicated misbehavior's in each session - pub fn get_misbehaved_unique(kind: T::Kind) -> u64 { - let window = >::get(kind); - - let mut seen_ids = rstd::vec::Vec::new(); - let mut unique = 0_u64; - let mut last_seen_session = 0; - - for (session, id) in window { - // new session reset `seen_ids` - if session > last_seen_session { - seen_ids.clear(); - } - - // Unfortunately O(n) - if !seen_ids.contains(&id) { - unique = unique.saturating_add(1); - seen_ids.push(id); - } - - last_seen_session = session; +impl srml_session::OnSessionEnding for Module { + fn on_session_ending(ending: SessionIndex, _applied_at: SessionIndex) -> Option> { + for (kind, _) in >::enumerate() { + let window_length = kind.window_length(); + >::mutate(kind, |reports| { + // it is guaranteed that `reported_session` happened before `end` + reports.retain(|reported_session| { + let diff = ending.wrapping_sub(*reported_session); + diff < *window_length + }); + }); } - unique + // don't provide a new validator set + None } } @@ -181,101 +164,54 @@ mod tests { use runtime_io::with_externalities; use crate::mock::*; use substrate_primitives::H256; + use srml_session::OnSessionEnding; type RollingWindow = Module; - #[test] fn it_works() { with_externalities(&mut ExtBuilder::default() .build(), || { - for i in 1..=10 { - RollingWindow::report_misbehavior(Kind::One, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::One), i); - } - - for i in 1..=4 { - RollingWindow::report_misbehavior(Kind::Two, H256::zero()); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), i); - } - - RollingWindow::on_session_end(); - // session = 1 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); - - RollingWindow::on_session_end(); - // session = 2 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 4); + let zero = H256::zero(); + let one: H256 = [1_u8; 32].into(); - RollingWindow::on_session_end(); - // session = 3 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 10); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + let mut current_session = 0; - RollingWindow::on_session_end(); - // session = 4 - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_err()); - }); - } + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).unwrap(), 2); + assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); - #[test] - fn unique() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - let one: H256 = [1_u8; 32].into(); - let two: H256 = [2_u8; 32].into(); + assert_eq!(RollingWindow::report_misbehavior(Kind::Three, one, current_session).unwrap(), 1); - for _ in 0..10 { - RollingWindow::report_misbehavior(Kind::Two, H256::zero()); - } + RollingWindow::on_session_ending(current_session, current_session + 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 1); - for _ in 0..33 { - RollingWindow::report_misbehavior(Kind::Three, H256::zero()); - } + current_session += 1; - RollingWindow::report_misbehavior(Kind::Two, one); - RollingWindow::report_misbehavior(Kind::Two, two); + RollingWindow::on_session_ending(current_session, current_session + 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 12); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 3); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 1); - RollingWindow::on_session_end(); - // session index = 1 + current_session += 1; - for _ in 0..5 { - RollingWindow::report_misbehavior(Kind::Two, H256::zero()); - } + RollingWindow::on_session_ending(current_session, current_session + 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); + assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 4); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 33); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 1); - RollingWindow::on_session_end(); - // session index = 2 - // Kind::Three should have been expired + current_session += 1; - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 17); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 4); + RollingWindow::on_session_ending(current_session, current_session + 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 0); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Three), 0); - RollingWindow::on_session_end(); - // session index = 3 - // events that happend in session 0 should have been expired - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 5); - assert_eq!(RollingWindow::get_misbehaved_unique(Kind::Two), 1); }); } - #[test] fn macros() { use rstd::marker::PhantomData; diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 4900284c172fa..635f8a6c5364e 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -32,7 +32,7 @@ pub type BlockNumber = u64; pub type Severity = u64; pub type Balance = u64; pub type Balances = balances::Module; -pub type Session = session::Module; +pub type Session = srml_session::Module; pub type Staking = srml_staking::Module; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type ExtendedBalance = u128; @@ -124,11 +124,11 @@ impl srml_staking::Trait for Test { type SessionInterface = Self; } -impl session::Trait for Test { +impl srml_session::Trait for Test { type SelectInitialValidators = Staking; type OnSessionEnding = Staking; type Keys = UintAuthorityId; - type ShouldEndSession = session::PeriodicSessions; + type ShouldEndSession = srml_session::PeriodicSessions; type SessionHandler = (); type Event = (); type ValidatorId = AccountId; @@ -145,7 +145,7 @@ impl Trait for Test { type Kind = Kind; } -impl session::historical::Trait for Test { +impl srml_session::historical::Trait for Test { type FullIdentification = srml_staking::Exposure; type FullIdentificationOf = srml_staking::ExposureOf; } @@ -155,7 +155,7 @@ parameter_types! { } parameter_types! { - pub const SessionsPerEra: session::SessionIndex = 3; + pub const SessionsPerEra: srml_session::SessionIndex = 3; pub const BondingDuration: srml_staking::EraIndex = 3; } @@ -294,7 +294,7 @@ impl ExtBuilder { invulnerables: vec![], }.assimilate_storage(&mut t, &mut c); - let _ = session::GenesisConfig:: { + let _ = srml_session::GenesisConfig:: { keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), }.assimilate_storage(&mut t, &mut c); From 6b9093266c60eb9dcfa2be2b4ba21c29d2260ba7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 17 Jul 2019 23:53:45 +0200 Subject: [PATCH 26/66] fix grumbles + a couple of questions --- srml/rolling-window/src/lib.rs | 48 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index bfe71ba75f76f..96c3356bd3cae 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -57,8 +57,19 @@ decl_storage! { /// Bonding guard /// - /// Keeps track of unique reported misconducts - BondingGuard get(uniq): linked_map (T::Kind, T::Hash) => (); + /// Keeps track of uniquely reported misconducts in during the `Bonding Duration` + /// + /// (niklasad1): + /// how to keep track of the bonding duration?! + /// + /// Do we need to store `EraIndex` and detect when `staking::BondingDuration` has been reached?! + /// If so, we need dependency to the staking module too. + /// + /// Additionally, it may `unbond` a portion or all funds and it may be invalidate potential candidates + /// to be slashed but it shouldn't have any impact on the already reported misbehaviors in the rolling window + /// + /// For now, we have a function `on_bonding_duration` which should be called when the BondingDuration expires. + BondingGuard get(uniq): linked_map T::Hash => SessionIndex; } } @@ -73,11 +84,11 @@ impl Module { /// If the misbehavior is not unique `Err` is returned otherwise the number of misbehaviors for the kind /// is returned pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash, current_session: SessionIndex) -> Result { - if >::exists((kind, footprint)) { + if >::exists(footprint) { return Err(()); } - >::insert((kind, footprint), ()); + >::insert(footprint, current_session); if >::exists(kind) { >::mutate(kind, |entry| entry.push(current_session)); @@ -95,6 +106,20 @@ impl Module { } } +/// An event handler for when `BondingDuration` has expired +pub trait OnBondingDurationEnd { + /// Take action when `BondingDuration` expired + fn on_bonding_duration_end(); +} + + +impl OnBondingDurationEnd for Module { + fn on_bonding_duration_end() { + for (id, _) in >::enumerate() { + >::remove(id); + } + } +} impl srml_session::OnSessionEnding for Module { fn on_session_ending(ending: SessionIndex, _applied_at: SessionIndex) -> Option> { @@ -176,6 +201,7 @@ mod tests { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); + let two: H256 = [2_u8; 32].into(); let mut current_session = 0; @@ -185,30 +211,24 @@ mod tests { assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).unwrap(), 2); assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Three, one, current_session).unwrap(), 1); RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 1); current_session += 1; + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).unwrap(), 3); RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); current_session += 1; RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 2); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); current_session += 1; RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Three), 0); - + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 1); }); } From 503e51271d0d25cb0fe562d1a61a1029b28620a0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 10:32:00 +0200 Subject: [PATCH 27/66] fix: better name + Result in slashing traits --- srml/staking/src/lib.rs | 16 +++++++++------- srml/support/src/traits.rs | 13 +++++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index c312f4134b237..7378a554473d3 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1384,19 +1384,21 @@ impl SelectInitialValidators for Module { /// StakingSlasher pub struct StakingSlasher(rstd::marker::PhantomData); -impl - DoSlash<(T::AccountId, Exposure>), RewardId, Perbill> +impl + DoSlash<(T::AccountId, Exposure>), Reporter, Perbill> for StakingSlasher { fn do_slash( - victim: (T::AccountId, Exposure>), - _r: RewardId, + (who, exposure): (T::AccountId, Exposure>), + _to_reward: Reporter, severity: Perbill - ) { - T::Currency::slash(&victim.0, severity * victim.1.own); + ) -> Result<(), ()> { + T::Currency::slash(&who, severity * exposure.own); - for nominator in victim.1.others { + for nominator in exposure.others { T::Currency::slash(&nominator.who, severity * nominator.value); } + + Ok(()) } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index f325607df92e5..0a680c93927f1 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -651,13 +651,18 @@ impl ChangeMembers for () { } /// A generic trait for reporting slashing violations. -pub trait ReportSlash { - fn slash(victim: VictimId, rewarder: RewardId, footprint: Hash) -> Result<(), ()>; +pub trait ReportSlash { + /// Reports slashing for a misconduct where `to_slash` is the misbehaved entities and + /// `to_reward` is the entities that detected and reported the misbehavior + /// + /// Returns `Ok(misconduct level)` if the misconduct was unique otherwise `Err` + fn slash(to_slash: Misbehaved, to_reward: Reporter, footprint: Hash) -> Result; } /// A generic trait for enacting slashes. -pub trait DoSlash { - fn do_slash(victim: VictimId, rewarder: RewardId, severity: Severity); +pub trait DoSlash { + /// Performs that actual slashing and rewarding based on severity + fn do_slash(to_slash: Misbehaved, to_reward: Reporter, severity: Severity) -> Result<(), ()>; } /// Trait for representing window length From 5b3debe36d5fba7213512ed8d9b66f587e6ddd2f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 12:15:11 +0200 Subject: [PATCH 28/66] add a couple of tests --- srml/rolling-window/src/lib.rs | 43 +++++++++++++++++++++++++++++++++ srml/rolling-window/src/mock.rs | 2 ++ 2 files changed, 45 insertions(+) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 96c3356bd3cae..f5163d2db012d 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -232,6 +232,49 @@ mod tests { }); } + #[test] + fn rolling_window_wrapped() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + // window length is u32::max_value should expire at session 24 + assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).unwrap(), 1); + + // `u32::max_value() - 25` sessions have been executed + RollingWindow::on_session_ending(u32::max_value(), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 1); + + for session in 0..24 { + RollingWindow::on_session_ending(session, session + 1); + assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 1); + } + + // `u32::max_value` sessions have been executed should removed from the window + RollingWindow::on_session_ending(24, 25); + assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 0); + }); + } + + #[test] + fn bonding_period_expire_decoupled_from_rolling_window() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + let mut current_session = 0; + + assert_eq!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).is_err()); + + RollingWindow::on_session_ending(current_session, current_session + 1); + current_session += 1; + + RollingWindow::on_bonding_duration_end(); + assert_eq!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).unwrap(), 2); + }); + } + #[test] fn macros() { use rstd::marker::PhantomData; diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 635f8a6c5364e..6e92ab9cfac6a 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -65,6 +65,7 @@ pub enum Kind { One, Two, Three, + Four, } impl WindowLength for Kind { @@ -73,6 +74,7 @@ impl WindowLength for Kind { Kind::One => &4, Kind::Two => &3, Kind::Three => &2, + Kind::Four => &u32::max_value(), } } } From 2a2841abf96f9459937a772b8368ebd6c93da00f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 14:17:15 +0200 Subject: [PATCH 29/66] fix: `BondingGuard` will be unbounded --- srml/rolling-window/src/lib.rs | 48 ++------------------------------- srml/rolling-window/src/mock.rs | 2 +- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index f5163d2db012d..0ba266b4eb993 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -57,18 +57,8 @@ decl_storage! { /// Bonding guard /// - /// Keeps track of uniquely reported misconducts in during the `Bonding Duration` - /// - /// (niklasad1): - /// how to keep track of the bonding duration?! - /// - /// Do we need to store `EraIndex` and detect when `staking::BondingDuration` has been reached?! - /// If so, we need dependency to the staking module too. - /// - /// Additionally, it may `unbond` a portion or all funds and it may be invalidate potential candidates - /// to be slashed but it shouldn't have any impact on the already reported misbehaviors in the rolling window - /// - /// For now, we have a function `on_bonding_duration` which should be called when the BondingDuration expires. + /// Keeps track of uniquely reported misconducts in during entire bonding duration + /// It will be unbounded BondingGuard get(uniq): linked_map T::Hash => SessionIndex; } } @@ -106,21 +96,6 @@ impl Module { } } -/// An event handler for when `BondingDuration` has expired -pub trait OnBondingDurationEnd { - /// Take action when `BondingDuration` expired - fn on_bonding_duration_end(); -} - - -impl OnBondingDurationEnd for Module { - fn on_bonding_duration_end() { - for (id, _) in >::enumerate() { - >::remove(id); - } - } -} - impl srml_session::OnSessionEnding for Module { fn on_session_ending(ending: SessionIndex, _applied_at: SessionIndex) -> Option> { for (kind, _) in >::enumerate() { @@ -256,25 +231,6 @@ mod tests { }); } - #[test] - fn bonding_period_expire_decoupled_from_rolling_window() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - - let mut current_session = 0; - - assert_eq!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).unwrap(), 1); - assert!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).is_err()); - - RollingWindow::on_session_ending(current_session, current_session + 1); - current_session += 1; - - RollingWindow::on_bonding_duration_end(); - assert_eq!(RollingWindow::report_misbehavior(Kind::One, H256::zero(), current_session).unwrap(), 2); - }); - } - #[test] fn macros() { use rstd::marker::PhantomData; diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 6e92ab9cfac6a..39adb4221ea3f 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -128,7 +128,7 @@ impl srml_staking::Trait for Test { impl srml_session::Trait for Test { type SelectInitialValidators = Staking; - type OnSessionEnding = Staking; + type OnSessionEnding = super::Module; type Keys = UintAuthorityId; type ShouldEndSession = srml_session::PeriodicSessions; type SessionHandler = (); From 358b14f629c5d151590c5494064f0b6f6677e9b2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 14:25:49 +0200 Subject: [PATCH 30/66] nits --- srml/rolling-window/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 0ba266b4eb993..e260691c441ff 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -89,8 +89,7 @@ impl Module { Ok(>::get(kind).len() as u64) } - /// Return number of misbehavior's in the current window which - /// may include duplicated misbehaviour's + /// Return number of misbehavior's in the current window pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 } @@ -237,7 +236,7 @@ mod tests { struct Bar; - struct Foo((PhantomData, PhantomData)); + struct Foo(PhantomData<(T, U)>); impl_base_severity!(Bar, usize: 1); impl_base_severity!(Foo, usize: 1337); From f037ca318df613d0f8a1c600aad247896eec6e7c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 17:00:28 +0200 Subject: [PATCH 31/66] feat: era_idx and era_dur for BondingUniqueness --- srml/rolling-window/Cargo.toml | 2 +- srml/rolling-window/src/lib.rs | 88 ++++++++++++++++++++++++++------- srml/rolling-window/src/mock.rs | 1 + 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index 86e9c82f6b209..2d100324e036a 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -11,13 +11,13 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals serde = { version = "1.0", optional = true } sr-primitives = { path = "../../core/sr-primitives", default-features = false } srml-support = { path = "../support", default-features = false } +srml-staking = { package = "srml-staking", path = "../staking" } srml-session = { path = "../session", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } [dev-dependencies] balances = { package = "srml-balances", path = "../balances" } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } -srml-staking = { package = "srml-staking", path = "../staking" } substrate-primitives = { path = "../../core/primitives" } timestamp = { package = "srml-timestamp", path = "../timestamp" } diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index e260691c441ff..ddf21f5c4297a 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -35,16 +35,20 @@ mod mock; use srml_support::{ StorageMap, EnumerableStorageMap, decl_module, decl_storage, - traits::WindowLength + traits::{Get, WindowLength} }; use parity_codec::Codec; use sr_primitives::traits::MaybeSerializeDebug; use srml_session::SessionIndex; +use srml_staking::EraIndex; /// Rolling Window trait pub trait Trait: system::Trait { /// Kind to report with window length type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; + + /// Number of eras that staked funds must remain bonded for. + type BondingDuration: Get; } decl_storage! { @@ -55,11 +59,15 @@ decl_storage! { // TODO(niklasad1): optimize how to shrink the window when sessions expire MisconductReports get(kind): linked_map T::Kind => Vec; + /// EraData which have mapping from `EraIndex` to a unique Hash + /// + EraData get(idx): linked_map EraIndex => Vec; + /// Bonding guard /// - /// Keeps track of uniquely reported misconducts in during entire bonding duration + /// Keeps track of uniquely reported misconducts in the entire bonding duration /// It will be unbounded - BondingGuard get(uniq): linked_map T::Hash => SessionIndex; + BondingUniqueness get(uniq): linked_map T::Hash => SessionIndex; } } @@ -73,22 +81,42 @@ impl Module { /// /// If the misbehavior is not unique `Err` is returned otherwise the number of misbehaviors for the kind /// is returned - pub fn report_misbehavior(kind: T::Kind, footprint: T::Hash, current_session: SessionIndex) -> Result { - if >::exists(footprint) { - return Err(()); - } + pub fn report_misbehavior( + kind: T::Kind, + footprint: T::Hash, + current_session: SessionIndex, + current_era: EraIndex + ) -> Result { - >::insert(footprint, current_session); + // filter out misconduct uniqueness over 1 period old + Self::refresh(current_era); - if >::exists(kind) { - >::mutate(kind, |entry| entry.push(current_session)); + if >::exists(footprint) { + return Err(()); } else { - >::insert(kind, vec![current_session]); + >::insert(footprint, current_session); } + >::mutate(current_era, |entry| entry.push(footprint)); + >::mutate(kind, |entry| entry.push(current_session)); + Ok(>::get(kind).len() as u64) } + // TODO(niklasad1): get bonding duration from Staking + fn refresh(current_era: EraIndex) { + let bonding_duration = T::BondingDuration::get(); + + for (idx, _) in >::enumerate() { + let diff = current_era.wrapping_sub(idx); + if diff >= bonding_duration { + for old in >::take(idx) { + >::remove(old); + } + } + } + } + /// Return number of misbehavior's in the current window pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 @@ -178,19 +206,20 @@ mod tests { let two: H256 = [2_u8; 32].into(); let mut current_session = 0; + let current_era = 0; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).unwrap(), 1); - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session, current_era).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session, current_era).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).unwrap(), 2); - assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session, current_era).unwrap(), 2); + assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session, current_era).is_err()); RollingWindow::on_session_ending(current_session, current_session + 1); current_session += 1; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).unwrap(), 3); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session, current_era).unwrap(), 3); RollingWindow::on_session_ending(current_session, current_session + 1); assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); @@ -206,14 +235,39 @@ mod tests { }); } + #[test] + fn bonding_uniqueness_simple() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + let zero = H256::zero(); + let one: H256 = [1_u8; 32].into(); + + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 0).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0, 0).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, 0, 1).unwrap(), 2); + + for era in 1..3 { + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, era).is_err()); + } + + // bonding period expired but not the rolling window + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 3).unwrap(), 3); + + }); + } + #[test] fn rolling_window_wrapped() { with_externalities(&mut ExtBuilder::default() .build(), || { + let era_index = 0; + // window length is u32::max_value should expire at session 24 - assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).unwrap(), 1); + assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25, era_index).unwrap(), 1); // `u32::max_value() - 25` sessions have been executed RollingWindow::on_session_ending(u32::max_value(), 0); diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 39adb4221ea3f..93cd00c64ea62 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -145,6 +145,7 @@ impl timestamp::Trait for Test { impl Trait for Test { type Kind = Kind; + type BondingDuration = BondingDuration; } impl srml_session::historical::Trait for Test { From cfd66c776e3b1cd91449d54d774e3bc5cde0b376 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 18 Jul 2019 17:19:55 +0200 Subject: [PATCH 32/66] nits --- srml/rolling-window/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index ddf21f5c4297a..038184d7cf9c8 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -47,7 +47,7 @@ pub trait Trait: system::Trait { /// Kind to report with window length type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; - /// Number of eras that staked funds must remain bonded for. + /// Number of eras that the bonding duration consist of type BondingDuration: Get; } @@ -59,14 +59,13 @@ decl_storage! { // TODO(niklasad1): optimize how to shrink the window when sessions expire MisconductReports get(kind): linked_map T::Kind => Vec; - /// EraData which have mapping from `EraIndex` to a unique Hash + /// EraData which have mapping from `EraIndex` to a list of unique hashes /// EraData get(idx): linked_map EraIndex => Vec; /// Bonding guard /// /// Keeps track of uniquely reported misconducts in the entire bonding duration - /// It will be unbounded BondingUniqueness get(uniq): linked_map T::Hash => SessionIndex; } } @@ -103,7 +102,7 @@ impl Module { Ok(>::get(kind).len() as u64) } - // TODO(niklasad1): get bonding duration from Staking + // TODO(niklasad1): optimize fn refresh(current_era: EraIndex) { let bonding_duration = T::BondingDuration::get(); From de83094fc13c64aca19a292f8c3f11340a8878a7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 09:13:57 +0200 Subject: [PATCH 33/66] fix: EraIndex > BondingDuration instead of `>=` --- srml/rolling-window/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 038184d7cf9c8..250aa8ea3ca12 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -60,10 +60,9 @@ decl_storage! { MisconductReports get(kind): linked_map T::Kind => Vec; /// EraData which have mapping from `EraIndex` to a list of unique hashes - /// EraData get(idx): linked_map EraIndex => Vec; - /// Bonding guard + /// Bonding Uniqueness /// /// Keeps track of uniquely reported misconducts in the entire bonding duration BondingUniqueness get(uniq): linked_map T::Hash => SessionIndex; @@ -87,8 +86,7 @@ impl Module { current_era: EraIndex ) -> Result { - // filter out misconduct uniqueness over 1 period old - Self::refresh(current_era); + Self::remove_old_bonding_data(current_era); if >::exists(footprint) { return Err(()); @@ -102,13 +100,15 @@ impl Module { Ok(>::get(kind).len() as u64) } + // Remove bonding data which is older than one bonding period + // // TODO(niklasad1): optimize - fn refresh(current_era: EraIndex) { + fn remove_old_bonding_data(current_era: EraIndex) { let bonding_duration = T::BondingDuration::get(); for (idx, _) in >::enumerate() { let diff = current_era.wrapping_sub(idx); - if diff >= bonding_duration { + if diff > bonding_duration { for old in >::take(idx) { >::remove(old); } @@ -116,7 +116,7 @@ impl Module { } } - /// Return number of misbehavior's in the current window + /// Return number of misbehavior's in the current window for a kind pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 } @@ -247,12 +247,12 @@ mod tests { assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0, 0).is_err()); assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, 0, 1).unwrap(), 2); - for era in 1..3 { + for era in 1..=3 { assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, era).is_err()); - } + } // bonding period expired but not the rolling window - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 3).unwrap(), 3); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 4).unwrap(), 3); }); } From adc54fbd52d55152e2854fa38c3cf47d09a745ae Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 11:26:18 +0200 Subject: [PATCH 34/66] grumble: make `BondingUniqueness` insert_only --- srml/rolling-window/src/lib.rs | 64 ++++++++++----------------------- srml/rolling-window/src/mock.rs | 1 - 2 files changed, 19 insertions(+), 46 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 250aa8ea3ca12..cf9fd811b97db 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -35,20 +35,16 @@ mod mock; use srml_support::{ StorageMap, EnumerableStorageMap, decl_module, decl_storage, - traits::{Get, WindowLength} + traits::WindowLength }; use parity_codec::Codec; use sr_primitives::traits::MaybeSerializeDebug; use srml_session::SessionIndex; -use srml_staking::EraIndex; /// Rolling Window trait pub trait Trait: system::Trait { /// Kind to report with window length type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; - - /// Number of eras that the bonding duration consist of - type BondingDuration: Get; } decl_storage! { @@ -59,12 +55,14 @@ decl_storage! { // TODO(niklasad1): optimize how to shrink the window when sessions expire MisconductReports get(kind): linked_map T::Kind => Vec; - /// EraData which have mapping from `EraIndex` to a list of unique hashes - EraData get(idx): linked_map EraIndex => Vec; /// Bonding Uniqueness /// /// Keeps track of uniquely reported misconducts in the entire bonding duration + /// which is currently unbounded (insert only) + /// + /// Footprints need to be unique or stash accounts must be banned from joining + /// the validator set after been slashed BondingUniqueness get(uniq): linked_map T::Hash => SessionIndex; } } @@ -83,39 +81,19 @@ impl Module { kind: T::Kind, footprint: T::Hash, current_session: SessionIndex, - current_era: EraIndex ) -> Result { - Self::remove_old_bonding_data(current_era); - if >::exists(footprint) { return Err(()); } else { >::insert(footprint, current_session); } - >::mutate(current_era, |entry| entry.push(footprint)); >::mutate(kind, |entry| entry.push(current_session)); Ok(>::get(kind).len() as u64) } - // Remove bonding data which is older than one bonding period - // - // TODO(niklasad1): optimize - fn remove_old_bonding_data(current_era: EraIndex) { - let bonding_duration = T::BondingDuration::get(); - - for (idx, _) in >::enumerate() { - let diff = current_era.wrapping_sub(idx); - if diff > bonding_duration { - for old in >::take(idx) { - >::remove(old); - } - } - } - } - /// Return number of misbehavior's in the current window for a kind pub fn get_misbehaved(kind: T::Kind) -> u64 { >::get(kind).len() as u64 @@ -205,20 +183,19 @@ mod tests { let two: H256 = [2_u8; 32].into(); let mut current_session = 0; - let current_era = 0; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session, current_era).unwrap(), 1); - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session, current_era).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session, current_era).unwrap(), 2); - assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session, current_era).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).unwrap(), 2); + assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); RollingWindow::on_session_ending(current_session, current_session + 1); current_session += 1; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session, current_era).unwrap(), 3); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).unwrap(), 3); RollingWindow::on_session_ending(current_session, current_session + 1); assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); @@ -235,7 +212,7 @@ mod tests { } #[test] - fn bonding_uniqueness_simple() { + fn bonding_unbounded() { with_externalities(&mut ExtBuilder::default() .build(), || { @@ -243,17 +220,16 @@ mod tests { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 0).unwrap(), 1); - assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0, 0).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, 0, 1).unwrap(), 2); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0).is_err()); + assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, 0).unwrap(), 2); - for era in 1..=3 { - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, era).is_err()); + // rolling window has expired but bonding_uniqueness shall be unbounded + RollingWindow::on_session_ending(50, 51); - } // bonding period expired but not the rolling window - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0, 4).unwrap(), 3); - + assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); + assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); }); } @@ -263,10 +239,8 @@ mod tests { .build(), || { - let era_index = 0; - // window length is u32::max_value should expire at session 24 - assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25, era_index).unwrap(), 1); + assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).unwrap(), 1); // `u32::max_value() - 25` sessions have been executed RollingWindow::on_session_ending(u32::max_value(), 0); diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 93cd00c64ea62..39adb4221ea3f 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -145,7 +145,6 @@ impl timestamp::Trait for Test { impl Trait for Test { type Kind = Kind; - type BondingDuration = BondingDuration; } impl srml_session::historical::Trait for Test { From ac0b2d2d73e48359b9a94060aaa2755eaa126df6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 11:33:17 +0200 Subject: [PATCH 35/66] style nits --- srml/rolling-window/src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index cf9fd811b97db..d1cc3e8617e9e 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -82,16 +82,13 @@ impl Module { footprint: T::Hash, current_session: SessionIndex, ) -> Result { - if >::exists(footprint) { - return Err(()); + Err(()) } else { >::insert(footprint, current_session); + >::mutate(kind, |entry| entry.push(current_session)); + Ok(>::get(kind).len() as u64) } - - >::mutate(kind, |entry| entry.push(current_session)); - - Ok(>::get(kind).len() as u64) } /// Return number of misbehavior's in the current window for a kind From c4c7132e9c8b2587870c9b033e4691b1d800d445 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 11:36:54 +0200 Subject: [PATCH 36/66] remove srml_staking dependency --- Cargo.lock | 1 - srml/rolling-window/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bbff0a5efefe..2b0ff5862b007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3882,7 +3882,6 @@ dependencies = [ "sr-std 2.0.0", "srml-balances 2.0.0", "srml-session 2.0.0", - "srml-staking 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", "srml-timestamp 2.0.0", diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index 2d100324e036a..6e57344232053 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -11,7 +11,6 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals serde = { version = "1.0", optional = true } sr-primitives = { path = "../../core/sr-primitives", default-features = false } srml-support = { path = "../support", default-features = false } -srml-staking = { package = "srml-staking", path = "../staking" } srml-session = { path = "../session", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } From 487b4f2a1f6ab392b4cfb2fc6218efc5dd25c457 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 12:04:57 +0200 Subject: [PATCH 37/66] fix broken build --- srml/rolling-window/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index 6e57344232053..427a1f1b2ad74 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -19,6 +19,7 @@ balances = { package = "srml-balances", path = "../balances" } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } substrate-primitives = { path = "../../core/primitives" } timestamp = { package = "srml-timestamp", path = "../timestamp" } +srml-staking = { path = "../staking", default-features = false } [features] default = ["std"] From 764611e4c283e0029d0f4fd5c6116953ccb38336 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 12:09:28 +0200 Subject: [PATCH 38/66] broken build --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 2b0ff5862b007..2bbff0a5efefe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3882,6 +3882,7 @@ dependencies = [ "sr-std 2.0.0", "srml-balances 2.0.0", "srml-session 2.0.0", + "srml-staking 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", "srml-timestamp 2.0.0", From 818171b19373f005f96b0b18263cb016a1817b20 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 12:20:08 +0200 Subject: [PATCH 39/66] broken build --- srml/rolling-window/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/rolling-window/Cargo.toml b/srml/rolling-window/Cargo.toml index 427a1f1b2ad74..905d2c7e7251c 100644 --- a/srml/rolling-window/Cargo.toml +++ b/srml/rolling-window/Cargo.toml @@ -19,7 +19,7 @@ balances = { package = "srml-balances", path = "../balances" } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } substrate-primitives = { path = "../../core/primitives" } timestamp = { package = "srml-timestamp", path = "../timestamp" } -srml-staking = { path = "../staking", default-features = false } +srml-staking = { path = "../staking" } [features] default = ["std"] From b9d00514543f3676d7351992cc3625b07a4f615c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 14:41:31 +0200 Subject: [PATCH 40/66] fix: slash don't return misconduct level --- srml/support/src/traits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 0a680c93927f1..97eade90c886e 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -655,8 +655,8 @@ pub trait ReportSlash { /// Reports slashing for a misconduct where `to_slash` is the misbehaved entities and /// `to_reward` is the entities that detected and reported the misbehavior /// - /// Returns `Ok(misconduct level)` if the misconduct was unique otherwise `Err` - fn slash(to_slash: Misbehaved, to_reward: Reporter, footprint: Hash) -> Result; + /// Returns `Ok` if the misconduct was unique otherwise `Err` + fn slash(to_slash: Misbehaved, to_reward: Reporter, footprint: Hash) -> Result<(), ()>; } /// A generic trait for enacting slashes. From af8dba8d18f4f34aed760bcc13cb3d87166c5a5d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 19 Jul 2019 19:00:49 +0200 Subject: [PATCH 41/66] fix grumbles --- srml/rolling-window/src/lib.rs | 22 ++++++++++++++-------- srml/support/src/traits.rs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index d1cc3e8617e9e..d8ea42e179ea7 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -52,10 +52,9 @@ decl_storage! { /// Misbehavior reports /// /// It stores every unique misbehavior of a kind - // TODO(niklasad1): optimize how to shrink the window when sessions expire + // TODO [#3149]: optimize how to shrink the window when sessions expire MisconductReports get(kind): linked_map T::Kind => Vec; - /// Bonding Uniqueness /// /// Keeps track of uniquely reported misconducts in the entire bonding duration @@ -73,11 +72,23 @@ decl_module! { } impl Module { + /// Return number of misbehavior's in the current window for a kind + pub fn get_misbehaved(kind: T::Kind) -> u64 { + >::get(kind).len() as u64 + } +} + +/// Trait for reporting misbehavior's +pub trait MisbehaviorReporter { /// Report misbehavior for a kind /// /// If the misbehavior is not unique `Err` is returned otherwise the number of misbehaviors for the kind /// is returned - pub fn report_misbehavior( + fn report_misbehavior(kind: Kind, footprint: Hash, current_session: SessionIndex) -> Result; +} + +impl MisbehaviorReporter for Module { + fn report_misbehavior( kind: T::Kind, footprint: T::Hash, current_session: SessionIndex, @@ -90,11 +101,6 @@ impl Module { Ok(>::get(kind).len() as u64) } } - - /// Return number of misbehavior's in the current window for a kind - pub fn get_misbehaved(kind: T::Kind) -> u64 { - >::get(kind).len() as u64 - } } impl srml_session::OnSessionEnding for Module { diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 97eade90c886e..fa0b7483d21d8 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -661,7 +661,7 @@ pub trait ReportSlash { /// A generic trait for enacting slashes. pub trait DoSlash { - /// Performs that actual slashing and rewarding based on severity + /// Performs the actual slashing and rewarding based on severity fn do_slash(to_slash: Misbehaved, to_reward: Reporter, severity: Severity) -> Result<(), ()>; } From f9407c9013fd66a70418aa8ce5a907ebecf333a2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 21 Jul 2019 20:07:58 +0200 Subject: [PATCH 42/66] bump runtime --- node/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 8a028005b0fe9..47013cf00954b 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -77,8 +77,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 115, - impl_version: 115, + spec_version: 116, + impl_version: 116, apis: RUNTIME_API_VERSIONS, }; From 2ad7bb215401d0d1d950437d048fb54a93623e86 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 21 Jul 2019 21:12:16 +0200 Subject: [PATCH 43/66] make tests compile again --- srml/rolling-window/src/mock.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 39adb4221ea3f..6764bf4a15243 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -97,6 +97,7 @@ impl system::Trait for Test { type Header = Header; type Event = (); type BlockHashCount = BlockHashCount; + type WeightMultiplierUpdate = (); } impl balances::Trait for Test { From 63b4374daf8183f07f354d3dffad79fbbd615ef1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 09:48:43 +0200 Subject: [PATCH 44/66] clarify comment --- srml/rolling-window/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index d8ea42e179ea7..9a9b04dbf45e5 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -108,7 +108,7 @@ impl srml_session::OnSessionEnding for Module { for (kind, _) in >::enumerate() { let window_length = kind.window_length(); >::mutate(kind, |reports| { - // it is guaranteed that `reported_session` happened before `end` + // it is guaranteed that `reported_session` happened in the same session or before `ending` reports.retain(|reported_session| { let diff = ending.wrapping_sub(*reported_session); diff < *window_length From 154a97cc988e6b0208de104efbea5fedebe45e9e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 11:34:29 +0200 Subject: [PATCH 45/66] grumbles: nits in the docs and naming --- srml/support/src/traits.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 09fb3df9dd2c6..3eeae7f1103b6 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -650,18 +650,18 @@ impl ChangeMembers for () { } /// A generic trait for reporting slashing violations. -pub trait ReportSlash { - /// Reports slashing for a misconduct where `to_slash` is the misbehaved entities and - /// `to_reward` is the entities that detected and reported the misbehavior +pub trait ReportSlash { + /// Reports slashing for a misconduct where `to_slash` are the misbehaved entities and + /// `to_reward` are the entities that detected and reported the misbehavior /// /// Returns `Ok` if the misconduct was unique otherwise `Err` - fn slash(to_slash: Misbehaved, to_reward: Reporter, footprint: Hash) -> Result<(), ()>; + fn slash(to_slash: Misbehaved, to_reward: Reporters, footprint: Hash) -> Result<(), ()>; } /// A generic trait for enacting slashes. -pub trait DoSlash { +pub trait DoSlash { /// Performs the actual slashing and rewarding based on severity - fn do_slash(to_slash: Misbehaved, to_reward: Reporter, severity: Severity) -> Result<(), ()>; + fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity) -> Result<(), ()>; } /// Trait for representing window length From 9d7df9738ce86d094656a11d079fae8f3803bb35 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 13:57:48 +0200 Subject: [PATCH 46/66] Update srml/rolling-window/src/lib.rs Co-Authored-By: Marcio Diaz --- srml/rolling-window/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 9a9b04dbf45e5..ba39e194f6d05 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -22,7 +22,7 @@ //! that it just reports the number of occurrences in the window instead of //! calculating the average. //! -//! It is mainly implemented to keep track of misbehaviors and only the take +//! It is mainly implemented to keep track of misbehaviors and only to take //! the last `sessions` of misbehaviors into account. //! From 7f3b8796330578d4f1f30d7732ae9d8b21f15aa6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 14:31:42 +0200 Subject: [PATCH 47/66] address grumbles 1. rename MisconductReports -> MisbehaviorReports 2. don't return number of misconducts when in `report_misconduct` 3. traitfy `get_misbehaved()` 4. clarify bad test 5. fix bad type documentation 6. give a better example of how `reports` will be used in `srml-staking` --- srml/rolling-window/src/lib.rs | 68 +++++++++++++++++++--------------- srml/staking/src/lib.rs | 12 ++++-- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index ba39e194f6d05..d40d8ae7daf72 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -53,7 +53,7 @@ decl_storage! { /// /// It stores every unique misbehavior of a kind // TODO [#3149]: optimize how to shrink the window when sessions expire - MisconductReports get(kind): linked_map T::Kind => Vec; + MisbehaviorReports get(misbehavior_reports): linked_map T::Kind => Vec; /// Bonding Uniqueness /// @@ -62,7 +62,7 @@ decl_storage! { /// /// Footprints need to be unique or stash accounts must be banned from joining /// the validator set after been slashed - BondingUniqueness get(uniq): linked_map T::Hash => SessionIndex; + BondingUniqueness get(bonding_uniqueness): linked_map T::Hash => SessionIndex; } } @@ -71,20 +71,22 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin {} } -impl Module { - /// Return number of misbehavior's in the current window for a kind - pub fn get_misbehaved(kind: T::Kind) -> u64 { - >::get(kind).len() as u64 +/// Trait for getting the number of misbehaviors in the current window +pub trait GetMisbehaviors { + /// Get number of misbehavior's in the current window for a kind + fn get_misbehaviors(kind: Kind) -> u64; +} + +impl GetMisbehaviors for Module { + fn get_misbehaviors(kind: T::Kind) -> u64 { + >::get(kind).len() as u64 } } /// Trait for reporting misbehavior's pub trait MisbehaviorReporter { /// Report misbehavior for a kind - /// - /// If the misbehavior is not unique `Err` is returned otherwise the number of misbehaviors for the kind - /// is returned - fn report_misbehavior(kind: Kind, footprint: Hash, current_session: SessionIndex) -> Result; + fn report_misbehavior(kind: Kind, footprint: Hash, current_session: SessionIndex) -> Result<(), ()>; } impl MisbehaviorReporter for Module { @@ -92,22 +94,22 @@ impl MisbehaviorReporter for Module { kind: T::Kind, footprint: T::Hash, current_session: SessionIndex, - ) -> Result { + ) -> Result<(), ()> { if >::exists(footprint) { Err(()) } else { >::insert(footprint, current_session); - >::mutate(kind, |entry| entry.push(current_session)); - Ok(>::get(kind).len() as u64) + >::mutate(kind, |entry| entry.push(current_session)); + Ok(()) } } } impl srml_session::OnSessionEnding for Module { fn on_session_ending(ending: SessionIndex, _applied_at: SessionIndex) -> Option> { - for (kind, _) in >::enumerate() { + for (kind, _) in >::enumerate() { let window_length = kind.window_length(); - >::mutate(kind, |reports| { + >::mutate(kind, |reports| { // it is guaranteed that `reported_session` happened in the same session or before `ending` reports.retain(|reported_session| { let diff = ending.wrapping_sub(*reported_session); @@ -187,10 +189,12 @@ mod tests { let mut current_session = 0; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).unwrap(), 2); + assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); @@ -198,19 +202,21 @@ mod tests { current_session += 1; - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).unwrap(), 3); + assert!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); + RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); current_session += 1; RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 3); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); current_session += 1; RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 1); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); }); } @@ -223,16 +229,17 @@ mod tests { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, zero, 0).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 0).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0).is_err()); - assert_eq!(RollingWindow::report_misbehavior(Kind::Two, one, 0).unwrap(), 2); + assert!(RollingWindow::report_misbehavior(Kind::Two, one, 0).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); // rolling window has expired but bonding_uniqueness shall be unbounded RollingWindow::on_session_ending(50, 51); - // bonding period expired but not the rolling window - assert_eq!(RollingWindow::get_misbehaved(Kind::One), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Two), 0); + assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 51).is_err()); + assert!(RollingWindow::report_misbehavior(Kind::One, one, 52).is_err()); }); } @@ -243,20 +250,21 @@ mod tests { || { // window length is u32::max_value should expire at session 24 - assert_eq!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).unwrap(), 1); + assert!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).is_ok()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); // `u32::max_value() - 25` sessions have been executed RollingWindow::on_session_ending(u32::max_value(), 0); - assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 1); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); for session in 0..24 { RollingWindow::on_session_ending(session, session + 1); - assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 1); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); } // `u32::max_value` sessions have been executed should removed from the window RollingWindow::on_session_ending(24, 25); - assert_eq!(RollingWindow::get_misbehaved(Kind::Four), 0); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 0); }); } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index f40cd7bb85724..59ec03939ae8a 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1381,16 +1381,20 @@ impl SelectInitialValidators for Module { } } -/// StakingSlasher +/// A type for performing slashing based on severity of misbehavior pub struct StakingSlasher(rstd::marker::PhantomData); -impl - DoSlash<(T::AccountId, Exposure>), Reporter, Perbill> +impl DoSlash<(T::AccountId, Exposure>), Reporters, Perbill> for StakingSlasher +where + Reporters: IntoIterator, { + // TODO: #XXXX pay out reward to the reporters + // The reporters shall be sorted in the same order as they reported misbehavior. + // The first reporter shall get the biggest reward and the last reporter the smallest reward. fn do_slash( (who, exposure): (T::AccountId, Exposure>), - _to_reward: Reporter, + _reporters: Reporters, severity: Perbill ) -> Result<(), ()> { T::Currency::slash(&who, severity * exposure.own); From 2f1f6d01955cf9dd4e033a44ca63884f098ed60a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 16:26:39 +0200 Subject: [PATCH 48/66] add issue number for StakingSlasher::do_slash --- srml/staking/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 59ec03939ae8a..4506cf48f5d5e 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1387,11 +1387,10 @@ pub struct StakingSlasher(rstd::marker::PhantomData); impl DoSlash<(T::AccountId, Exposure>), Reporters, Perbill> for StakingSlasher where - Reporters: IntoIterator, + Reporters: IntoIterator, { - // TODO: #XXXX pay out reward to the reporters - // The reporters shall be sorted in the same order as they reported misbehavior. - // The first reporter shall get the biggest reward and the last reporter the smallest reward. + // TODO: #3166 pay out reward to the reporters + // Perbill is priority for the reporter fn do_slash( (who, exposure): (T::AccountId, Exposure>), _reporters: Reporters, From 10a1359754803e2bb418bce1199c97ec29f58a79 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 22 Jul 2019 22:32:16 +0200 Subject: [PATCH 49/66] fix bad merge --- srml/rolling-window/src/mock.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 6764bf4a15243..c478873a6f8a9 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -98,6 +98,8 @@ impl system::Trait for Test { type Event = (); type BlockHashCount = BlockHashCount; type WeightMultiplierUpdate = (); + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; } impl balances::Trait for Test { @@ -176,6 +178,8 @@ parameter_types! { parameter_types! { pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; } pub struct ExtBuilder { From 4072658450f0e6258f06b6860e19e11619d5e7b4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 23 Jul 2019 22:59:35 +0200 Subject: [PATCH 50/66] simplify mock --- srml/rolling-window/src/mock.rs | 166 ++++---------------------------- 1 file changed, 17 insertions(+), 149 deletions(-) diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index c478873a6f8a9..b76e663b48573 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -14,32 +14,23 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -#![allow(unused)] - use super::*; use serde::{Serialize, Deserialize}; -use sr_primitives::{Perbill, traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; +use sr_primitives::{traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; use substrate_primitives::{Blake2Hasher, H256}; -use srml_staking::{GenesisConfig, StakerStatus}; -use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get, WindowLength}}; -use std::{marker::PhantomData, cell::RefCell}; -use std::collections::{HashSet, HashMap}; -use parity_codec::{Encode, Decode, Codec}; +use srml_support::{impl_outer_origin, parameter_types, traits::{Get, WindowLength}}; +use std::{collections::HashSet, cell::RefCell}; +use parity_codec::{Encode, Decode}; pub type AccountId = u64; -pub type Exposure = u64; -pub type BlockNumber = u64; -pub type Severity = u64; pub type Balance = u64; -pub type Balances = balances::Module; -pub type Session = srml_session::Module; +pub type BlockNumber = u64; pub type Staking = srml_staking::Module; -pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -pub type ExtendedBalance = u128; pub struct CurrencyToVoteHandler; thread_local! { + static NEXT_VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); } @@ -89,7 +80,7 @@ impl_outer_origin!{ impl system::Trait for Test { type Origin = Origin; type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; @@ -140,11 +131,6 @@ impl srml_session::Trait for Test { type ValidatorIdOf = srml_staking::StashOf; } -impl timestamp::Trait for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; -} impl Trait for Test { type Kind = Kind; @@ -182,136 +168,18 @@ parameter_types! { pub const MaximumBlockLength: u32 = 2 * 1024; } -pub struct ExtBuilder { - existential_deposit: u64, - reward: u64, - validator_pool: bool, - nominate: bool, - validator_count: u32, - minimum_validator_count: u32, - fair: bool, - num_validators: Option, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { - existential_deposit: 0, - reward: 10, - validator_pool: false, - nominate: true, - validator_count: 2, - minimum_validator_count: 0, - fair: true, - num_validators: None, - } - } -} +#[derive(Default)] +pub struct ExtBuilder; impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn validator_pool(mut self, validator_pool: bool) -> Self { - self.validator_pool = validator_pool; - self - } - pub fn nominate(mut self, nominate: bool) -> Self { - self.nominate = nominate; - self - } - pub fn validator_count(mut self, count: u32) -> Self { - self.validator_count = count; - self - } - pub fn minimum_validator_count(mut self, count: u32) -> Self { - self.minimum_validator_count = count; - self - } - pub fn fair(mut self, is_fair: bool) -> Self { - self.fair = is_fair; - self - } - pub fn num_validators(mut self, num_validators: u32) -> Self { - self.num_validators = Some(num_validators); - self - } - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - } pub fn build(self) -> runtime_io::TestExternalities { - self.set_associated_consts(); - let (mut t, mut c) = system::GenesisConfig::default().build_storage::().unwrap(); - let balance_factor = if self.existential_deposit > 0 { - 256 - } else { - 1 - }; - - let num_validators = self.num_validators.unwrap_or(self.validator_count); - let validators = (0..num_validators) - .map(|x| ((x + 1) * 10) as u64) - .collect::>(); - - let _ = balances::GenesisConfig::{ - balances: vec![ - (1, 10 * balance_factor), - (2, 20 * balance_factor), - (3, 300 * balance_factor), - (4, 400 * balance_factor), - (10, balance_factor), - (11, balance_factor * 1000), - (20, balance_factor), - (21, balance_factor * 2000), - (30, balance_factor), - (31, balance_factor * 2000), - (40, balance_factor), - (41, balance_factor * 2000), - (100, 2000 * balance_factor), - (101, 2000 * balance_factor), - ], - vesting: vec![], - }.assimilate_storage(&mut t, &mut c); - - let stake_21 = if self.fair { 1000 } else { 2000 }; - let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; - let status_41 = if self.validator_pool { - StakerStatus::::Validator - } else { - StakerStatus::::Idle - }; - let nominated = if self.nominate { vec![11, 21] } else { vec![] }; - let _ = GenesisConfig::{ - current_era: 0, - stakers: vec![ - (11, 10, balance_factor * 1000, StakerStatus::::Validator), - (21, 20, stake_21, StakerStatus::::Validator), - (31, 30, stake_31, StakerStatus::::Validator), - (41, 40, balance_factor * 1000, status_41), - // nominator - (101, 100, balance_factor * 500, StakerStatus::::Nominator(nominated)) - ], - validator_count: self.validator_count, - minimum_validator_count: self.minimum_validator_count, - session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32), - offline_slash: Perbill::from_percent(5), - current_session_reward: self.reward, - offline_slash_grace: 0, - invulnerables: vec![], - }.assimilate_storage(&mut t, &mut c); - - let _ = srml_session::GenesisConfig:: { - keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), - }.assimilate_storage(&mut t, &mut c); - - let mut ext = t.into(); - runtime_io::with_externalities(&mut ext, || { - let validators = Session::validators(); - SESSION.with(|x| - *x.borrow_mut() = (validators.clone(), HashSet::new()) - ); - }); - ext + let mut t = system::GenesisConfig::default().build_storage::().unwrap(); + + srml_session::GenesisConfig:: { + keys: NEXT_VALIDATORS.with(|l| + l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect() + ), + }.assimilate_storage(&mut t.0, &mut t.1).unwrap(); + runtime_io::TestExternalities::new_with_children(t) } } From d514f45409926cd256334aa5061e49699e9afb1e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 24 Jul 2019 09:12:00 +0200 Subject: [PATCH 51/66] mock: remove needless ExtBuilder --- srml/rolling-window/src/lib.rs | 15 +++------------ srml/rolling-window/src/mock.rs | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index d40d8ae7daf72..3d69d8f02985b 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -179,10 +179,7 @@ mod tests { #[test] fn it_works() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - + with_externalities(&mut new_test_ext(), || { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); let two: H256 = [2_u8; 32].into(); @@ -222,10 +219,7 @@ mod tests { #[test] fn bonding_unbounded() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - + with_externalities(&mut new_test_ext(), || { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); @@ -245,10 +239,7 @@ mod tests { #[test] fn rolling_window_wrapped() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - + with_externalities(&mut new_test_ext(), || { // window length is u32::max_value should expire at session 24 assert!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).is_ok()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index b76e663b48573..82ea5f347204e 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -168,18 +168,13 @@ parameter_types! { pub const MaximumBlockLength: u32 = 2 * 1024; } -#[derive(Default)] -pub struct ExtBuilder; - -impl ExtBuilder { - pub fn build(self) -> runtime_io::TestExternalities { - let mut t = system::GenesisConfig::default().build_storage::().unwrap(); - - srml_session::GenesisConfig:: { - keys: NEXT_VALIDATORS.with(|l| - l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect() - ), - }.assimilate_storage(&mut t.0, &mut t.1).unwrap(); - runtime_io::TestExternalities::new_with_children(t) - } +pub fn new_test_ext() -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::default().build_storage::().unwrap(); + + srml_session::GenesisConfig:: { + keys: NEXT_VALIDATORS.with(|l| + l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect() + ), + }.assimilate_storage(&mut t.0, &mut t.1).unwrap(); + runtime_io::TestExternalities::new_with_children(t) } From e40e8298ade4fb67652381e5624b5df2480fd16a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 24 Jul 2019 14:52:09 +0200 Subject: [PATCH 52/66] grumbles: use `assert_ok` and `assert_noop` --- srml/rolling-window/src/lib.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 3d69d8f02985b..3fe078e8a7be6 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -174,6 +174,7 @@ mod tests { use crate::mock::*; use substrate_primitives::H256; use srml_session::OnSessionEnding; + use srml_support::{assert_ok, assert_noop}; type RollingWindow = Module; @@ -186,20 +187,20 @@ mod tests { let mut current_session = 0; - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_ok()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session).is_err()); + assert_noop!(RollingWindow::report_misbehavior(Kind::Two, zero, current_session), ()); - assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_ok()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Two, one, current_session)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); - assert!(RollingWindow::report_misbehavior(Kind::Two, one, current_session).is_err()); + assert_noop!(RollingWindow::report_misbehavior(Kind::Two, one, current_session), ()); RollingWindow::on_session_ending(current_session, current_session + 1); current_session += 1; - assert!(RollingWindow::report_misbehavior(Kind::Two, two, current_session).is_ok()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Two, two, current_session)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); RollingWindow::on_session_ending(current_session, current_session + 1); @@ -223,17 +224,17 @@ mod tests { let zero = H256::zero(); let one: H256 = [1_u8; 32].into(); - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 0).is_ok()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Two, zero, 0)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); - assert!(RollingWindow::report_misbehavior(Kind::One, zero, 0).is_err()); - assert!(RollingWindow::report_misbehavior(Kind::Two, one, 0).is_ok()); + assert_noop!(RollingWindow::report_misbehavior(Kind::One, zero, 0), ()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Two, one, 0)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); // rolling window has expired but bonding_uniqueness shall be unbounded RollingWindow::on_session_ending(50, 51); - assert!(RollingWindow::report_misbehavior(Kind::Two, zero, 51).is_err()); - assert!(RollingWindow::report_misbehavior(Kind::One, one, 52).is_err()); + assert_noop!(RollingWindow::report_misbehavior(Kind::Two, zero, 51), ()); + assert_noop!(RollingWindow::report_misbehavior(Kind::One, one, 52), ()); }); } @@ -241,7 +242,7 @@ mod tests { fn rolling_window_wrapped() { with_externalities(&mut new_test_ext(), || { // window length is u32::max_value should expire at session 24 - assert!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25).is_ok()); + assert_ok!(RollingWindow::report_misbehavior(Kind::Four, H256::zero(), 25)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); // `u32::max_value() - 25` sessions have been executed From 5016b6aa511fcc76086a4271d1209aaa5873f718 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 25 Jul 2019 11:32:33 +0200 Subject: [PATCH 53/66] replace `OnSessionEnding` with `OneSessionHandler` --- Cargo.lock | 1 + node/runtime/Cargo.toml | 2 ++ node/runtime/src/lib.rs | 8 ++++- srml/aura/src/lib.rs | 3 +- srml/grandpa/src/lib.rs | 3 +- srml/im-online/src/lib.rs | 2 +- srml/rolling-window/src/lib.rs | 56 +++++++++++++++++++-------------- srml/rolling-window/src/mock.rs | 3 +- srml/session/src/lib.rs | 27 +++++++++++++--- srml/session/src/mock.rs | 1 + 10 files changed, 73 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e9bd9d9f4679..0c477041ee648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2364,6 +2364,7 @@ dependencies = [ "srml-grandpa 2.0.0", "srml-im-online 0.1.0", "srml-indices 2.0.0", + "srml-rolling-window 2.0.0", "srml-session 2.0.0", "srml-staking 2.0.0", "srml-sudo 2.0.0", diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index 17c666dbdc312..f511c7a7b5855 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -27,6 +27,7 @@ executive = { package = "srml-executive", path = "../../srml/executive", default finality-tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false } grandpa = { package = "srml-grandpa", path = "../../srml/grandpa", default-features = false } indices = { package = "srml-indices", path = "../../srml/indices", default-features = false } +rolling-window = { package = "srml-rolling-window", path = "../../srml/rolling-window", default-features = false } session = { package = "srml-session", path = "../../srml/session", default-features = false, features = ["historical"] } staking = { package = "srml-staking", path = "../../srml/staking", default-features = false } system = { package = "srml-system", path = "../../srml/system", default-features = false } @@ -78,6 +79,7 @@ std = [ "client/std", "consensus_aura/std", "rustc-hex", + "rolling-window/std", "substrate-keyring", "offchain-primitives/std", "im-online/std", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index aa7ba04af3335..9a61360090be3 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -201,7 +201,7 @@ parameter_types! { pub const Offset: BlockNumber = 0; } -type SessionHandlers = (Grandpa, Aura, ImOnline); +type SessionHandlers = (Grandpa, Aura, ImOnline, RollingWindow); impl_opaque_keys! { pub struct SessionKeys { @@ -388,6 +388,11 @@ impl im_online::Trait for Runtime { type IsValidAuthorityId = Aura; } +impl rolling_window::Trait for Runtime { + type Kind = Misbehavior; + type SessionKey = substrate_primitives::sr25519::Public; +} + impl grandpa::Trait for Runtime { type Event = Event; } @@ -427,6 +432,7 @@ construct_runtime!( Contracts: contracts, Sudo: sudo, ImOnline: im_online::{default, ValidateUnsigned}, + RollingWindow: rolling_window::{Module, Storage}, } ); diff --git a/srml/aura/src/lib.rs b/srml/aura/src/lib.rs index a0f7ad6242429..fd468f6aa1faf 100644 --- a/srml/aura/src/lib.rs +++ b/srml/aura/src/lib.rs @@ -66,6 +66,7 @@ use inherents::{InherentDataProviders, ProvideInherentData}; use substrate_consensus_aura_primitives::{AURA_ENGINE_ID, ConsensusLog}; #[cfg(feature = "std")] use parity_codec::Decode; +use session::SessionIndex; mod mock; mod tests; @@ -188,7 +189,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = T::AuthorityId; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) + fn on_new_session<'a, I: 'a>(_: (SessionIndex, SessionIndex), changed: bool, validators: I, _queued_validators: I) where I: Iterator { // instant changes diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 9615953e67683..62cc5b026d90a 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -41,6 +41,7 @@ use primitives::{ use fg_primitives::{ScheduledChange, ConsensusLog, GRANDPA_ENGINE_ID}; pub use fg_primitives::{AuthorityId, AuthorityWeight}; use system::{ensure_signed, DigestOf}; +use session::SessionIndex; mod mock; mod tests; @@ -346,7 +347,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = AuthorityId; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) + fn on_new_session<'a, I: 'a>(_: (SessionIndex, SessionIndex), changed: bool, validators: I, _queued_validators: I) where I: Iterator { // instant changes diff --git a/srml/im-online/src/lib.rs b/srml/im-online/src/lib.rs index f1868c599b719..f8514fbf538c4 100644 --- a/srml/im-online/src/lib.rs +++ b/srml/im-online/src/lib.rs @@ -356,7 +356,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = ::AuthorityId; - fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _next_validators: I) { + fn on_new_session<'a, I: 'a>(_: (SessionIndex, SessionIndex), _changed: bool, _validators: I, _next_validators: I) { Self::new_session(); } diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 3fe078e8a7be6..94f947a8a48f7 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -34,17 +34,21 @@ mod mock; use srml_support::{ - StorageMap, EnumerableStorageMap, decl_module, decl_storage, + StorageMap, Parameter, EnumerableStorageMap, decl_module, decl_storage, traits::WindowLength }; -use parity_codec::Codec; -use sr_primitives::traits::MaybeSerializeDebug; -use srml_session::SessionIndex; +use parity_codec::{Codec, Decode, Encode}; +use rstd::vec::Vec; +use sr_primitives::traits::{Member, MaybeSerializeDebug, TypedKey}; +use srml_session::{SessionIndex, OneSessionHandler}; /// Rolling Window trait pub trait Trait: system::Trait { /// Kind to report with window length type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; + + /// Identifier type to implement `OneSessionHandler` + type SessionKey: Member + Parameter + Default + TypedKey + Decode + Encode + AsRef<[u8]>; } decl_storage! { @@ -105,22 +109,30 @@ impl MisbehaviorReporter for Module { } } -impl srml_session::OnSessionEnding for Module { - fn on_session_ending(ending: SessionIndex, _applied_at: SessionIndex) -> Option> { +impl OneSessionHandler for Module +{ + type Key = T::SessionKey; + + fn on_new_session<'a, I: 'a>( + (ended_session, _new_session): (SessionIndex, SessionIndex), + _changed: bool, + _validators: I, + _queued_validators: I, + ) { for (kind, _) in >::enumerate() { let window_length = kind.window_length(); >::mutate(kind, |reports| { // it is guaranteed that `reported_session` happened in the same session or before `ending` reports.retain(|reported_session| { - let diff = ending.wrapping_sub(*reported_session); + let diff = ended_session.wrapping_sub(*reported_session); diff < *window_length }); }); } - - // don't provide a new validator set - None } + + // ignored + fn on_disabled(_: usize) {} } /// Macro for implement static `base_severity` which may be used for misconducts implementations @@ -170,10 +182,10 @@ macro_rules! impl_kind { #[cfg(test)] mod tests { use super::*; + use std::iter::empty; use runtime_io::with_externalities; use crate::mock::*; use substrate_primitives::H256; - use srml_session::OnSessionEnding; use srml_support::{assert_ok, assert_noop}; type RollingWindow = Module; @@ -195,25 +207,23 @@ mod tests { assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); assert_noop!(RollingWindow::report_misbehavior(Kind::Two, one, current_session), ()); - - RollingWindow::on_session_ending(current_session, current_session + 1); - current_session += 1; + RollingWindow::on_new_session((current_session - 1, current_session), false, empty(), empty()); assert_ok!(RollingWindow::report_misbehavior(Kind::Two, two, current_session)); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); - RollingWindow::on_session_ending(current_session, current_session + 1); - assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); - current_session += 1; - RollingWindow::on_session_ending(current_session, current_session + 1); + RollingWindow::on_new_session((current_session - 1, current_session), false, empty(), empty()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); current_session += 1; + RollingWindow::on_new_session((current_session - 1, current_session), false, empty(), empty()); + assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 3); - RollingWindow::on_session_ending(current_session, current_session + 1); + current_session += 1; + RollingWindow::on_new_session((current_session - 1, current_session), false, empty(), empty()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 1); }); } @@ -231,7 +241,7 @@ mod tests { assert_eq!(RollingWindow::get_misbehaviors(Kind::Two), 2); // rolling window has expired but bonding_uniqueness shall be unbounded - RollingWindow::on_session_ending(50, 51); + RollingWindow::on_new_session((50, 51), false, empty(), empty()); assert_noop!(RollingWindow::report_misbehavior(Kind::Two, zero, 51), ()); assert_noop!(RollingWindow::report_misbehavior(Kind::One, one, 52), ()); @@ -246,16 +256,16 @@ mod tests { assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); // `u32::max_value() - 25` sessions have been executed - RollingWindow::on_session_ending(u32::max_value(), 0); + RollingWindow::on_new_session((u32::max_value(), 0), false, empty(), empty()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); for session in 0..24 { - RollingWindow::on_session_ending(session, session + 1); + RollingWindow::on_new_session((session, session + 1), false, empty(), empty()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 1); } // `u32::max_value` sessions have been executed should removed from the window - RollingWindow::on_session_ending(24, 25); + RollingWindow::on_new_session((24, 25), false, empty(), empty()); assert_eq!(RollingWindow::get_misbehaviors(Kind::Four), 0); }); } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 82ea5f347204e..ecb9812c304c6 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -122,7 +122,7 @@ impl srml_staking::Trait for Test { impl srml_session::Trait for Test { type SelectInitialValidators = Staking; - type OnSessionEnding = super::Module; + type OnSessionEnding = Staking; type Keys = UintAuthorityId; type ShouldEndSession = srml_session::PeriodicSessions; type SessionHandler = (); @@ -134,6 +134,7 @@ impl srml_session::Trait for Test { impl Trait for Test { type Kind = Kind; + type SessionKey = UintAuthorityId; } impl srml_session::historical::Trait for Test { diff --git a/srml/session/src/lib.rs b/srml/session/src/lib.rs index 5495cdbe6535e..8794f4508f21f 100644 --- a/srml/session/src/lib.rs +++ b/srml/session/src/lib.rs @@ -187,6 +187,7 @@ impl OnSessionEnding for () { pub trait SessionHandler { /// Session set has changed; act appropriately. fn on_new_session( + session_index: (SessionIndex, SessionIndex), changed: bool, validators: &[(ValidatorId, Ks)], queued_validators: &[(ValidatorId, Ks)], @@ -201,15 +202,25 @@ pub trait OneSessionHandler { /// The key type expected. type Key: Decode + Default + TypedKey; - fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I) - where I: Iterator, ValidatorId: 'a; + fn on_new_session<'a, I: 'a>( + session_index: (SessionIndex, SessionIndex), + changed: bool, + validators: I, + queued_validators: I + ) where I: Iterator, ValidatorId: 'a; + fn on_disabled(i: usize); } macro_rules! impl_session_handlers { () => ( impl SessionHandler for () { - fn on_new_session(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {} + fn on_new_session( + _: (SessionIndex, SessionIndex), + _: bool, + _: &[(AId, Ks)], + _: &[(AId, Ks)], + ) {} fn on_disabled(_: usize) {} } ); @@ -217,6 +228,7 @@ macro_rules! impl_session_handlers { ( $($t:ident)* ) => { impl ),*> SessionHandler for ( $( $t , )* ) { fn on_new_session( + session_index: (SessionIndex, SessionIndex), changed: bool, validators: &[(AId, Ks)], queued_validators: &[(AId, Ks)], @@ -228,7 +240,7 @@ macro_rules! impl_session_handlers { let queued_keys: Box> = Box::new(queued_validators.iter() .map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE) .unwrap_or_default()))); - $t::on_new_session(changed, our_keys, queued_keys); + $t::on_new_session(session_index, changed, our_keys, queued_keys); )* } fn on_disabled(i: usize) { @@ -438,7 +450,12 @@ impl Module { Self::deposit_event(Event::NewSession(session_index)); // Tell everyone about the new session keys. - T::SessionHandler::on_new_session::(changed, &session_keys, &queued_amalgamated); + T::SessionHandler::on_new_session::( + (session_index - 1, session_index), + changed, + &session_keys, + &queued_amalgamated, + ); } /// Disable the validator of index `i`. diff --git a/srml/session/src/mock.rs b/srml/session/src/mock.rs index b5cb7e4278fc7..2c6f65436a311 100644 --- a/srml/session/src/mock.rs +++ b/srml/session/src/mock.rs @@ -51,6 +51,7 @@ impl ShouldEndSession for TestShouldEndSession { pub struct TestSessionHandler; impl SessionHandler for TestSessionHandler { fn on_new_session( + (_ended_index, _new_index): (SessionIndex, SessionIndex), changed: bool, validators: &[(u64, T)], _queued_validators: &[(u64, T)], From 9f326fbef6a1ec2e5b1c352a26e63714b1182bad Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 25 Jul 2019 12:03:54 +0200 Subject: [PATCH 54/66] Kind -> MisbehaviorKind --- node/runtime/src/lib.rs | 4 ++-- srml/babe/src/lib.rs | 11 +++++++++-- srml/rolling-window/src/lib.rs | 22 +++++++++++----------- srml/rolling-window/src/mock.rs | 8 +++++++- srml/staking/src/lib.rs | 1 - 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 69911c62e185c..1c7ff4d0a7cbd 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -205,7 +205,7 @@ parameter_types! { pub const Offset: BlockNumber = 0; } -type SessionHandlers = (Grandpa, Aura, ImOnline, RollingWindow); +type SessionHandlers = (Grandpa, ImOnline, RollingWindow); impl_opaque_keys! { pub struct SessionKeys { @@ -396,7 +396,7 @@ impl im_online::Trait for Runtime { } impl rolling_window::Trait for Runtime { - type Kind = Misbehavior; + type MisbehaviorKind = Misbehavior; type SessionKey = substrate_primitives::sr25519::Public; } diff --git a/srml/babe/src/lib.rs b/srml/babe/src/lib.rs index c75869ab579d6..63e9e1810b4c9 100644 --- a/srml/babe/src/lib.rs +++ b/srml/babe/src/lib.rs @@ -34,6 +34,7 @@ use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent use inherents::{InherentDataProviders, ProvideInherentData}; use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, BabeWeight, Epoch, RawBabePreDigest}; pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH}; +use session::SessionIndex; /// The BABE inherent identifier. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot"; @@ -261,8 +262,14 @@ impl OnTimestampSet for Module { impl session::OneSessionHandler for Module { type Key = AuthorityId; - fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) - where I: Iterator + + fn on_new_session<'a, I: 'a>( + _: (SessionIndex, SessionIndex), + _changed: bool, + validators: I, + queued_validators: I, + ) where + I: Iterator { use staking::BalanceOf; let to_votes = |b: BalanceOf| { diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index 94f947a8a48f7..e9ac07e29bfc6 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -44,8 +44,8 @@ use srml_session::{SessionIndex, OneSessionHandler}; /// Rolling Window trait pub trait Trait: system::Trait { - /// Kind to report with window length - type Kind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; + /// MisbehaviorKind to report with window length + type MisbehaviorKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; /// Identifier type to implement `OneSessionHandler` type SessionKey: Member + Parameter + Default + TypedKey + Decode + Encode + AsRef<[u8]>; @@ -57,7 +57,7 @@ decl_storage! { /// /// It stores every unique misbehavior of a kind // TODO [#3149]: optimize how to shrink the window when sessions expire - MisbehaviorReports get(misbehavior_reports): linked_map T::Kind => Vec; + MisbehaviorReports get(misbehavior_reports): linked_map T::MisbehaviorKind => Vec; /// Bonding Uniqueness /// @@ -76,26 +76,26 @@ decl_module! { } /// Trait for getting the number of misbehaviors in the current window -pub trait GetMisbehaviors { +pub trait GetMisbehaviors { /// Get number of misbehavior's in the current window for a kind - fn get_misbehaviors(kind: Kind) -> u64; + fn get_misbehaviors(kind: MisbehaviorKind) -> u64; } -impl GetMisbehaviors for Module { - fn get_misbehaviors(kind: T::Kind) -> u64 { +impl GetMisbehaviors for Module { + fn get_misbehaviors(kind: T::MisbehaviorKind) -> u64 { >::get(kind).len() as u64 } } /// Trait for reporting misbehavior's -pub trait MisbehaviorReporter { +pub trait MisbehaviorReporter { /// Report misbehavior for a kind - fn report_misbehavior(kind: Kind, footprint: Hash, current_session: SessionIndex) -> Result<(), ()>; + fn report_misbehavior(kind: MisbehaviorKind, footprint: Hash, current_session: SessionIndex) -> Result<(), ()>; } -impl MisbehaviorReporter for Module { +impl MisbehaviorReporter for Module { fn report_misbehavior( - kind: T::Kind, + kind: T::MisbehaviorKind, footprint: T::Hash, current_session: SessionIndex, ) -> Result<(), ()> { diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index ecb9812c304c6..8931022e6a0dd 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -118,6 +118,7 @@ impl srml_staking::Trait for Test { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; + type Time = timestamp::Module; } impl srml_session::Trait for Test { @@ -131,9 +132,14 @@ impl srml_session::Trait for Test { type ValidatorIdOf = srml_staking::StashOf; } +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} impl Trait for Test { - type Kind = Kind; + type MisbehaviorKind = Kind; type SessionKey = UintAuthorityId; } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index b6ad94ad25765..00a9ff075375b 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -290,7 +290,6 @@ use srml_support::{ decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, DoSlash, Time - WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, Time } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; From 1f3c340a71f40ce0dcc15b3178e3e9110b4df289 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 25 Jul 2019 12:13:40 +0200 Subject: [PATCH 55/66] move `Misbehavior` to `runtime/impls` --- node/runtime/src/impls.rs | 41 +++++++++++++++++++++++++++++++++++++- node/runtime/src/lib.rs | 42 ++------------------------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/node/runtime/src/impls.rs b/node/runtime/src/impls.rs index 85365b6a7e73e..71b8c863c8a5f 100644 --- a/node/runtime/src/impls.rs +++ b/node/runtime/src/impls.rs @@ -20,9 +20,13 @@ use node_primitives::Balance; use runtime_primitives::weights::{Weight, WeightMultiplier}; use runtime_primitives::traits::{Convert, Saturating}; use runtime_primitives::Fixed64; -use support::traits::Get; +use support::traits::{Get, WindowLength}; +use parity_codec::{Encode, Decode}; use crate::{Balances, MaximumBlockWeight}; +#[cfg(any(feature = "std", test))] +use serde::{Serialize, Deserialize}; + /// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election /// calculation. pub struct CurrencyToVoteHandler; @@ -97,6 +101,41 @@ impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierU } } +/// Misbehavior type which takes window length as input +/// Each variant and its data is a seperate kind +#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub enum Misbehavior { + /// Validator is not online + Unresponsiveness(u32), + /// Unjustified vote + UnjustifiedVote(u32), + /// Rejecting set of votes + RejectSetVotes(u32), + /// Equivocation + Equivocation(u32), + /// Invalid Vote + InvalidVote(u32), + /// Invalid block + InvalidBlock(u32), + /// Parachain Invalid validity statement + ParachainInvalidity(u32), +} + +impl WindowLength for Misbehavior { + fn window_length(&self) -> &u32 { + match self { + Misbehavior::Unresponsiveness(len) => len, + Misbehavior::UnjustifiedVote(len) => len, + Misbehavior::RejectSetVotes(len) => len, + Misbehavior::Equivocation(len) => len, + Misbehavior::InvalidVote(len) => len, + Misbehavior::InvalidBlock(len) => len, + Misbehavior::ParachainInvalidity(len) => len, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 1c7ff4d0a7cbd..b38819c3546d3 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ use rstd::prelude::*; use support::{ - construct_runtime, parameter_types, traits::{SplitTwoWays, Currency, OnUnbalanced, WindowLength} + construct_runtime, parameter_types, traits::{SplitTwoWays, Currency, OnUnbalanced} }; use substrate_primitives::u32_trait::{_1, _2, _3, _4}; use node_primitives::{ @@ -45,9 +45,6 @@ use version::RuntimeVersion; use elections::VoteIndex; #[cfg(any(feature = "std", test))] use version::NativeVersion; -#[cfg(any(feature = "std", test))] -use serde::{Serialize, Deserialize}; -use parity_codec::{Encode, Decode}; use substrate_primitives::OpaqueMetadata; use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; use finality_tracker::{DEFAULT_REPORT_LATENCY, DEFAULT_WINDOW_SIZE}; @@ -64,7 +61,7 @@ pub use staking::StakerStatus; /// Implementations for `Convert` and other helper structs passed into runtime modules as associated /// types. pub mod impls; -use impls::{CurrencyToVoteHandler, WeightMultiplierUpdateHandler}; +use impls::{CurrencyToVoteHandler, WeightMultiplierUpdateHandler, Misbehavior}; // Make the WASM binary available. #[cfg(feature = "std")] @@ -560,38 +557,3 @@ impl_runtime_apis! { } } } - -/// Misbehavior type which takes window length as input -/// Each variant and its data is a seperate kind -#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub enum Misbehavior { - /// Validator is not online - Unresponsiveness(u32), - /// Unjustified vote - UnjustifiedVote(u32), - /// Rejecting set of votes - RejectSetVotes(u32), - /// Equivocation - Equivocation(u32), - /// Invalid Vote - InvalidVote(u32), - /// Invalid block - InvalidBlock(u32), - /// Parachain Invalid validity statement - ParachainInvalidity(u32), -} - -impl WindowLength for Misbehavior { - fn window_length(&self) -> &u32 { - match self { - Misbehavior::Unresponsiveness(len) => len, - Misbehavior::UnjustifiedVote(len) => len, - Misbehavior::RejectSetVotes(len) => len, - Misbehavior::Equivocation(len) => len, - Misbehavior::InvalidVote(len) => len, - Misbehavior::InvalidBlock(len) => len, - Misbehavior::ParachainInvalidity(len) => len, - } - } -} From c7569a1bf79755f0171cf9aaa89746968aef5af3 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 25 Jul 2019 12:32:42 +0200 Subject: [PATCH 56/66] fix bad merge --- node/runtime/src/lib.rs | 11 +++-------- srml/rolling-window/src/mock.rs | 17 ++++------------- srml/staking/src/mock.rs | 2 ++ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 485481647e061..d592f6bbf1a72 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -79,8 +79,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 120, - impl_version: 120, + spec_version: 121, + impl_version: 121, apis: RUNTIME_API_VERSIONS, }; @@ -186,12 +186,7 @@ impl authorship::Trait for Runtime { type EventHandler = (); } -parameter_types! { - pub const Period: BlockNumber = 10 * MINUTES; - pub const Offset: BlockNumber = 0; -} - -type SessionHandlers = (Grandpa, ImOnline, RollingWindow); +type SessionHandlers = (Grandpa, Babe, ImOnline, RollingWindow); impl_opaque_keys! { pub struct SessionKeys { diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index 8931022e6a0dd..c8b48b491f9c0 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -16,7 +16,7 @@ use super::*; use serde::{Serialize, Deserialize}; -use sr_primitives::{traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; +use sr_primitives::{Perbill, traits::{BlakeTwo256, IdentityLookup, Convert}, testing::{Header, UintAuthorityId}}; use substrate_primitives::{Blake2Hasher, H256}; use srml_support::{impl_outer_origin, parameter_types, traits::{Get, WindowLength}}; use std::{collections::HashSet, cell::RefCell}; @@ -91,6 +91,7 @@ impl system::Trait for Test { type WeightMultiplierUpdate = (); type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; } impl balances::Trait for Test { @@ -106,6 +107,7 @@ impl balances::Trait for Test { type CreationFee = CreationFee; type TransactionBaseFee = TransactionBaseFee; type TransactionByteFee = TransactionByteFee; + type WeightToFee = (); } impl srml_staking::Trait for Test { @@ -150,26 +152,15 @@ impl srml_session::historical::Trait for Test { parameter_types! { pub const MinimumPeriod: u64 = 5; -} - -parameter_types! { pub const SessionsPerEra: srml_session::SessionIndex = 3; pub const BondingDuration: srml_staking::EraIndex = 3; -} - -parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; -} - -parameter_types! { pub const TransferFee: u64 = 0; pub const CreationFee: u64 = 0; pub const TransactionBaseFee: u64 = 0; pub const TransactionByteFee: u64 = 0; -} - -parameter_types! { + pub const AvailableBlockRatio: Perbill = Perbill::one(); pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: u32 = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 426ba680a9e1e..7c1f7789ed2e6 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -22,6 +22,7 @@ use primitives::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize}; use primitives::testing::{Header, UintAuthorityId}; use substrate_primitives::{H256, Blake2Hasher}; use runtime_io; +use session::SessionIndex; use srml_support::{assert_ok, impl_outer_origin, parameter_types, EnumerableStorageMap}; use srml_support::traits::{Currency, Get, FindAuthor}; use crate::{ @@ -53,6 +54,7 @@ thread_local! { pub struct TestSessionHandler; impl session::SessionHandler for TestSessionHandler { fn on_new_session( + _session_index: (SessionIndex, SessionIndex), _changed: bool, validators: &[(AccountId, Ks)], _queued_validators: &[(AccountId, Ks)], From fe33a5e9ba4612b7da7d1580a05eb6775882ddd4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 29 Jul 2019 21:06:33 +0200 Subject: [PATCH 57/66] introduce `SlashHistory` in srml_staking --- Cargo.lock | 1 + node/runtime/src/lib.rs | 1 + srml/staking/Cargo.toml | 1 + srml/staking/src/lib.rs | 174 ++++++++++++++-- srml/staking/src/mock.rs | 29 ++- srml/staking/src/slash_tests.rs | 348 ++++++++++++++++++++++++++++++++ srml/support/src/traits.rs | 10 +- 7 files changed, 549 insertions(+), 15 deletions(-) create mode 100644 srml/staking/src/slash_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 62a51b709e5bb..3b73306d4fd14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3970,6 +3970,7 @@ dependencies = [ "sr-std 2.0.0", "srml-authorship 0.1.0", "srml-balances 2.0.0", + "srml-rolling-window 2.0.0", "srml-session 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index d592f6bbf1a72..18c174959e940 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -235,6 +235,7 @@ impl staking::Trait for Runtime { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; + type SlashKind = Misbehavior; } parameter_types! { diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index e9a660ebe06a7..b99b49eaa4988 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -22,6 +22,7 @@ substrate-primitives = { path = "../../core/primitives" } balances = { package = "srml-balances", path = "../balances" } timestamp = { package = "srml-timestamp", path = "../timestamp" } rand = "0.6.5" +srml-rolling-window = { path = "../rolling-window", default-features = false } [features] equalize = [] diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 7e4fe690a0746..79df3781c2b60 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -275,6 +275,9 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(test)] +mod slash_tests; + mod phragmen; mod inflation; @@ -283,13 +286,14 @@ mod benches; #[cfg(feature = "std")] use runtime_io::with_storage; -use rstd::{prelude::*, result, collections::btree_map::BTreeMap}; -use parity_codec::{HasCompact, Encode, Decode}; +use rstd::{prelude::*, result, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; +use parity_codec::{Codec, HasCompact, Encode, Decode}; use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, - WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, DoSlash, Time + WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, DoSlash, Time, + WindowLength, AfterSlash, } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; @@ -297,7 +301,7 @@ use primitives::Perbill; use primitives::weights::SimpleDispatchInfo; use primitives::traits::{ Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded, - SaturatedConversion, SimpleArithmetic + SaturatedConversion, SimpleArithmetic, MaybeSerializeDebug }; #[cfg(feature = "std")] use primitives::{Serialize, Deserialize}; @@ -452,6 +456,22 @@ pub struct Exposure { pub others: Vec>, } +/// State of a slashed entity +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct SlashState { + exposure: Exposure, + slashed_amount: SlashAmount, +} + +/// Slashed amount for a entity including its nominators +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct SlashAmount { + own: Balance, + others: BTreeMap, +} + pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type PositiveImbalanceOf = @@ -540,11 +560,18 @@ pub trait Trait: system::Trait { /// Interface for interacting with a session module. type SessionInterface: self::SessionInterface; + + /// Slash kind for keeping track of different classes of slashes in history + type SlashKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; } decl_storage! { trait Store for Module as Staking { + /// Slashing history + pub SlashHistory get(slash_history): linked_map T::SlashKind => + BTreeMap>>; + /// The ideal number of staking participants. pub ValidatorCount get(validator_count) config(): u32; /// Minimum number of staking participants before emergency conditions are imposed. @@ -1410,6 +1437,93 @@ impl Module { }); }); } + + + /// Tries to adjust the `slash` based on `new_slash` and `prev_slash` + /// + /// Returns the slashed amount + fn adjust_slash(who: &T::AccountId, new_slash: BalanceOf, prev_slash: BalanceOf) -> BalanceOf { + if new_slash > prev_slash { + T::Currency::slash(who, new_slash - prev_slash); + new_slash + } else { + prev_slash + } + } + + /// Update exposure for an already known validator and its nominators + fn update_exposure_for_known( + who: &T::AccountId, + prev_state: &mut SlashState>, + exposure: &Exposure>, + severity: Perbill + ) { + let new_slash = severity * exposure.own; + + T::Currency::slash(&who, new_slash - prev_state.slashed_amount.own); + + let intersection: BTreeSet = exposure.others + .iter() + .filter_map(|e1| prev_state.exposure.others.iter().find(|e2| e1.who == e2.who)) + .map(|e| e.who.clone()) + .collect(); + + let previous_slash = rstd::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); + + for nominator in &exposure.others { + let new_slash = severity * nominator.value; + + // Make sure that we are not double slashing and only apply the difference + // if the nominator was already slashed + if intersection.contains(&nominator.who) { + previous_slash.get(&nominator.who).map(|prev| Self::adjust_slash(&nominator.who, new_slash, *prev)); + } else { + T::Currency::slash(&nominator.who, new_slash); + }; + + prev_state.slashed_amount.others.insert(nominator.who.clone(), new_slash); + } + + prev_state.exposure = exposure.clone(); + prev_state.slashed_amount.own = new_slash; + } + + /// Updates the history of slashes based on the new severity and applies + /// the new slash if the estimated `slash_amount` exceeds the `previous slash_amount` + /// + /// Returns the `true` if `who` was already in the history otherwise `false` + fn mutate_slash_history( + who: &T::AccountId, + exposure: &Exposure>, + severity: Perbill, + kind: T::SlashKind, + ) -> bool { + >::mutate(kind, |state| { + + let mut in_history = false; + + for (stash, s) in state.iter_mut() { + if stash == who { + Self::update_exposure_for_known(stash, s, exposure, severity); + in_history = true; + } else { + s.slashed_amount.own = Self::adjust_slash(stash, severity * s.exposure.own, s.slashed_amount.own); + + for nominator in &s.exposure.others { + let new_slash = severity * nominator.value; + if let Some(prev_slash) = s.slashed_amount.others.get_mut(&nominator.who) { + *prev_slash = Self::adjust_slash(&nominator.who, new_slash, *prev_slash); + } else { + s.slashed_amount.others.insert(nominator.who.clone(), new_slash); + T::Currency::slash(&nominator.who, new_slash); + } + } + } + } + + in_history + }) + } } impl session::OnSessionEnding for Module { @@ -1499,11 +1613,16 @@ impl SelectInitialValidators for Module { } } -/// A type for performing slashing based on severity of misbehavior +/// Type for slashing and receiving events after slashing pub struct StakingSlasher(rstd::marker::PhantomData); -impl DoSlash<(T::AccountId, Exposure>), Reporters, Perbill> - for StakingSlasher +impl + DoSlash< + (T::AccountId, Exposure>), + Reporters, + Perbill, + T::SlashKind + > for StakingSlasher where Reporters: IntoIterator, { @@ -1512,14 +1631,45 @@ where fn do_slash( (who, exposure): (T::AccountId, Exposure>), _reporters: Reporters, - severity: Perbill + severity: Perbill, + kind: T::SlashKind, ) -> Result<(), ()> { - T::Currency::slash(&who, severity * exposure.own); + let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind); - for nominator in exposure.others { - T::Currency::slash(&nominator.who, severity * nominator.value); - } + if !who_exist { + let amount = severity * exposure.own; + T::Currency::slash(&who, amount); + let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; + for nominator in &exposure.others { + let amount = severity * nominator.value; + T::Currency::slash(&nominator.who, amount); + slashed_amount.others.insert(nominator.who.clone(), amount); + } + + >::mutate(kind, |state| { + state.insert(who, SlashState { exposure, slashed_amount }); + }); + } Ok(()) } } + +impl AfterSlash>)> + for StakingSlasher +{ + fn after_slash( + level: u8, + (who, _exposure): (T::AccountId, Exposure>) + ) { + //TODO: remove `who` NPoS validator election + + if level >= 2 { + let _ = T::SessionInterface::disable_validator(&who); + } + + if level >= 3 { + //TODO: remove all the nominators' lists of trusted candidates + } + } +} diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 7c1f7789ed2e6..da8d93d92d2ab 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -24,11 +24,13 @@ use substrate_primitives::{H256, Blake2Hasher}; use runtime_io; use session::SessionIndex; use srml_support::{assert_ok, impl_outer_origin, parameter_types, EnumerableStorageMap}; -use srml_support::traits::{Currency, Get, FindAuthor}; +use srml_support::traits::{Currency, Get, FindAuthor, WindowLength}; use crate::{ EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, Nominators, inflation }; +use parity_codec::{Encode, Decode}; +use serde::{Serialize, Deserialize}; /// The AccountId alias in this test module. pub type AccountId = u64; @@ -194,6 +196,31 @@ impl Trait for Test { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; + type SlashKind = Kind; +} + +impl srml_rolling_window::Trait for Test { + type MisbehaviorKind = Kind; + type SessionKey = UintAuthorityId; +} + +#[derive(Debug, Copy, Clone, Encode, Decode, Serialize, Deserialize, PartialEq)] +pub enum Kind { + One, + Two, + Three, + Four, +} + +impl WindowLength for Kind { + fn window_length(&self) -> &u32 { + match self { + Kind::One => &4, + Kind::Two => &3, + Kind::Three => &2, + Kind::Four => &u32::max_value(), + } + } } pub struct ExtBuilder { diff --git a/srml/staking/src/slash_tests.rs b/srml/staking/src/slash_tests.rs new file mode 100644 index 0000000000000..a7b43b31d1905 --- /dev/null +++ b/srml/staking/src/slash_tests.rs @@ -0,0 +1,348 @@ +#![cfg(test)] + +use crate::*; +use std::marker::PhantomData; +use srml_support::{assert_ok, traits::{ReportSlash, DoSlash, AfterSlash, KeyOwnerProofSystem}}; +use crate::mock::*; +use runtime_io::with_externalities; +use srml_rolling_window::{Module as RollingWindow, MisbehaviorReporter, GetMisbehaviors, + impl_base_severity, impl_kind}; +use substrate_primitives::H256; +use primitives::traits::Hash; +use std::collections::HashMap; +use rstd::cell::RefCell; + +type Balances = balances::Module; + +thread_local! { + static EXPOSURES: RefCell>> = + RefCell::new(Default::default()); +} + +/// Trait for reporting slashes +pub trait ReporterTrait: srml_rolling_window::Trait { + /// Key that identifies the owner + type KeyOwner: KeyOwnerProofSystem; + + type Reporter; + + /// Type of slashing + /// + /// FullId - is the full identification of the entity to slash + /// which may be (AccountId, Exposure) + type BabeEquivocation: ReportSlash< + Self::Hash, + Self::Reporter, + <::KeyOwner as KeyOwnerProofSystem>::FullIdentification + >; +} + +impl ReporterTrait for Test { + type KeyOwner = FakeProver; + type BabeEquivocation = BabeEquivocation, StakingSlasher>; + type Reporter = Vec<(u64, Perbill)>; +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq)] +pub struct FakeProof { + first_header: H, + second_header: H, + author: AccountId, + membership_proof: Proof, +} + +impl FakeProof, AccountId> { + fn new(author: AccountId) -> Self { + Self { + first_header: Default::default(), + second_header: Default::default(), + author, + membership_proof: Vec::new() + } + } +} + +pub struct FakeProver(PhantomData); + +impl KeyOwnerProofSystem for FakeProver { + type Proof = Vec; + type FullIdentification = (u64, Exposure); + + fn prove(_who: u64) -> Option { + Some(Vec::new()) + } + + fn check_proof(who: u64, _proof: Self::Proof) -> Option { + if let Some(exp) = EXPOSURES.with(|x| x.borrow().get(&who).cloned()) { + Some((who, exp)) + } else { + None + } + } +} + +pub struct BabeEquivocationReporter(PhantomData); + +impl BabeEquivocationReporter { + + /// Report an equivocation + pub fn report_equivocation( + proof: FakeProof< + T::Hash, + <::KeyOwner as KeyOwnerProofSystem>::Proof, + T::AccountId + >, + reporters: ::Reporter, + ) -> Result<(), ()> { + let identification = match T::KeyOwner::check_proof(proof.author.clone(), proof.membership_proof) { + Some(id) => id, + None => return Err(()), + }; + + // ignore equivocation slot for this test + let nonce = H256::random(); + let footprint = T::Hashing::hash_of(&(0xbabe, proof.author, nonce)); + + T::BabeEquivocation::slash(footprint, reporters, identification) + } +} + +pub struct BabeEquivocation(PhantomData<(T, DS, AS)>); + +impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); +impl_kind!(BabeEquivocation, Kind: Kind::One); + +impl BabeEquivocation { + pub fn as_misconduct_level(severity: Perbill) -> u8 { + if severity >= Perbill::from_percent(1) { + 3 + } else { + 1 + } + } +} + +impl ReportSlash for BabeEquivocation +where + Who: Clone, + T: ReporterTrait, + DS: DoSlash, + AS: AfterSlash, +{ + fn slash(footprint: T::Hash, reporter: Reporter, who: Who) -> Result<(), ()> { + let kind = Self::kind(); + let _base_seve = Self::base_severity(); + + RollingWindow::::report_misbehavior(kind, footprint, 0)?; + let num_violations = RollingWindow::::get_misbehaviors(kind); + + // number of validators + let n = 50; + + // example how to estimate severity + // 3k / n^2 + let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); + println!("num_violations: {:?}", num_violations); + + DS::do_slash(who.clone(), reporter, severity, kind)?; + AS::after_slash(Self::as_misconduct_level(severity), who); + + Ok(()) + } +} + +#[test] +fn it_works() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let who: AccountId = 0; + + EXPOSURES.with(|x| { + let exp = Exposure { + own: 1000, + total: 1000, + others: Vec::new(), + }; + x.borrow_mut().insert(who, exp) + }); + + let _ = Balances::make_free_balance_be(&who, 1000); + assert_eq!(Balances::free_balance(&who), 1000); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(who), vec![])); + assert_eq!(Balances::free_balance(&who), 999); + }); +} + +#[test] +fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { + let misbehaved: Vec = (0..10).collect(); + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + EXPOSURES.with(|x| { + for who in &misbehaved { + let exp = Exposure { + own: 1000, + total: 1000, + others: Vec::new(), + }; + let _ = Balances::make_free_balance_be(who, 1000); + x.borrow_mut().insert(*who, exp); + } + }); + + for who in &misbehaved { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + } + + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 988, "should slash 1.2%"); + } + }); +} + +#[test] +fn slash_with_nominators_simple() { + let misbehaved = 0; + + let nom_1 = 11; + let nom_2 = 12; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + let _ = Balances::make_free_balance_be(&misbehaved, 9_000); + assert_eq!(Balances::free_balance(&misbehaved), 9_000); + assert_eq!(Balances::free_balance(&nom_1), 10_000); + assert_eq!(Balances::free_balance(&nom_2), 50_000); + + EXPOSURES.with(|x| { + let exp = Exposure { + own: 9_000, + total: 11_200, + others: vec![ + IndividualExposure { who: 11, value: 1500 }, + IndividualExposure { who: 12, value: 700 }, + ], + }; + x.borrow_mut().insert(misbehaved, exp); + }); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + assert_eq!(Balances::free_balance(&misbehaved), 8_990, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_1), 9_999, "should slash 0.12% of exposure not total balance"); + assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.12% of exposure not total balance"); + }); +} + +#[test] +fn slash_should_keep_state_and_increase_slash_for_history_with_nominators() { + let misbehaved: Vec = (0..3).collect(); + + let nom_1 = 11; + let nom_2 = 12; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + + EXPOSURES.with(|x| { + for &who in &misbehaved { + let exp = Exposure { + own: 1000, + total: 1500, + others: vec![ + IndividualExposure { who: nom_1, value: 300 }, + IndividualExposure { who: nom_2, value: 200 }, + ], + }; + let _ = Balances::make_free_balance_be(&who, 1000); + x.borrow_mut().insert(who, exp); + } + }); + + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 1000); + } + + for who in &misbehaved { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + } + + assert_eq!(Balances::free_balance(&0), 997, "should slash 0.36%"); + assert_eq!(Balances::free_balance(&1), 997, "should slash 0.36%"); + assert_eq!(Balances::free_balance(&2), 997, "should slash 0.36%"); + // (300 * 0.0036) * 3 = 3 + assert_eq!(Balances::free_balance(&nom_1), 9_997, "should slash 0.36%"); + // (200 * 0.0036) * 3 = 0 + assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.36%"); + }); +} + +#[test] +fn slash_update_exposure_when_same_validator_gets_slashed_twice() { + let misbehaved = 0; + + let nom_1 = 11; + let nom_2 = 12; + let nom_3 = 13; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + let _ = Balances::make_free_balance_be(&nom_3, 5_000); + let _ = Balances::make_free_balance_be(&misbehaved, 1000); + + + let exp1 = Exposure { + own: 1_000, + total: 31_000, + others: vec![ + IndividualExposure { who: nom_1, value: 5_000 }, + IndividualExposure { who: nom_2, value: 25_000 }, + ], + }; + + EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp1)); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + assert_eq!(Balances::free_balance(&misbehaved), 999, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_1), 9_994, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_2), 49_970, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_3), 5_000, "not exposed should not be slashed"); + + let exp2 = Exposure { + own: 999, + total: 16098, + others: vec![ + IndividualExposure { who: nom_1, value: 10_000 }, + IndividualExposure { who: nom_2, value: 100 }, + IndividualExposure { who: nom_3, value: 4_999 }, + ], + }; + + // change exposure for `misbehaved + EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp2)); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + // exposure is 999 so slashed based on that amount but revert previous slash + // -> 999 * 0.0024 = 2, -> 1000 - 2 = 998 + assert_eq!(Balances::free_balance(&misbehaved), 998, "should slash 0.24%"); + assert_eq!(Balances::free_balance(&nom_1), 9_976, "should slash 0.24%"); + assert_eq!(Balances::free_balance(&nom_2), 49_970, "exposed but slash is smaller previous is still valid"); + // exposure is 4999, slash 0.0024 * 4999 -> 11 + // 5000 - 11 = 4989 + assert_eq!(Balances::free_balance(&nom_3), 4_989, "should slash 0.24%"); + }); +} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index f0c4ace9976a9..7217218eced9e 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -651,9 +651,15 @@ pub trait ReportSlash { } /// A generic trait for enacting slashes. -pub trait DoSlash { +pub trait DoSlash { /// Performs the actual slashing and rewarding based on severity - fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity) -> Result<(), ()>; + fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity, kind: Kind) -> Result<(), ()>; +} + +/// A generic event handler trait after slashing occured +pub trait AfterSlash { + /// Invoke event handler after slashing occurred. + fn after_slash(who: Who, misconduct_level: MisconductLevel); } /// Trait for representing window length From 5d2d75684363ddf4107b77318c4aa64e3641dbb7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 30 Jul 2019 13:50:57 +0200 Subject: [PATCH 58/66] improve `after_slash` impl --- srml/staking/src/lib.rs | 16 ++++++-- srml/staking/src/mock.rs | 4 +- srml/staking/src/slash_tests.rs | 66 +++++++++++++++++++++++---------- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index e95b50a5fce8f..3817ecae9663c 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1619,7 +1619,7 @@ impl SelectInitialValidators for Module { } } -/// Type for slashing and receiving events after slashing +/// Type for handling slashing and rewarding pub struct StakingSlasher(rstd::marker::PhantomData); impl @@ -1666,16 +1666,24 @@ impl AfterSlash { fn after_slash( level: u8, - (who, _exposure): (T::AccountId, Exposure>) + (who, exposure): (T::AccountId, Exposure>) ) { - //TODO: remove `who` NPoS validator election + // Remove from the validator from next NPoS election + >::remove(&who); + // Disable the validator if level >= 2 { let _ = T::SessionInterface::disable_validator(&who); } + // Remove the validator from nominators' lists of trusted candidates + // TODO(niklasad1): not sure if this is correct if level >= 3 { - //TODO: remove all the nominators' lists of trusted candidates + for nominator in &exposure.others { + // make sure that duplicated entries are removed to + >::mutate(&nominator.who, |validators| validators.retain(|v| v != &who)); + } + >::remove(&who); } } } diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 4c2f69c354c40..8f2b7aa8ac414 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -75,8 +75,8 @@ impl session::SessionHandler for TestSessionHandler { } } -pub fn is_disabled(validator: AccountId) -> bool { - let stash = Staking::ledger(&validator).unwrap().stash; +pub fn is_disabled(controller: AccountId) -> bool { + let stash = Staking::ledger(&controller).unwrap().stash; SESSION.with(|d| d.borrow().1.contains(&stash)) } diff --git a/srml/staking/src/slash_tests.rs b/srml/staking/src/slash_tests.rs index f74d45bcbaed6..d1ccefd34e034 100644 --- a/srml/staking/src/slash_tests.rs +++ b/srml/staking/src/slash_tests.rs @@ -108,6 +108,8 @@ impl BabeEquivocationReporter { } } +// think of this a `decl_mod!` which have some events +// such as `Misbehavior(Self::kind())`, pub struct BabeEquivocation(PhantomData<(T, DS, AS)>); impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); @@ -115,8 +117,12 @@ impl_kind!(BabeEquivocation, Kind: Kind::One); impl BabeEquivocation { pub fn as_misconduct_level(severity: Perbill) -> u8 { - if severity >= Perbill::from_percent(1) { + if severity > Perbill::from_percent(10) { + 4 + } else if severity > Perbill::from_percent(1) { 3 + } else if severity > Perbill::from_rational_approximation(1_u32, 1000_u32) { + 2 } else { 1 } @@ -143,36 +149,56 @@ where // example how to estimate severity // 3k / n^2 let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); - println!("num_violations: {:?}", num_violations); + let misconduct_level = Self::as_misconduct_level(severity); + println!("num_violations: {:?}, misconduct_level: {:?}", num_violations, misconduct_level); DS::do_slash(who.clone(), reporter, severity, kind)?; - AS::after_slash(Self::as_misconduct_level(severity), who); + AS::after_slash(misconduct_level, who); Ok(()) } } #[test] -fn it_works() { +fn simple_with_after_slash() { with_externalities(&mut ExtBuilder::default() .build(), || { - let who: AccountId = 0; + let who = 11; + let controller = 10; + let nom = 101; + let exp = Staking::stakers(who); + + // who is stash and controller is controller + assert_eq!(Staking::bonded(&who), Some(controller)); + assert!(>::exists(&who)); + assert_eq!( + exp, + Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: nom, value: 250 }] } + ); EXPOSURES.with(|x| { - let exp = Exposure { - own: 1000, - total: 1000, - others: Vec::new(), - }; x.borrow_mut().insert(who, exp) }); - let _ = Balances::make_free_balance_be(&who, 1000); - assert_eq!(Balances::free_balance(&who), 1000); - + let nominator_balance = Balances::free_balance(&nom); assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(who), vec![])); - assert_eq!(Balances::free_balance(&who), 999); + assert_eq!(Balances::free_balance(&who), 999, "slash 0.12%"); + assert_eq!(Balances::free_balance(&nom), nominator_balance, "0.12% of 250 is zero, don't slash anythin"); + + assert!(is_disabled(controller), "severity higher than 0.1%"); + assert!(!>::exists(&who), "remove from the `wannabe` validators"); + + // increase severity to level 3 + for _ in 0..10 { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(who), vec![])); + } + + for v in Staking::nominators(nom) { + assert!(v != who, "slashed validators on level 3 should not be trusted"); + } + + assert_eq!(Staking::stakers(who).total, 0); }); } @@ -207,7 +233,7 @@ fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { #[test] fn slash_with_nominators_simple() { - let misbehaved = 0; + let misbehaved = 1; let nom_1 = 11; let nom_2 = 12; @@ -227,8 +253,8 @@ fn slash_with_nominators_simple() { own: 9_000, total: 11_200, others: vec![ - IndividualExposure { who: 11, value: 1500 }, - IndividualExposure { who: 12, value: 700 }, + IndividualExposure { who: nom_1, value: 1500 }, + IndividualExposure { who: nom_2, value: 700 }, ], }; x.borrow_mut().insert(misbehaved, exp); @@ -278,9 +304,9 @@ fn slash_should_keep_state_and_increase_slash_for_history_with_nominators() { assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); } - assert_eq!(Balances::free_balance(&0), 997, "should slash 0.36%"); - assert_eq!(Balances::free_balance(&1), 997, "should slash 0.36%"); - assert_eq!(Balances::free_balance(&2), 997, "should slash 0.36%"); + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 997, "should slash 0.36%"); + } // (300 * 0.0036) * 3 = 3 assert_eq!(Balances::free_balance(&nom_1), 9_997, "should slash 0.36%"); // (200 * 0.0036) * 3 = 0 From b22cc2714002735fae0b3ad464d2bba7e7736bb7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 31 Jul 2019 16:04:37 +0200 Subject: [PATCH 59/66] restructure: slash mod in staking for now --- srml/staking/src/lib.rs | 189 ++-------- srml/staking/src/mock.rs | 1 - srml/staking/src/slash.rs | 616 ++++++++++++++++++++++++++++++++ srml/staking/src/slash_tests.rs | 375 ------------------- srml/support/src/traits.rs | 9 +- 5 files changed, 651 insertions(+), 539 deletions(-) create mode 100644 srml/staking/src/slash.rs delete mode 100644 srml/staking/src/slash_tests.rs diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 3817ecae9663c..ecc138cc4b879 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -275,25 +275,23 @@ mod mock; #[cfg(test)] mod tests; -#[cfg(test)] -mod slash_tests; - mod phragmen; mod inflation; +mod slash; #[cfg(all(feature = "bench", test))] mod benches; #[cfg(feature = "std")] use runtime_io::with_storage; -use rstd::{prelude::*, result, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; -use parity_codec::{Codec, HasCompact, Encode, Decode}; +use rstd::{prelude::*, result, collections::{btree_map::BTreeMap}}; +use parity_codec::{HasCompact, Encode, Decode}; use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, - WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, DoSlash, Time, - WindowLength, AfterSlash, + WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, Time, + AfterSlash, } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; @@ -301,7 +299,7 @@ use sr_primitives::Perbill; use sr_primitives::weights::SimpleDispatchInfo; use sr_primitives::traits::{ Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded, - SaturatedConversion, SimpleArithmetic, MaybeSerializeDebug + SaturatedConversion, SimpleArithmetic }; #[cfg(feature = "std")] use sr_primitives::{Serialize, Deserialize}; @@ -560,18 +558,10 @@ pub trait Trait: system::Trait { /// Interface for interacting with a session module. type SessionInterface: self::SessionInterface; - - /// Slash kind for keeping track of different classes of slashes in history - type SlashKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; } decl_storage! { trait Store for Module as Staking { - - /// Slashing history - pub SlashHistory get(slash_history): linked_map T::SlashKind => - BTreeMap>>; - /// The ideal number of staking participants. pub ValidatorCount get(validator_count) config(): u32; /// Minimum number of staking participants before emergency conditions are imposed. @@ -1443,93 +1433,6 @@ impl Module { }); }); } - - - /// Tries to adjust the `slash` based on `new_slash` and `prev_slash` - /// - /// Returns the slashed amount - fn adjust_slash(who: &T::AccountId, new_slash: BalanceOf, prev_slash: BalanceOf) -> BalanceOf { - if new_slash > prev_slash { - T::Currency::slash(who, new_slash - prev_slash); - new_slash - } else { - prev_slash - } - } - - /// Update exposure for an already known validator and its nominators - fn update_exposure_for_known( - who: &T::AccountId, - prev_state: &mut SlashState>, - exposure: &Exposure>, - severity: Perbill - ) { - let new_slash = severity * exposure.own; - - T::Currency::slash(&who, new_slash - prev_state.slashed_amount.own); - - let intersection: BTreeSet = exposure.others - .iter() - .filter_map(|e1| prev_state.exposure.others.iter().find(|e2| e1.who == e2.who)) - .map(|e| e.who.clone()) - .collect(); - - let previous_slash = rstd::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); - - for nominator in &exposure.others { - let new_slash = severity * nominator.value; - - // Make sure that we are not double slashing and only apply the difference - // if the nominator was already slashed - if intersection.contains(&nominator.who) { - previous_slash.get(&nominator.who).map(|prev| Self::adjust_slash(&nominator.who, new_slash, *prev)); - } else { - T::Currency::slash(&nominator.who, new_slash); - }; - - prev_state.slashed_amount.others.insert(nominator.who.clone(), new_slash); - } - - prev_state.exposure = exposure.clone(); - prev_state.slashed_amount.own = new_slash; - } - - /// Updates the history of slashes based on the new severity and applies - /// the new slash if the estimated `slash_amount` exceeds the `previous slash_amount` - /// - /// Returns the `true` if `who` was already in the history otherwise `false` - fn mutate_slash_history( - who: &T::AccountId, - exposure: &Exposure>, - severity: Perbill, - kind: T::SlashKind, - ) -> bool { - >::mutate(kind, |state| { - - let mut in_history = false; - - for (stash, s) in state.iter_mut() { - if stash == who { - Self::update_exposure_for_known(stash, s, exposure, severity); - in_history = true; - } else { - s.slashed_amount.own = Self::adjust_slash(stash, severity * s.exposure.own, s.slashed_amount.own); - - for nominator in &s.exposure.others { - let new_slash = severity * nominator.value; - if let Some(prev_slash) = s.slashed_amount.others.get_mut(&nominator.who) { - *prev_slash = Self::adjust_slash(&nominator.who, new_slash, *prev_slash); - } else { - s.slashed_amount.others.insert(nominator.who.clone(), new_slash); - T::Currency::slash(&nominator.who, new_slash); - } - } - } - } - - in_history - }) - } } impl session::OnSessionEnding for Module { @@ -1619,71 +1522,33 @@ impl SelectInitialValidators for Module { } } -/// Type for handling slashing and rewarding -pub struct StakingSlasher(rstd::marker::PhantomData); +/// A type for taking action after slashing and rewarding occurred +pub struct AfterSlashing(rstd::marker::PhantomData); -impl - DoSlash< - (T::AccountId, Exposure>), - Reporters, - Perbill, - T::SlashKind - > for StakingSlasher +/// Update state after slashing occurred +impl AfterSlash for AfterSlashing where - Reporters: IntoIterator, + Who: IntoIterator>)>, { - // TODO: #3166 pay out rewards to the reporters - // Perbill is priority for the reporter - fn do_slash( - (who, exposure): (T::AccountId, Exposure>), - _reporters: Reporters, - severity: Perbill, - kind: T::SlashKind, - ) -> Result<(), ()> { - let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind); - - if !who_exist { - let amount = severity * exposure.own; - T::Currency::slash(&who, amount); - let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; - - for nominator in &exposure.others { - let amount = severity * nominator.value; - T::Currency::slash(&nominator.who, amount); - slashed_amount.others.insert(nominator.who.clone(), amount); + fn after_slash(misbehaved: Who, level: u8) { + for (who, exposure) in misbehaved { + // Remove from the validator from next NPoS election + >::remove(&who); + + // Disable the validator + if level >= 2 { + let _ = T::SessionInterface::disable_validator(&who); } - >::mutate(kind, |state| { - state.insert(who, SlashState { exposure, slashed_amount }); - }); - } - Ok(()) - } -} - -impl AfterSlash>)> - for StakingSlasher -{ - fn after_slash( - level: u8, - (who, exposure): (T::AccountId, Exposure>) - ) { - // Remove from the validator from next NPoS election - >::remove(&who); - - // Disable the validator - if level >= 2 { - let _ = T::SessionInterface::disable_validator(&who); - } - - // Remove the validator from nominators' lists of trusted candidates - // TODO(niklasad1): not sure if this is correct - if level >= 3 { - for nominator in &exposure.others { - // make sure that duplicated entries are removed to - >::mutate(&nominator.who, |validators| validators.retain(|v| v != &who)); + // Remove the validator from nominators' lists of trusted candidates + // TODO(niklasad1): not sure if this is correct + if level >= 3 { + for nominator in &exposure.others { + // use `retain` to make sure that duplicated entries are removed too + >::mutate(&nominator.who, |validators| validators.retain(|v| v != &who)); + } + >::remove(&who); } - >::remove(&who); } } } diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 8f2b7aa8ac414..876b44377b2f7 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -196,7 +196,6 @@ impl Trait for Test { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; - type SlashKind = Kind; } impl srml_rolling_window::Trait for Test { diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs new file mode 100644 index 0000000000000..f8906c5745fc8 --- /dev/null +++ b/srml/staking/src/slash.rs @@ -0,0 +1,616 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Slashing mod + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs, rust_2018_idioms)] + +use crate::Exposure; +use srml_support::{ + StorageMap, decl_module, decl_storage, + traits::{Currency, WindowLength, DoSlash} +}; +use parity_codec::{HasCompact, Codec, Decode, Encode}; +use rstd::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +use sr_primitives::{Perbill, traits::MaybeSerializeDebug}; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// Slashing trait +pub trait Trait: system::Trait { + /// Slashing kind + type SlashKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; + /// Currency + type Currency: Currency; +} + +/// State of a slashed entity +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct SlashState { + exposure: Exposure, + slashed_amount: SlashAmount, +} + +/// Slashed amount for a entity including its nominators +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct SlashAmount { + own: Balance, + others: BTreeMap, +} + +decl_storage! { + trait Store for Module as RollingWindow { + /// Misbehavior reports + SlashHistory get(misbehavior_reports): linked_map T::SlashKind => + BTreeMap>>; + } +} + +decl_module! { + /// Slashing module + pub struct Module for enum Call where origin: T::Origin {} +} + +impl Module { + + /// Tries to adjust the `slash` based on `new_slash` and `prev_slash` + /// + /// Returns the total slashed amount + fn adjust_slash(who: &T::AccountId, new_slash: BalanceOf, prev_slash: BalanceOf) -> BalanceOf { + if new_slash > prev_slash { + T::Currency::slash(who, new_slash - prev_slash); + new_slash + } else { + prev_slash + } + } + + fn update_exposure_for_known( + who: &T::AccountId, + prev_state: &mut SlashState>, + exposure: &Exposure>, + severity: Perbill + ) { + let new_slash = severity * exposure.own; + + T::Currency::slash(&who, new_slash - prev_state.slashed_amount.own); + + let intersection: BTreeSet = exposure.others + .iter() + .filter_map(|e1| prev_state.exposure.others.iter().find(|e2| e1.who == e2.who)) + .map(|e| e.who.clone()) + .collect(); + + let previous_slash = std::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); + + for nominator in &exposure.others { + let new_slash = severity * nominator.value; + + // make sure that we are not double slashing + if intersection.contains(&nominator.who) { + previous_slash.get(&nominator.who).map(|prev| Self::adjust_slash(&nominator.who, new_slash, *prev)); + } else { + T::Currency::slash(&nominator.who, new_slash); + }; + + prev_state.slashed_amount.others.insert(nominator.who.clone(), new_slash); + } + + prev_state.exposure = exposure.clone(); + prev_state.slashed_amount.own = new_slash; + } + + /// Updates the history of slashes based on the new severity and only apply new slash + /// if the estimated `slash_amount` exceeds the `previous slash_amount` + /// + /// Returns the `true` if `who` was already in the history otherwise `false` + fn mutate_slash_history( + who: &T::AccountId, + exposure: &Exposure>, + severity: Perbill, + kind: T::SlashKind + ) -> bool { + >::mutate(kind, |state| { + + let mut in_history = false; + + for (stash, s) in state.iter_mut() { + if stash == who { + Self::update_exposure_for_known(stash, s, exposure, severity); + in_history = true; + } else { + s.slashed_amount.own = Self::adjust_slash(stash, severity * s.exposure.own, s.slashed_amount.own); + + for nominator in &s.exposure.others { + let new_slash = severity * nominator.value; + if let Some(prev_slash) = s.slashed_amount.others.get_mut(&nominator.who) { + *prev_slash = Self::adjust_slash(&nominator.who, new_slash, *prev_slash); + } else { + s.slashed_amount.others.insert(nominator.who.clone(), new_slash); + T::Currency::slash(&nominator.who, new_slash); + } + } + } + } + + in_history + }) + } +} + +impl + DoSlash< + (T::AccountId, Exposure>), + Reporters, + Perbill, + T::SlashKind + > for Module +where + Reporters: IntoIterator, +{ + type Slashed = Vec<(T::AccountId, Exposure>)>; + + // TODO: #3166 pay out rewards to the reporters + // Perbill is priority for the reporter + fn do_slash( + (who, exposure): (T::AccountId, Exposure>), + _reporters: Reporters, + severity: Perbill, + kind: T::SlashKind, + ) -> Result { + + let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind); + + if !who_exist { + let amount = severity * exposure.own; + T::Currency::slash(&who, amount); + let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; + + for nominator in &exposure.others { + let amount = severity * nominator.value; + T::Currency::slash(&nominator.who, amount); + slashed_amount.others.insert(nominator.who.clone(), amount); + } + + >::mutate(kind, |state| { + state.insert(who, SlashState { exposure, slashed_amount }); + }); + } + + let slashed = >::get(kind) + .iter() + .map(|(who, state)| (who.clone(), state.exposure.clone())) + .collect(); + + Ok(slashed) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + Exposure, IndividualExposure, Validators, + slash::{Trait, Module as SlashingModule}, + mock::* + }; + use rstd::cell::RefCell; + use runtime_io::with_externalities; + use sr_primitives::{Perbill, traits::Hash}; + use srml_rolling_window::{ + Module as RollingWindow, MisbehaviorReporter, GetMisbehaviors, impl_base_severity, impl_kind + }; + use srml_support::{assert_ok, traits::{ReportSlash, DoSlash, AfterSlash, KeyOwnerProofSystem}}; + use std::collections::HashMap; + use std::marker::PhantomData; + use primitives::H256; + + type Balances = balances::Module; + + thread_local! { + static EXPOSURES: RefCell>> = + RefCell::new(Default::default()); + } + + /// Trait for reporting slashes + pub trait ReporterTrait: srml_rolling_window::Trait + Trait { + /// Key that identifies the owner + type KeyOwner: KeyOwnerProofSystem; + + type Reporter; + + type BabeEquivocation: ReportSlash< + Self::Hash, + Self::Reporter, + <::KeyOwner as KeyOwnerProofSystem>::FullIdentification + >; + } + + impl Trait for Test { + type SlashKind = Kind; + type Currency = Balances; + } + + impl ReporterTrait for Test { + type KeyOwner = FakeProver; + type BabeEquivocation = BabeEquivocation, crate::AfterSlashing>; + type Reporter = Vec<(u64, Perbill)>; + } + + #[derive(Debug, Clone, Encode, Decode, PartialEq)] + pub struct FakeProof { + first_header: H, + second_header: H, + author: AccountId, + membership_proof: Proof, + } + + impl FakeProof, AccountId> { + fn new(author: AccountId) -> Self { + Self { + first_header: Default::default(), + second_header: Default::default(), + author, + membership_proof: Vec::new() + } + } + } + + pub struct FakeProver(PhantomData); + + impl KeyOwnerProofSystem for FakeProver { + type Proof = Vec; + type FullIdentification = (u64, Exposure); + + fn prove(_who: u64) -> Option { + Some(Vec::new()) + } + + fn check_proof(who: u64, _proof: Self::Proof) -> Option { + if let Some(exp) = EXPOSURES.with(|x| x.borrow().get(&who).cloned()) { + Some((who, exp)) + } else { + None + } + } + } + + pub struct BabeEquivocationReporter(PhantomData); + + impl BabeEquivocationReporter { + + /// Report an equivocation + pub fn report_equivocation( + proof: FakeProof< + T::Hash, + <::KeyOwner as KeyOwnerProofSystem>::Proof, + T::AccountId + >, + reporters: ::Reporter, + ) -> Result<(), ()> { + let identification = match T::KeyOwner::check_proof(proof.author.clone(), proof.membership_proof) { + Some(id) => id, + None => return Err(()), + }; + + // ignore equivocation slot for this test + let nonce = H256::random(); + let footprint = T::Hashing::hash_of(&(0xbabe, proof.author, nonce)); + + T::BabeEquivocation::slash(footprint, reporters, identification) + } + } + + pub struct BabeEquivocation(PhantomData<(T, DS, AS)>); + + impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); + impl_kind!(BabeEquivocation, Kind: Kind::One); + + impl BabeEquivocation { + pub fn as_misconduct_level(severity: Perbill) -> u8 { + if severity > Perbill::from_percent(10) { + 4 + } else if severity > Perbill::from_percent(1) { + 3 + } else if severity > Perbill::from_rational_approximation(1_u32, 1000_u32) { + 2 + } else { + 1 + } + } + } + + impl ReportSlash for BabeEquivocation + where + Who: Clone, + T: ReporterTrait, + DS: DoSlash, + AS: AfterSlash, + { + fn slash(footprint: T::Hash, reporter: Reporter, who: Who) -> Result<(), ()> { + let kind = Self::kind(); + let _base_seve = Self::base_severity(); + + RollingWindow::::report_misbehavior(kind, footprint, 0)?; + let num_violations = RollingWindow::::get_misbehaviors(kind); + + // number of validators + let n = 50; + + // example how to estimate severity + // 3k / n^2 + let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); + let slashed = DS::do_slash(who, reporter, severity, kind)?; + AS::after_slash(slashed, Self::as_misconduct_level(severity)); + + Ok(()) + } + } + + #[test] + fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { + let misbehaved: Vec = (0..10).collect(); + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + EXPOSURES.with(|x| { + for who in &misbehaved { + let exp = Exposure { + own: 1000, + total: 1000, + others: Vec::new(), + }; + let _ = Balances::make_free_balance_be(who, 1000); + x.borrow_mut().insert(*who, exp); + } + }); + + for who in &misbehaved { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + } + + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 988, "should slash 1.2%"); + } + }); + } + + #[test] + fn slash_with_nominators_simple() { + let misbehaved = 1; + + let nom_1 = 11; + let nom_2 = 12; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + let _ = Balances::make_free_balance_be(&misbehaved, 9_000); + assert_eq!(Balances::free_balance(&misbehaved), 9_000); + assert_eq!(Balances::free_balance(&nom_1), 10_000); + assert_eq!(Balances::free_balance(&nom_2), 50_000); + + EXPOSURES.with(|x| { + let exp = Exposure { + own: 9_000, + total: 11_200, + others: vec![ + IndividualExposure { who: nom_1, value: 1500 }, + IndividualExposure { who: nom_2, value: 700 }, + ], + }; + x.borrow_mut().insert(misbehaved, exp); + }); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + assert_eq!(Balances::free_balance(&misbehaved), 8_990, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_1), 9_999, "should slash 0.12% of exposure not total balance"); + assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.12% of exposure not total balance"); + }); + } + + #[test] + fn slash_should_keep_state_and_increase_slash_for_history_with_nominators() { + let misbehaved: Vec = (0..3).collect(); + + let nom_1 = 11; + let nom_2 = 12; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + + EXPOSURES.with(|x| { + for &who in &misbehaved { + let exp = Exposure { + own: 1000, + total: 1500, + others: vec![ + IndividualExposure { who: nom_1, value: 300 }, + IndividualExposure { who: nom_2, value: 200 }, + ], + }; + let _ = Balances::make_free_balance_be(&who, 1000); + x.borrow_mut().insert(who, exp); + } + }); + + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 1000); + } + + for who in &misbehaved { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + } + + for who in &misbehaved { + assert_eq!(Balances::free_balance(who), 997, "should slash 0.36%"); + } + // (300 * 0.0036) * 3 = 3 + assert_eq!(Balances::free_balance(&nom_1), 9_997, "should slash 0.36%"); + // (200 * 0.0036) * 3 = 0 + assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.36%"); + }); + } + + #[test] + fn slash_update_exposure_when_same_validator_gets_slashed_twice() { + let misbehaved = 0; + + let nom_1 = 11; + let nom_2 = 12; + let nom_3 = 13; + + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let _ = Balances::make_free_balance_be(&nom_1, 10_000); + let _ = Balances::make_free_balance_be(&nom_2, 50_000); + let _ = Balances::make_free_balance_be(&nom_3, 5_000); + let _ = Balances::make_free_balance_be(&misbehaved, 1000); + + + let exp1 = Exposure { + own: 1_000, + total: 31_000, + others: vec![ + IndividualExposure { who: nom_1, value: 5_000 }, + IndividualExposure { who: nom_2, value: 25_000 }, + ], + }; + + EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp1)); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + assert_eq!(Balances::free_balance(&misbehaved), 999, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_1), 9_994, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_2), 49_970, "should slash 0.12%"); + assert_eq!(Balances::free_balance(&nom_3), 5_000, "not exposed should not be slashed"); + + let exp2 = Exposure { + own: 999, + total: 16098, + others: vec![ + IndividualExposure { who: nom_1, value: 10_000 }, + IndividualExposure { who: nom_2, value: 100 }, + IndividualExposure { who: nom_3, value: 4_999 }, + ], + }; + + // change exposure for `misbehaved + EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp2)); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + + // exposure is 999 so slashed based on that amount but revert previous slash + // -> 999 * 0.0024 = 2, -> 1000 - 2 = 998 + assert_eq!(Balances::free_balance(&misbehaved), 998, "should slash 0.24%"); + assert_eq!(Balances::free_balance(&nom_1), 9_976, "should slash 0.24%"); + assert_eq!(Balances::free_balance(&nom_2), 49_970, "exposed but slash is smaller previous is still valid"); + // exposure is 4999, slash 0.0024 * 4999 -> 11 + // 5000 - 11 = 4989 + assert_eq!(Balances::free_balance(&nom_3), 4_989, "should slash 0.24%"); + }); + } + + #[test] + fn simple_with_after_slash() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + let m1 = 11; + let c1 = 10; + let m2 = 21; + let c2 = 20; + let nom = 101; + let exp1 = Staking::stakers(m1); + let exp2 = Staking::stakers(m2); + let initial_balance_m1 = Balances::free_balance(&m1); + let initial_balance_m2 = Balances::free_balance(&m2); + let initial_balance_nom = Balances::free_balance(&nom); + + // m1 (stash) -> c1 (controller) + // m2 (stash) -> c2 (controller) + assert_eq!(Staking::bonded(&m1), Some(c1)); + assert_eq!(Staking::bonded(&m2), Some(c2)); + assert!(>::exists(&m1)); + assert!(>::exists(&m2)); + + assert_eq!( + exp1, + Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: nom, value: 250 }] } + ); + assert_eq!( + exp2, + Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: nom, value: 250 }] } + ); + + EXPOSURES.with(|x| { + x.borrow_mut().insert(m1, exp1); + x.borrow_mut().insert(m2, exp2) + }); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![])); + assert_eq!(Balances::free_balance(&m1), initial_balance_m1 - 1, "should slash 0.12% of 1000"); + assert_eq!(Balances::free_balance(&m2), initial_balance_m2, "no misconducts yet; no slash"); + assert_eq!(Balances::free_balance(&nom), initial_balance_nom, "0.12% of 250 is zero, don't slash anything"); + + assert!(is_disabled(c1), "m1 has misconduct level 2 should be disabled by now"); + assert!(!>::exists(&m1), "m1 is misconducter shall be disregard from next election"); + assert!(!is_disabled(c2), "m2 is not a misconducter; still available"); + assert!(>::exists(&m2), "no misconducts yet; still a candidate"); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![])); + + assert_eq!(Balances::free_balance(&m1), initial_balance_m1 - 2, "should slash 0.24% of 1000"); + assert_eq!(Balances::free_balance(&m2), initial_balance_m2 - 2, "should slash 0.24% of 1000"); + assert_eq!(Balances::free_balance(&nom), initial_balance_nom, "0.12% of 250 is zero, don't slash anything"); + + assert!(is_disabled(c1), "m1 has misconduct level 2 should be disabled by now"); + assert!(!>::exists(&m1), "m1 is misconducter shall be disregard from next election"); + assert!(is_disabled(c2), "m2 has misconduct level 2 should be disabled by now"); + assert!(!>::exists(&m2), "m2 has misconduct level 2 should be disabled by now"); + + // ensure m1 and m2 are still trusted by its nominator + assert_eq!(Staking::nominators(nom).contains(&m1), true); + assert_eq!(Staking::nominators(nom).contains(&m2), true); + + // increase severity to level 3 + // note, this only reports misconducts from `m2` but `m1` should be updated as well. + for _ in 0..10 { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![])); + } + + // ensure m1 and m2 are not trusted by its nominator anymore + assert_eq!(Staking::nominators(nom).contains(&m1), false); + assert_eq!(Staking::nominators(nom).contains(&m2), false); + + assert_eq!(Staking::stakers(m1).total, 0); + assert_eq!(Staking::stakers(m2).total, 0); + }); + } +} diff --git a/srml/staking/src/slash_tests.rs b/srml/staking/src/slash_tests.rs deleted file mode 100644 index d1ccefd34e034..0000000000000 --- a/srml/staking/src/slash_tests.rs +++ /dev/null @@ -1,375 +0,0 @@ -#![cfg(test)] - -use crate::*; -use crate::mock::*; -use primitives::H256; -use rstd::cell::RefCell; -use runtime_io::with_externalities; -use sr_primitives::traits::Hash; -use srml_rolling_window::{ - Module as RollingWindow, MisbehaviorReporter, GetMisbehaviors,impl_base_severity, impl_kind -}; -use srml_support::{assert_ok, traits::{ReportSlash, DoSlash, AfterSlash, KeyOwnerProofSystem}}; -use std::collections::HashMap; -use std::marker::PhantomData; - -type Balances = balances::Module; - -thread_local! { - static EXPOSURES: RefCell>> = - RefCell::new(Default::default()); -} - -/// Trait for reporting slashes -pub trait ReporterTrait: srml_rolling_window::Trait { - /// Key that identifies the owner - type KeyOwner: KeyOwnerProofSystem; - - type Reporter; - - /// Type of slashing - /// - /// FullId - is the full identification of the entity to slash - /// which may be (AccountId, Exposure) - type BabeEquivocation: ReportSlash< - Self::Hash, - Self::Reporter, - <::KeyOwner as KeyOwnerProofSystem>::FullIdentification - >; -} - -impl ReporterTrait for Test { - type KeyOwner = FakeProver; - type BabeEquivocation = BabeEquivocation, StakingSlasher>; - type Reporter = Vec<(u64, Perbill)>; -} - -#[derive(Debug, Clone, Encode, Decode, PartialEq)] -pub struct FakeProof { - first_header: H, - second_header: H, - author: AccountId, - membership_proof: Proof, -} - -impl FakeProof, AccountId> { - fn new(author: AccountId) -> Self { - Self { - first_header: Default::default(), - second_header: Default::default(), - author, - membership_proof: Vec::new() - } - } -} - -pub struct FakeProver(PhantomData); - -impl KeyOwnerProofSystem for FakeProver { - type Proof = Vec; - type FullIdentification = (u64, Exposure); - - fn prove(_who: u64) -> Option { - Some(Vec::new()) - } - - fn check_proof(who: u64, _proof: Self::Proof) -> Option { - if let Some(exp) = EXPOSURES.with(|x| x.borrow().get(&who).cloned()) { - Some((who, exp)) - } else { - None - } - } -} - -pub struct BabeEquivocationReporter(PhantomData); - -impl BabeEquivocationReporter { - - /// Report an equivocation - pub fn report_equivocation( - proof: FakeProof< - T::Hash, - <::KeyOwner as KeyOwnerProofSystem>::Proof, - T::AccountId - >, - reporters: ::Reporter, - ) -> Result<(), ()> { - let identification = match T::KeyOwner::check_proof(proof.author.clone(), proof.membership_proof) { - Some(id) => id, - None => return Err(()), - }; - - // ignore equivocation slot for this test - let nonce = H256::random(); - let footprint = T::Hashing::hash_of(&(0xbabe, proof.author, nonce)); - - T::BabeEquivocation::slash(footprint, reporters, identification) - } -} - -// think of this a `decl_mod!` which have some events -// such as `Misbehavior(Self::kind())`, -pub struct BabeEquivocation(PhantomData<(T, DS, AS)>); - -impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); -impl_kind!(BabeEquivocation, Kind: Kind::One); - -impl BabeEquivocation { - pub fn as_misconduct_level(severity: Perbill) -> u8 { - if severity > Perbill::from_percent(10) { - 4 - } else if severity > Perbill::from_percent(1) { - 3 - } else if severity > Perbill::from_rational_approximation(1_u32, 1000_u32) { - 2 - } else { - 1 - } - } -} - -impl ReportSlash for BabeEquivocation -where - Who: Clone, - T: ReporterTrait, - DS: DoSlash, - AS: AfterSlash, -{ - fn slash(footprint: T::Hash, reporter: Reporter, who: Who) -> Result<(), ()> { - let kind = Self::kind(); - let _base_seve = Self::base_severity(); - - RollingWindow::::report_misbehavior(kind, footprint, 0)?; - let num_violations = RollingWindow::::get_misbehaviors(kind); - - // number of validators - let n = 50; - - // example how to estimate severity - // 3k / n^2 - let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); - let misconduct_level = Self::as_misconduct_level(severity); - println!("num_violations: {:?}, misconduct_level: {:?}", num_violations, misconduct_level); - - DS::do_slash(who.clone(), reporter, severity, kind)?; - AS::after_slash(misconduct_level, who); - - Ok(()) - } -} - -#[test] -fn simple_with_after_slash() { - with_externalities(&mut ExtBuilder::default() - .build(), - || { - let who = 11; - let controller = 10; - let nom = 101; - let exp = Staking::stakers(who); - - // who is stash and controller is controller - assert_eq!(Staking::bonded(&who), Some(controller)); - assert!(>::exists(&who)); - assert_eq!( - exp, - Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: nom, value: 250 }] } - ); - - EXPOSURES.with(|x| { - x.borrow_mut().insert(who, exp) - }); - - let nominator_balance = Balances::free_balance(&nom); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(who), vec![])); - assert_eq!(Balances::free_balance(&who), 999, "slash 0.12%"); - assert_eq!(Balances::free_balance(&nom), nominator_balance, "0.12% of 250 is zero, don't slash anythin"); - - assert!(is_disabled(controller), "severity higher than 0.1%"); - assert!(!>::exists(&who), "remove from the `wannabe` validators"); - - // increase severity to level 3 - for _ in 0..10 { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(who), vec![])); - } - - for v in Staking::nominators(nom) { - assert!(v != who, "slashed validators on level 3 should not be trusted"); - } - - assert_eq!(Staking::stakers(who).total, 0); - }); -} - -#[test] -fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { - let misbehaved: Vec = (0..10).collect(); - - with_externalities(&mut ExtBuilder::default() - .build(), - || { - EXPOSURES.with(|x| { - for who in &misbehaved { - let exp = Exposure { - own: 1000, - total: 1000, - others: Vec::new(), - }; - let _ = Balances::make_free_balance_be(who, 1000); - x.borrow_mut().insert(*who, exp); - } - }); - - for who in &misbehaved { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); - } - - for who in &misbehaved { - assert_eq!(Balances::free_balance(who), 988, "should slash 1.2%"); - } - }); -} - -#[test] -fn slash_with_nominators_simple() { - let misbehaved = 1; - - let nom_1 = 11; - let nom_2 = 12; - - with_externalities(&mut ExtBuilder::default() - .build(), - || { - let _ = Balances::make_free_balance_be(&nom_1, 10_000); - let _ = Balances::make_free_balance_be(&nom_2, 50_000); - let _ = Balances::make_free_balance_be(&misbehaved, 9_000); - assert_eq!(Balances::free_balance(&misbehaved), 9_000); - assert_eq!(Balances::free_balance(&nom_1), 10_000); - assert_eq!(Balances::free_balance(&nom_2), 50_000); - - EXPOSURES.with(|x| { - let exp = Exposure { - own: 9_000, - total: 11_200, - others: vec![ - IndividualExposure { who: nom_1, value: 1500 }, - IndividualExposure { who: nom_2, value: 700 }, - ], - }; - x.borrow_mut().insert(misbehaved, exp); - }); - - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); - - assert_eq!(Balances::free_balance(&misbehaved), 8_990, "should slash 0.12%"); - assert_eq!(Balances::free_balance(&nom_1), 9_999, "should slash 0.12% of exposure not total balance"); - assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.12% of exposure not total balance"); - }); -} - -#[test] -fn slash_should_keep_state_and_increase_slash_for_history_with_nominators() { - let misbehaved: Vec = (0..3).collect(); - - let nom_1 = 11; - let nom_2 = 12; - - with_externalities(&mut ExtBuilder::default() - .build(), - || { - let _ = Balances::make_free_balance_be(&nom_1, 10_000); - let _ = Balances::make_free_balance_be(&nom_2, 50_000); - - EXPOSURES.with(|x| { - for &who in &misbehaved { - let exp = Exposure { - own: 1000, - total: 1500, - others: vec![ - IndividualExposure { who: nom_1, value: 300 }, - IndividualExposure { who: nom_2, value: 200 }, - ], - }; - let _ = Balances::make_free_balance_be(&who, 1000); - x.borrow_mut().insert(who, exp); - } - }); - - for who in &misbehaved { - assert_eq!(Balances::free_balance(who), 1000); - } - - for who in &misbehaved { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); - } - - for who in &misbehaved { - assert_eq!(Balances::free_balance(who), 997, "should slash 0.36%"); - } - // (300 * 0.0036) * 3 = 3 - assert_eq!(Balances::free_balance(&nom_1), 9_997, "should slash 0.36%"); - // (200 * 0.0036) * 3 = 0 - assert_eq!(Balances::free_balance(&nom_2), 50_000, "should slash 0.36%"); - }); -} - -#[test] -fn slash_update_exposure_when_same_validator_gets_slashed_twice() { - let misbehaved = 0; - - let nom_1 = 11; - let nom_2 = 12; - let nom_3 = 13; - - with_externalities(&mut ExtBuilder::default() - .build(), - || { - let _ = Balances::make_free_balance_be(&nom_1, 10_000); - let _ = Balances::make_free_balance_be(&nom_2, 50_000); - let _ = Balances::make_free_balance_be(&nom_3, 5_000); - let _ = Balances::make_free_balance_be(&misbehaved, 1000); - - - let exp1 = Exposure { - own: 1_000, - total: 31_000, - others: vec![ - IndividualExposure { who: nom_1, value: 5_000 }, - IndividualExposure { who: nom_2, value: 25_000 }, - ], - }; - - EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp1)); - - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); - - assert_eq!(Balances::free_balance(&misbehaved), 999, "should slash 0.12%"); - assert_eq!(Balances::free_balance(&nom_1), 9_994, "should slash 0.12%"); - assert_eq!(Balances::free_balance(&nom_2), 49_970, "should slash 0.12%"); - assert_eq!(Balances::free_balance(&nom_3), 5_000, "not exposed should not be slashed"); - - let exp2 = Exposure { - own: 999, - total: 16098, - others: vec![ - IndividualExposure { who: nom_1, value: 10_000 }, - IndividualExposure { who: nom_2, value: 100 }, - IndividualExposure { who: nom_3, value: 4_999 }, - ], - }; - - // change exposure for `misbehaved - EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp2)); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); - - // exposure is 999 so slashed based on that amount but revert previous slash - // -> 999 * 0.0024 = 2, -> 1000 - 2 = 998 - assert_eq!(Balances::free_balance(&misbehaved), 998, "should slash 0.24%"); - assert_eq!(Balances::free_balance(&nom_1), 9_976, "should slash 0.24%"); - assert_eq!(Balances::free_balance(&nom_2), 49_970, "exposed but slash is smaller previous is still valid"); - // exposure is 4999, slash 0.0024 * 4999 -> 11 - // 5000 - 11 = 4989 - assert_eq!(Balances::free_balance(&nom_3), 4_989, "should slash 0.24%"); - }); -} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 8fe6c0184fc58..1cd9f305ac792 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -652,8 +652,15 @@ pub trait ReportSlash { /// A generic trait for enacting slashes. pub trait DoSlash { + /// The slashed entries which may include history of previous slashes + type Slashed; + /// Performs the actual slashing and rewarding based on severity - fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity, kind: Kind) -> Result<(), ()>; + /// + /// Return the slashes entities which may not be the same as `to_slash` + /// because history slashes of the same kind may be included + fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity, kind: Kind) + -> Result; } /// A generic event handler trait after slashing occured From fa2f9c1d712a8d8fd3e5798a356bc8b62cc6d1f8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 1 Aug 2019 00:19:47 +0200 Subject: [PATCH 60/66] fix nits --- node/runtime/src/lib.rs | 4 ++++ srml/rolling-window/src/mock.rs | 1 - srml/staking/src/lib.rs | 18 +----------------- srml/staking/src/slash.rs | 10 ++++------ 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index a6e1ab6ef2478..179a990f72ddd 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -237,6 +237,10 @@ impl staking::Trait for Runtime { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; +} + +impl staking::slash::Trait for Runtime { + type Currency = Balances; type SlashKind = Misbehavior; } diff --git a/srml/rolling-window/src/mock.rs b/srml/rolling-window/src/mock.rs index f6d99aedb34f8..c8b48b491f9c0 100644 --- a/srml/rolling-window/src/mock.rs +++ b/srml/rolling-window/src/mock.rs @@ -121,7 +121,6 @@ impl srml_staking::Trait for Test { type BondingDuration = BondingDuration; type SessionInterface = Self; type Time = timestamp::Module; - type SlashKind = Kind; } impl srml_session::Trait for Test { diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index ecc138cc4b879..d0c1eb5d0e2ba 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -277,7 +277,7 @@ mod tests; mod phragmen; mod inflation; -mod slash; +pub mod slash; #[cfg(all(feature = "bench", test))] mod benches; @@ -454,22 +454,6 @@ pub struct Exposure { pub others: Vec>, } -/// State of a slashed entity -#[derive(Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct SlashState { - exposure: Exposure, - slashed_amount: SlashAmount, -} - -/// Slashed amount for a entity including its nominators -#[derive(Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct SlashAmount { - own: Balance, - others: BTreeMap, -} - pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type PositiveImbalanceOf = diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs index f8906c5745fc8..6d9568e57c56c 100644 --- a/srml/staking/src/slash.rs +++ b/srml/staking/src/slash.rs @@ -15,10 +15,8 @@ // along with Substrate. If not, see . //! Slashing mod - -// Ensure we're `no_std` when compiling for Wasm. -#![cfg_attr(not(feature = "std"), no_std)] -#![warn(missing_docs, rust_2018_idioms)] +//! +//! This is currently located in `Staking` because it has dependency to `Exposure` use crate::Exposure; use srml_support::{ @@ -26,7 +24,7 @@ use srml_support::{ traits::{Currency, WindowLength, DoSlash} }; use parity_codec::{HasCompact, Codec, Decode, Encode}; -use rstd::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +use rstd::{vec::Vec, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; use sr_primitives::{Perbill, traits::MaybeSerializeDebug}; type BalanceOf = @@ -99,7 +97,7 @@ impl Module { .map(|e| e.who.clone()) .collect(); - let previous_slash = std::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); + let previous_slash = rstd::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); for nominator in &exposure.others { let new_slash = severity * nominator.value; From 332626a317e24c10f402bbbce112af8b404adec3 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 1 Aug 2019 12:29:02 +0200 Subject: [PATCH 61/66] feat: split rewarding and slashing This commit splits the `DoSlash` trait in two traits: `DoReward` and `DoSlash` The reasons for it, is that we want reward based on the `total slashed amount` but the entire amount doesn't necessarily be rewarded. Some part of the total slashed amount might need to transmitted to some other part such as `treasory`. Thus, instead we return the total slash and `ReportSlashing` has to determine what to do with the total slashed amount. In other words, the concrete misconduct type determines what to do. --- srml/staking/src/slash.rs | 127 ++++++++++++++++++++++++------------- srml/support/src/traits.rs | 23 +++++-- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs index 6d9568e57c56c..7c4bcb7c3a2da 100644 --- a/srml/staking/src/slash.rs +++ b/srml/staking/src/slash.rs @@ -21,11 +21,11 @@ use crate::Exposure; use srml_support::{ StorageMap, decl_module, decl_storage, - traits::{Currency, WindowLength, DoSlash} + traits::{Currency, WindowLength, DoSlash, DoReward} }; use parity_codec::{HasCompact, Codec, Decode, Encode}; use rstd::{vec::Vec, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; -use sr_primitives::{Perbill, traits::MaybeSerializeDebug}; +use sr_primitives::{Perbill, traits::{MaybeSerializeDebug, Zero}}; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -72,9 +72,16 @@ impl Module { /// Tries to adjust the `slash` based on `new_slash` and `prev_slash` /// /// Returns the total slashed amount - fn adjust_slash(who: &T::AccountId, new_slash: BalanceOf, prev_slash: BalanceOf) -> BalanceOf { + fn adjust_slash( + who: &T::AccountId, + new_slash: BalanceOf, + prev_slash: BalanceOf, + slashed_amount: &mut BalanceOf, + ) -> BalanceOf { if new_slash > prev_slash { - T::Currency::slash(who, new_slash - prev_slash); + let amount = new_slash - prev_slash; + T::Currency::slash(who, amount); + *slashed_amount = *slashed_amount + amount; new_slash } else { prev_slash @@ -85,11 +92,12 @@ impl Module { who: &T::AccountId, prev_state: &mut SlashState>, exposure: &Exposure>, - severity: Perbill + severity: Perbill, + slashed_amount: &mut BalanceOf, ) { let new_slash = severity * exposure.own; - T::Currency::slash(&who, new_slash - prev_state.slashed_amount.own); + Self::adjust_slash(&who, new_slash, prev_state.slashed_amount.own, slashed_amount); let intersection: BTreeSet = exposure.others .iter() @@ -103,12 +111,13 @@ impl Module { let new_slash = severity * nominator.value; // make sure that we are not double slashing - if intersection.contains(&nominator.who) { - previous_slash.get(&nominator.who).map(|prev| Self::adjust_slash(&nominator.who, new_slash, *prev)); + let prev = if intersection.contains(&nominator.who) { + previous_slash.get(&nominator.who).cloned().unwrap_or_else(|| Zero::zero()) } else { - T::Currency::slash(&nominator.who, new_slash); + Zero::zero() }; + Self::adjust_slash(&nominator.who, new_slash, prev, slashed_amount); prev_state.slashed_amount.others.insert(nominator.who.clone(), new_slash); } @@ -124,7 +133,8 @@ impl Module { who: &T::AccountId, exposure: &Exposure>, severity: Perbill, - kind: T::SlashKind + kind: T::SlashKind, + total_slash: &mut BalanceOf, ) -> bool { >::mutate(kind, |state| { @@ -132,18 +142,23 @@ impl Module { for (stash, s) in state.iter_mut() { if stash == who { - Self::update_exposure_for_known(stash, s, exposure, severity); + Self::update_exposure_for_known(stash, s, exposure, severity, total_slash); in_history = true; } else { - s.slashed_amount.own = Self::adjust_slash(stash, severity * s.exposure.own, s.slashed_amount.own); + s.slashed_amount.own = Self::adjust_slash( + stash, + severity * s.exposure.own, + s.slashed_amount.own, + total_slash + ); for nominator in &s.exposure.others { let new_slash = severity * nominator.value; - if let Some(prev_slash) = s.slashed_amount.others.get_mut(&nominator.who) { - *prev_slash = Self::adjust_slash(&nominator.who, new_slash, *prev_slash); + if let Some(prev) = s.slashed_amount.others.get_mut(&nominator.who) { + *prev = Self::adjust_slash(&nominator.who, new_slash, *prev, total_slash); } else { + Self::adjust_slash(&nominator.who, new_slash, Zero::zero(), total_slash); s.slashed_amount.others.insert(nominator.who.clone(), new_slash); - T::Currency::slash(&nominator.who, new_slash); } } } @@ -154,37 +169,28 @@ impl Module { } } -impl - DoSlash< - (T::AccountId, Exposure>), - Reporters, - Perbill, - T::SlashKind - > for Module -where - Reporters: IntoIterator, +impl DoSlash<(T::AccountId, Exposure>), Perbill, T::SlashKind> for Module { - type Slashed = Vec<(T::AccountId, Exposure>)>; + type SlashedEntries = Vec<(T::AccountId, Exposure>)>; + type SlashedAmount = BalanceOf; - // TODO: #3166 pay out rewards to the reporters - // Perbill is priority for the reporter fn do_slash( (who, exposure): (T::AccountId, Exposure>), - _reporters: Reporters, severity: Perbill, kind: T::SlashKind, - ) -> Result { + ) -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()> { - let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind); + let mut total_slash = Zero::zero(); + let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind, &mut total_slash); if !who_exist { let amount = severity * exposure.own; - T::Currency::slash(&who, amount); + Self::adjust_slash(&who, amount, Zero::zero(), &mut total_slash); let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; for nominator in &exposure.others { let amount = severity * nominator.value; - T::Currency::slash(&nominator.who, amount); + Self::adjust_slash(&nominator.who, amount, Zero::zero(), &mut total_slash); slashed_amount.others.insert(nominator.who.clone(), amount); } @@ -193,12 +199,25 @@ where }); } - let slashed = >::get(kind) + let slashed_entities = >::get(kind) .iter() .map(|(who, state)| (who.clone(), state.exposure.clone())) .collect(); - Ok(slashed) + Ok((slashed_entities, total_slash)) + } +} + +impl DoReward> for Module +where + Reporters: IntoIterator, +{ + fn do_reward(reporters: Reporters, reward: BalanceOf) -> Result<(), ()> { + for (reporter, fraction) in reporters { + // This will fail in the account is not existing ignore it for now + let _ = T::Currency::deposit_into_existing(&reporter, fraction * reward); + } + Ok(()) } } @@ -249,7 +268,9 @@ mod tests { impl ReporterTrait for Test { type KeyOwner = FakeProver; - type BabeEquivocation = BabeEquivocation, crate::AfterSlashing>; + type BabeEquivocation = BabeEquivocation< + Self, SlashingModule, SlashingModule, crate::AfterSlashing + >; type Reporter = Vec<(u64, Perbill)>; } @@ -317,12 +338,16 @@ mod tests { } } - pub struct BabeEquivocation(PhantomData<(T, DS, AS)>); + /// This should be something similar to `decl_module!` macro + /// + /// It would probably be nice to have `DoSlash`, `DoReward` and `AfterSlash` + /// as associated types instead + pub struct BabeEquivocation(PhantomData<(T, DS, DR, AS)>); - impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); - impl_kind!(BabeEquivocation, Kind: Kind::One); + impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); + impl_kind!(BabeEquivocation, Kind: Kind::One); - impl BabeEquivocation { + impl BabeEquivocation { pub fn as_misconduct_level(severity: Perbill) -> u8 { if severity > Perbill::from_percent(10) { 4 @@ -336,14 +361,15 @@ mod tests { } } - impl ReportSlash for BabeEquivocation + impl ReportSlash for BabeEquivocation where Who: Clone, T: ReporterTrait, - DS: DoSlash, - AS: AfterSlash, + DS: DoSlash, + DR: DoReward, + AS: AfterSlash, { - fn slash(footprint: T::Hash, reporter: Reporter, who: Who) -> Result<(), ()> { + fn slash(footprint: T::Hash, reporters: Reporters, who: Who) -> Result<(), ()> { let kind = Self::kind(); let _base_seve = Self::base_severity(); @@ -356,8 +382,19 @@ mod tests { // example how to estimate severity // 3k / n^2 let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); - let slashed = DS::do_slash(who, reporter, severity, kind)?; - AS::after_slash(slashed, Self::as_misconduct_level(severity)); + + let misconduct_level = Self::as_misconduct_level(severity); + let (slashed, total_slash) = DS::do_slash(who, severity, kind)?; + + // hard code to reward 10% of the total amount + // different misconducts might want to handle rewarding differently... + let reward_amount = Perbill::from_percent(10) * total_slash; + + // the remaining 90% should go somewhere else, perhaps the `treasory module`?! + + // ignore if rewarding failed, because we need still to update the state of the validators + let _ = DR::do_reward(reporters, reward_amount); + AS::after_slash(slashed, misconduct_level); Ok(()) } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 1cd9f305ac792..a1c891b9b0ce9 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -650,17 +650,26 @@ pub trait ReportSlash { fn slash(to_slash: Misbehaved, to_reward: Reporters, footprint: Hash) -> Result<(), ()>; } -/// A generic trait for enacting slashes. -pub trait DoSlash { - /// The slashed entries which may include history of previous slashes - type Slashed; +/// A generic trait for enacting slashes +pub trait DoSlash { + /// The slashed entries + type SlashedEntries; - /// Performs the actual slashing and rewarding based on severity + /// The total slashed amount + type SlashedAmount: Copy + Clone + Codec + SimpleArithmetic; + + /// Performs the actual slashing based on severity /// /// Return the slashes entities which may not be the same as `to_slash` /// because history slashes of the same kind may be included - fn do_slash(to_slash: Misbehaved, to_reward: Reporters, severity: Severity, kind: Kind) - -> Result; + fn do_slash(to_slash: Misbehaved, severity: Severity, kind: Kind) + -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()>; +} + +/// A generic trait for paying out rewards +pub trait DoReward { + /// Payout reward to reporters + fn do_reward(reporters: Reporters, total_reward: Reward) -> Result<(), ()>; } /// A generic event handler trait after slashing occured From 28311fa6490f9a3ef26549b8921f6bc3d76218d7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 1 Aug 2019 19:36:44 +0200 Subject: [PATCH 62/66] add reward tests --- srml/staking/src/slash.rs | 68 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs index e6a54ebc2c2f2..b6186eaabca6e 100644 --- a/srml/staking/src/slash.rs +++ b/srml/staking/src/slash.rs @@ -213,9 +213,13 @@ where Reporters: IntoIterator, { fn do_reward(reporters: Reporters, reward: BalanceOf) -> Result<(), ()> { + let mut reward_pot = reward; + for (reporter, fraction) in reporters { + let amount = rstd::cmp::min(fraction * reward, reward_pot); + reward_pot -= amount; // This will fail in the account is not existing ignore it for now - let _ = T::Currency::deposit_into_existing(&reporter, fraction * reward); + let _ = T::Currency::deposit_into_existing(&reporter, amount); } Ok(()) } @@ -364,6 +368,7 @@ mod tests { DS: DoSlash, DR: DoReward, AS: AfterSlash, + DS::SlashedAmount: rstd::fmt::Debug, { fn slash(footprint: T::Hash, reporters: Reporters, who: Who) -> Result<(), ()> { let kind = Self::kind(); @@ -398,10 +403,12 @@ mod tests { #[test] fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { let misbehaved: Vec = (0..10).collect(); + let reporter = (99_u64, Perbill::one()); with_externalities(&mut ExtBuilder::default() .build(), || { + let _ = Balances::make_free_balance_be(&reporter.0, 50); EXPOSURES.with(|x| { for who in &misbehaved { let exp = Exposure { @@ -414,13 +421,26 @@ mod tests { } }); - for who in &misbehaved { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + + let mut last_slash = 0; + let mut last_balance = 50; + + // after every slash, the slash history and slash that occurred should be included in the reward + for (i, who) in misbehaved.iter().enumerate() { + let i = i as u64; + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![reporter])); + let slash = Perbill::from_rational_approximation(3 * (i + 1), 2500_u64) * 1000; + let total_slash = slash + (slash - last_slash) * i; + let reward = Perbill::from_percent(10) * total_slash; + assert_eq!(Balances::free_balance(&reporter.0), last_balance + reward); + last_balance = Balances::free_balance(&reporter.0); + last_slash = slash; } for who in &misbehaved { assert_eq!(Balances::free_balance(who), 988, "should slash 1.2%"); } + }); } @@ -567,6 +587,7 @@ mod tests { }); } + // note, this test hooks in the `staking` and uses its `AfterSlash` implementation #[test] fn simple_with_after_slash() { with_externalities(&mut ExtBuilder::default() @@ -643,4 +664,45 @@ mod tests { assert_eq!(Staking::stakers(m2).total, 0); }); } + + #[test] + fn rewarding() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + let m = 0; + let balance = u32::max_value() as u64; + let _ = Balances::make_free_balance_be(&m, balance); + + EXPOSURES.with(|x| x.borrow_mut().insert(m, Exposure { + own: balance, + total: balance, + others: vec![], + })); + + let reporters = vec![ + (1, Perbill::from_percent(50)), + (2, Perbill::from_percent(20)), + (3, Perbill::from_percent(15)), + (4, Perbill::from_percent(10)), + (5, Perbill::from_percent(50)), + ]; + + // reset balance to 1 for the reporter + for who in 1..=5 { + let _ = Balances::make_free_balance_be(&who, 1); + } + + // slashed amount: 5153960 (0,0132 * 4294967295) will be slashed + // 515396 (0.1 * 5153961) will be shared among the reporters + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m), reporters)); + + assert_eq!(Balances::free_balance(&1), 257698 + 1); + assert_eq!(Balances::free_balance(&2), 103079 + 1); + assert_eq!(Balances::free_balance(&3), 77309 + 1); + assert_eq!(Balances::free_balance(&4), 51539 + 1); + assert_eq!(Balances::free_balance(&5), 25771 + 1, "should only get what's left in the pot; 5% not 50%"); + }); + } } From e1541bf13fe29e3c46627fa45090e0dc144eccbf Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 2 Aug 2019 19:48:18 +0200 Subject: [PATCH 63/66] feat: slash multiple misconducts 100% --- srml/staking/src/mock.rs | 6 + srml/staking/src/slash.rs | 423 +++++++++++++++++++++++++++---------- srml/support/src/traits.rs | 12 +- 3 files changed, 324 insertions(+), 117 deletions(-) diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 876b44377b2f7..c92985a758a32 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -222,6 +222,12 @@ impl WindowLength for Kind { } } +impl Default for Kind { + fn default() -> Self { + Kind::One + } +} + pub struct ExtBuilder { existential_deposit: u64, validator_pool: bool, diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs index b6186eaabca6e..8c90e33995eca 100644 --- a/srml/staking/src/slash.rs +++ b/srml/staking/src/slash.rs @@ -20,45 +20,77 @@ use crate::Exposure; use srml_support::{ - StorageMap, decl_module, decl_storage, + EnumerableStorageMap, StorageMap, decl_module, decl_storage, traits::{Currency, WindowLength, DoSlash, DoReward} }; use parity_codec::{HasCompact, Codec, Decode, Encode}; use rstd::{vec::Vec, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; use sr_primitives::{Perbill, traits::{MaybeSerializeDebug, Zero}}; +type Timestamp = u128; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; /// Slashing trait pub trait Trait: system::Trait { /// Slashing kind - type SlashKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; + type SlashKind: Copy + Clone + Codec + Default + PartialEq + MaybeSerializeDebug + WindowLength; /// Currency type Currency: Currency; } -/// State of a slashed entity -#[derive(Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct SlashState { - exposure: Exposure, - slashed_amount: SlashAmount, -} - /// Slashed amount for a entity including its nominators -#[derive(Encode, Decode)] +#[derive(Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct SlashAmount { +pub struct SlashAmount +where + AccountId: Default + Ord, + Balance: Default + HasCompact, +{ own: Balance, others: BTreeMap, } +/// A misconduct kind with timestamp when it occurred +#[derive(Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct MisconductsByTime(Vec<(Timestamp, Kind)>); + +impl MisconductsByTime { + fn contains_kind(&self, kind: Kind) -> bool { + self.0.iter().any(|(_, k)| *k == kind) + } + + fn multiple_misbehaviors_at_same_time(&self, time: Timestamp, kind: Kind) -> bool { + self.0.iter().any(|(t, k)| *t == time && *k != kind) + } +} + +/// State of a validator +#[derive(Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct ValidatorState +where + AccountId: Default + Ord, + Balance: Default + HasCompact, + Kind: Default, +{ + /// The misconducts the validator has conducted + // TODO: replace this with BTreeSet sorted ordered by latest timestamp...smallest + misconducts: MisconductsByTime, + /// The rewards that the validator has received + rewards: Vec<(Timestamp, Balance)>, + /// Its own balance and the weight of the nominators that supports the validator + exposure: Exposure, + /// The slashed amounts both for the validator and its nominators + slashed_amount: SlashAmount, +} + decl_storage! { trait Store for Module as RollingWindow { - /// Slashing history - SlashHistory get(misbehavior_reports): linked_map T::SlashKind => - BTreeMap>>; + /// Slashing history for a given validator + SlashHistory get(misbehavior_reports): linked_map T::AccountId => + ValidatorState, T::SlashKind>; } } @@ -80,7 +112,7 @@ impl Module { ) -> BalanceOf { if new_slash > prev_slash { let amount = new_slash - prev_slash; - T::Currency::slash(who, amount); + T::Currency::slash(&who, amount); *slashed_amount = *slashed_amount + amount; new_slash } else { @@ -88,41 +120,74 @@ impl Module { } } - fn update_exposure_for_known( + /// Updates the state of an existing validator which implies updating exposure and + /// update the slashable amount + fn update_known_validator( who: &T::AccountId, - prev_state: &mut SlashState>, - exposure: &Exposure>, + exposure: Exposure>, severity: Perbill, - slashed_amount: &mut BalanceOf, + kind: T::SlashKind, + timestamp: u128, + total_slash: &mut BalanceOf, ) { - let new_slash = severity * exposure.own; + >::mutate(who, |mut state| { + let new_slash = severity * exposure.own; - Self::adjust_slash(&who, new_slash, prev_state.slashed_amount.own, slashed_amount); + Self::adjust_slash(who, new_slash, state.slashed_amount.own, total_slash); - let intersection: BTreeSet = exposure.others - .iter() - .filter_map(|e1| prev_state.exposure.others.iter().find(|e2| e1.who == e2.who)) - .map(|e| e.who.clone()) - .collect(); + let intersection: BTreeSet = exposure.others + .iter() + .filter_map(|e1| state.exposure.others.iter().find(|e2| e1.who == e2.who)) + .map(|e| e.who.clone()) + .collect(); - let previous_slash = rstd::mem::replace(&mut prev_state.slashed_amount.others, BTreeMap::new()); + let previous_slash = rstd::mem::replace(&mut state.slashed_amount.others, BTreeMap::new()); - for nominator in &exposure.others { - let new_slash = severity * nominator.value; + for nominator in &exposure.others { + let new_slash = severity * nominator.value; - // make sure that we are not double slashing - let prev = if intersection.contains(&nominator.who) { - previous_slash.get(&nominator.who).cloned().unwrap_or_else(|| Zero::zero()) - } else { - Zero::zero() - }; + // make sure that we are not double slashing + let prev = if intersection.contains(&nominator.who) { + previous_slash.get(&nominator.who).cloned().unwrap_or_else(Zero::zero) + } else { + Zero::zero() + }; + + Self::adjust_slash(&nominator.who, new_slash, prev, total_slash); + state.slashed_amount.others.insert(nominator.who.clone(), new_slash); + } + + state.misconducts.0.push((timestamp, kind)); + state.exposure = exposure; + state.slashed_amount.own = new_slash; + }); + } - Self::adjust_slash(&nominator.who, new_slash, prev, slashed_amount); - prev_state.slashed_amount.others.insert(nominator.who.clone(), new_slash); + /// Inserts a new validator in the slashing history and applies the slash + fn insert_new_validator( + who: T::AccountId, + exposure: Exposure>, + severity: Perbill, + kind: T::SlashKind, + timestamp: u128, + total_slash: &mut BalanceOf, + ) { + let amount = severity * exposure.own; + Self::adjust_slash(&who, amount, Zero::zero(), total_slash); + let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; + + for nominator in &exposure.others { + let amount = severity * nominator.value; + Self::adjust_slash(&nominator.who, amount, Zero::zero(), total_slash); + slashed_amount.others.insert(nominator.who.clone(), amount); } - prev_state.exposure = exposure.clone(); - prev_state.slashed_amount.own = new_slash; + >::insert(who, ValidatorState { + misconducts: MisconductsByTime(vec![(timestamp, kind)]), + rewards: Vec::new(), + exposure: exposure, + slashed_amount, + }); } /// Updates the history of slashes based on the new severity and only apply new slash @@ -134,42 +199,44 @@ impl Module { exposure: &Exposure>, severity: Perbill, kind: T::SlashKind, + slashed_entries: &mut Vec<(T::AccountId, Exposure>)>, total_slash: &mut BalanceOf, ) -> bool { - >::mutate(kind, |state| { - - let mut in_history = false; - - for (stash, s) in state.iter_mut() { - if stash == who { - Self::update_exposure_for_known(stash, s, exposure, severity, total_slash); - in_history = true; - } else { - s.slashed_amount.own = Self::adjust_slash( - stash, - severity * s.exposure.own, - s.slashed_amount.own, - total_slash - ); - - for nominator in &s.exposure.others { - let new_slash = severity * nominator.value; - if let Some(prev) = s.slashed_amount.others.get_mut(&nominator.who) { - *prev = Self::adjust_slash(&nominator.who, new_slash, *prev, total_slash); - } else { - Self::adjust_slash(&nominator.who, new_slash, Zero::zero(), total_slash); - s.slashed_amount.others.insert(nominator.who.clone(), new_slash); + let mut in_history = false; + + for (other_who, _) in >::enumerate() { + >::mutate(&other_who, |mut state| { + if state.misconducts.contains_kind(kind) { + if &other_who == who { + in_history = true; + } else { + slashed_entries.push((other_who.clone(), exposure.clone())); + state.slashed_amount.own = Self::adjust_slash( + &other_who, + severity * state.exposure.own, + state.slashed_amount.own, + total_slash + ); + + for nominator in &state.exposure.others { + let new_slash = severity * nominator.value; + if let Some(prev) = state.slashed_amount.others.get_mut(&nominator.who) { + *prev = Self::adjust_slash(&nominator.who, new_slash, *prev, total_slash); + } else { + Self::adjust_slash(&nominator.who, new_slash, Zero::zero(), total_slash); + state.slashed_amount.others.insert(nominator.who.clone(), new_slash); + } } } } - } + }); + } - in_history - }) + in_history } } -impl DoSlash<(T::AccountId, Exposure>), Perbill, T::SlashKind> for Module +impl DoSlash<(T::AccountId, Exposure>), Perbill, T::SlashKind, u128> for Module { type SlashedEntries = Vec<(T::AccountId, Exposure>)>; type SlashedAmount = BalanceOf; @@ -178,48 +245,54 @@ impl DoSlash<(T::AccountId, Exposure>), Per (who, exposure): (T::AccountId, Exposure>), severity: Perbill, kind: T::SlashKind, + timestamp: u128, ) -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()> { + // mutable state + let mut slashed_entries: Vec<(T::AccountId, Exposure>)> = Vec::new(); let mut total_slash = Zero::zero(); - let who_exist = >::mutate_slash_history(&who, &exposure, severity, kind, &mut total_slash); - if !who_exist { - let amount = severity * exposure.own; - Self::adjust_slash(&who, amount, Zero::zero(), &mut total_slash); - let mut slashed_amount = SlashAmount { own: amount, others: BTreeMap::new() }; - - for nominator in &exposure.others { - let amount = severity * nominator.value; - Self::adjust_slash(&nominator.who, amount, Zero::zero(), &mut total_slash); - slashed_amount.others.insert(nominator.who.clone(), amount); - } + let who_exist = >::mutate_slash_history( + &who, + &exposure, + severity, + kind, + &mut slashed_entries, + &mut total_slash, + ); + + let seve = if >::get(&who).misconducts.multiple_misbehaviors_at_same_time(timestamp, kind) { + Perbill::one() + } else { + severity + }; - >::mutate(kind, |state| { - state.insert(who, SlashState { exposure, slashed_amount }); - }); + if who_exist { + Self::update_known_validator(&who, exposure.clone(), seve, kind, timestamp, &mut total_slash); + } else { + Self::insert_new_validator(who.clone(), exposure.clone(), seve, kind, timestamp, &mut total_slash); } - let slashed_entities = >::get(kind) - .iter() - .map(|(who, state)| (who.clone(), state.exposure.clone())) - .collect(); - - Ok((slashed_entities, total_slash)) + slashed_entries.push((who, exposure)); + Ok((slashed_entries, total_slash)) } } -impl DoReward> for Module +impl DoReward, u128> for Module where Reporters: IntoIterator, { - fn do_reward(reporters: Reporters, reward: BalanceOf) -> Result<(), ()> { + fn do_reward(reporters: Reporters, reward: BalanceOf, timestamp: u128) -> Result<(), ()> { let mut reward_pot = reward; for (reporter, fraction) in reporters { let amount = rstd::cmp::min(fraction * reward, reward_pot); reward_pot -= amount; - // This will fail in the account is not existing ignore it for now - let _ = T::Currency::deposit_into_existing(&reporter, amount); + // This will fail if the account is not existing ignore it for now + if T::Currency::deposit_into_existing(&reporter, amount).is_ok() { + >::mutate(reporter, |state| state.rewards.push((timestamp, amount))); + } + } Ok(()) } @@ -249,6 +322,8 @@ mod tests { thread_local! { static EXPOSURES: RefCell>> = RefCell::new(Default::default()); + static CURRENT_TIME: RefCell = RefCell::new(0); + static CURRENT_KIND: RefCell = RefCell::new(Kind::One); } /// Trait for reporting slashes @@ -256,12 +331,15 @@ mod tests { /// Key that identifies the owner type KeyOwner: KeyOwnerProofSystem; + /// Report of the misconduct type Reporter; + /// Slash type BabeEquivocation: ReportSlash< Self::Hash, Self::Reporter, - <::KeyOwner as KeyOwnerProofSystem>::FullIdentification + <::KeyOwner as KeyOwnerProofSystem>::FullIdentification, + u128, >; } @@ -328,6 +406,7 @@ mod tests { T::AccountId >, reporters: ::Reporter, + timestamp: u128, ) -> Result<(), ()> { let identification = match T::KeyOwner::check_proof(proof.author.clone(), proof.membership_proof) { Some(id) => id, @@ -338,7 +417,7 @@ mod tests { let nonce = H256::random(); let footprint = T::Hashing::hash_of(&(0xbabe, proof.author, nonce)); - T::BabeEquivocation::slash(footprint, reporters, identification) + T::BabeEquivocation::slash(footprint, reporters, identification, timestamp) } } @@ -362,16 +441,17 @@ mod tests { } } - impl ReportSlash for BabeEquivocation + impl ReportSlash for BabeEquivocation where T: ReporterTrait, - DS: DoSlash, - DR: DoReward, + DS: DoSlash, + DR: DoReward, AS: AfterSlash, DS::SlashedAmount: rstd::fmt::Debug, + DS::SlashedEntries: rstd::fmt::Debug, { - fn slash(footprint: T::Hash, reporters: Reporters, who: Who) -> Result<(), ()> { - let kind = Self::kind(); + fn slash(footprint: T::Hash, reporters: Reporters, who: Who, timestamp: u128) -> Result<(), ()> { + let kind = get_current_misconduct_kind(); let _base_seve = Self::base_severity(); RollingWindow::::report_misbehavior(kind, footprint, 0)?; @@ -385,7 +465,7 @@ mod tests { let severity = Perbill::from_rational_approximation(3 * num_violations, n * n); let misconduct_level = Self::as_misconduct_level(severity); - let (slashed, total_slash) = DS::do_slash(who, severity, kind)?; + let (slashed, total_slash) = DS::do_slash(who, severity, kind, timestamp)?; // hard code reward to 10% of the total amount let reward_amount = Perbill::from_percent(10) * total_slash; @@ -393,13 +473,30 @@ mod tests { // the remaining 90% should go somewhere else, perhaps the `treasory module`?! // ignore if rewarding failed, because we need still to update the state of the validators - let _ = DR::do_reward(reporters, reward_amount); + let _ = DR::do_reward(reporters, reward_amount, timestamp); AS::after_slash(slashed, misconduct_level); Ok(()) } } + + fn get_current_time() -> u128 { + CURRENT_TIME.with(|t| *t.borrow()) + } + + fn increase_current_time() { + CURRENT_TIME.with(|t| *t.borrow_mut() += 1); + } + + fn get_current_misconduct_kind() -> Kind { + CURRENT_KIND.with(|t| *t.borrow()) + } + + fn set_current_misconduct_kind(kind: Kind) { + CURRENT_KIND.with(|t| *t.borrow_mut() = kind); + } + #[test] fn slash_should_keep_state_and_increase_slash_for_history_without_nominators() { let misbehaved: Vec = (0..10).collect(); @@ -428,13 +525,19 @@ mod tests { // after every slash, the slash history and slash that occurred should be included in the reward for (i, who) in misbehaved.iter().enumerate() { let i = i as u64; - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![reporter])); + assert_ok!(BabeEquivocationReporter::::report_equivocation( + FakeProof::new(*who), + vec![reporter], + get_current_time() + ) + ); let slash = Perbill::from_rational_approximation(3 * (i + 1), 2500_u64) * 1000; let total_slash = slash + (slash - last_slash) * i; let reward = Perbill::from_percent(10) * total_slash; assert_eq!(Balances::free_balance(&reporter.0), last_balance + reward); last_balance = Balances::free_balance(&reporter.0); last_slash = slash; + increase_current_time(); } for who in &misbehaved { @@ -473,7 +576,7 @@ mod tests { x.borrow_mut().insert(misbehaved, exp); }); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![], 0)); assert_eq!(Balances::free_balance(&misbehaved), 8_990, "should slash 0.12%"); assert_eq!(Balances::free_balance(&nom_1), 9_999, "should slash 0.12% of exposure not total balance"); @@ -514,7 +617,13 @@ mod tests { } for who in &misbehaved { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(*who), vec![])); + assert_ok!(BabeEquivocationReporter::::report_equivocation( + FakeProof::new(*who), + vec![], + get_current_time() + ) + ); + increase_current_time(); } for who in &misbehaved { @@ -555,7 +664,7 @@ mod tests { EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp1)); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![], 0)); assert_eq!(Balances::free_balance(&misbehaved), 999, "should slash 0.12%"); assert_eq!(Balances::free_balance(&nom_1), 9_994, "should slash 0.12%"); @@ -572,9 +681,9 @@ mod tests { ], }; - // change exposure for `misbehaved + // change exposure for `misbehaved` EXPOSURES.with(|x| x.borrow_mut().insert(misbehaved, exp2)); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![])); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(misbehaved), vec![], 1)); // exposure is 999 so slashed based on that amount but revert previous slash // -> 999 * 0.0024 = 2, -> 1000 - 2 = 998 @@ -587,7 +696,7 @@ mod tests { }); } - // note, this test hooks in the `staking` and uses its `AfterSlash` implementation + // note, this test hooks in to the `staking` and uses its `AfterSlash` implementation #[test] fn simple_with_after_slash() { with_externalities(&mut ExtBuilder::default() @@ -625,7 +734,9 @@ mod tests { x.borrow_mut().insert(m2, exp2) }); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![])); + assert_ok!( + BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], get_current_time()) + ); assert_eq!(Balances::free_balance(&m1), initial_balance_m1 - 1, "should slash 0.12% of 1000"); assert_eq!(Balances::free_balance(&m2), initial_balance_m2, "no misconducts yet; no slash"); assert_eq!(Balances::free_balance(&nom), initial_balance_nom, "0.12% of 250 is zero, don't slash anything"); @@ -635,7 +746,10 @@ mod tests { assert!(!is_disabled(c2), "m2 is not a misconducter; still available"); assert!(>::exists(&m2), "no misconducts yet; still a candidate"); - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![])); + increase_current_time(); + assert_ok!( + BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![], get_current_time()) + ); assert_eq!(Balances::free_balance(&m1), initial_balance_m1 - 2, "should slash 0.24% of 1000"); assert_eq!(Balances::free_balance(&m2), initial_balance_m2 - 2, "should slash 0.24% of 1000"); @@ -649,11 +763,19 @@ mod tests { // ensure m1 and m2 are still trusted by its nominator assert_eq!(Staking::nominators(nom).contains(&m1), true); assert_eq!(Staking::nominators(nom).contains(&m2), true); + increase_current_time(); // increase severity to level 3 // note, this only reports misconducts from `m2` but `m1` should be updated as well. for _ in 0..10 { - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![])); + assert_ok!( + BabeEquivocationReporter::::report_equivocation( + FakeProof::new(m2), + vec![], + get_current_time() + ) + ); + increase_current_time(); } // ensure m1 and m2 are not trusted by its nominator anymore @@ -696,7 +818,7 @@ mod tests { // slashed amount: 5153960 (0,0132 * 4294967295) will be slashed // 515396 (0.1 * 5153961) will be shared among the reporters - assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m), reporters)); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m), reporters, 0)); assert_eq!(Balances::free_balance(&1), 257698 + 1); assert_eq!(Balances::free_balance(&2), 103079 + 1); @@ -705,4 +827,83 @@ mod tests { assert_eq!(Balances::free_balance(&5), 25771 + 1, "should only get what's left in the pot; 5% not 50%"); }); } + + + #[test] + fn severity_is_based_on_kind() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + let exp = Exposure { + own: 1_000, + total: 1_000, + others: Vec::new(), + }; + + let m1 = 0; + let m2 = 1; + let _ = Balances::make_free_balance_be(&m1, 1000); + let _ = Balances::make_free_balance_be(&m2, 1000); + + EXPOSURES.with(|x| { + x.borrow_mut().insert(m1, exp.clone()); + x.borrow_mut().insert(m2, exp) + }); + + for t in 0..100 { + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], t)); + } + + assert_eq!(Balances::free_balance(&m1), 880, "should be slashed by 12%"); + assert_eq!(Balances::free_balance(&m2), 1000); + + set_current_misconduct_kind(Kind::Two); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], 3000)); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![], 3001)); + + assert_eq!(Balances::free_balance(&m1), 878, "should be slashed by severity on Kind::Two"); + assert_eq!(Balances::free_balance(&m2), 998); + }); + } + + #[test] + fn multiple_misbehaviors_at_the_same_time() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + + let exp = Exposure { + own: 1_000, + total: 1_000, + others: Vec::new(), + }; + + let m1 = 0; + let m2 = 1; + let _ = Balances::make_free_balance_be(&m1, 1000); + let _ = Balances::make_free_balance_be(&m2, 1000); + + EXPOSURES.with(|x| { + x.borrow_mut().insert(m1, exp.clone()); + x.borrow_mut().insert(m2, exp) + }); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], 0)); + + assert_eq!(Balances::free_balance(&m1), 999, "should be slashed by 0.12%"); + assert_eq!(Balances::free_balance(&m2), 1000); + + set_current_misconduct_kind(Kind::Two); + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![], 0)); + assert_eq!(Balances::free_balance(&m1), 999, "should not be slashed be slashed by Kind::Two"); + assert_eq!(Balances::free_balance(&m2), 999, "should be slashed by 0.12%"); + + assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], 0)); + + assert_eq!(Balances::free_balance(&m1), 0, "multiple misbehavior at the same time"); + assert_eq!(Balances::free_balance(&m2), 998, "should be slashed 0.24%"); + }); + } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index a1c891b9b0ce9..b50c5e89b0a25 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -642,16 +642,16 @@ impl ChangeMembers for () { } /// A generic trait for reporting slashing violations. -pub trait ReportSlash { +pub trait ReportSlash { /// Reports slashing for a misconduct where `to_slash` are the misbehaved entities and /// `to_reward` are the entities that detected and reported the misbehavior /// /// Returns `Ok` if the misconduct was unique otherwise `Err` - fn slash(to_slash: Misbehaved, to_reward: Reporters, footprint: Hash) -> Result<(), ()>; + fn slash(to_slash: Misbehaved, to_reward: Reporters, footprint: Hash, timestamp: Timestamp) -> Result<(), ()>; } /// A generic trait for enacting slashes -pub trait DoSlash { +pub trait DoSlash { /// The slashed entries type SlashedEntries; @@ -662,14 +662,14 @@ pub trait DoSlash { /// /// Return the slashes entities which may not be the same as `to_slash` /// because history slashes of the same kind may be included - fn do_slash(to_slash: Misbehaved, severity: Severity, kind: Kind) + fn do_slash(to_slash: Misbehaved, severity: Severity, kind: Kind, timestamp: Timestamp) -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()>; } /// A generic trait for paying out rewards -pub trait DoReward { +pub trait DoReward { /// Payout reward to reporters - fn do_reward(reporters: Reporters, total_reward: Reward) -> Result<(), ()>; + fn do_reward(reporters: Reporters, total_reward: Reward, timestamp: Timestamp) -> Result<(), ()>; } /// A generic event handler trait after slashing occured From 18ddb1bcd0d82d975fa25dbf4880714e412aae9e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 4 Aug 2019 18:42:10 +0200 Subject: [PATCH 64/66] remove enum `Misbehavior` --- node/runtime/src/impls.rs | 47 +-------------------------------------- node/runtime/src/lib.rs | 4 +--- 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/node/runtime/src/impls.rs b/node/runtime/src/impls.rs index 4a64204f936e8..2e1fcc8826e03 100644 --- a/node/runtime/src/impls.rs +++ b/node/runtime/src/impls.rs @@ -20,14 +20,10 @@ use node_primitives::Balance; use sr_primitives::weights::{Weight, WeightMultiplier}; use sr_primitives::traits::{Convert, Saturating}; use sr_primitives::Fixed64; -use parity_codec::{Encode, Decode}; -use support::traits::{OnUnbalanced, Currency, WindowLength}; +use support::traits::{OnUnbalanced, Currency}; use crate::{Balances, Authorship, MaximumBlockWeight, NegativeImbalance}; use crate::constants::fee::TARGET_BLOCK_FULLNESS; -#[cfg(any(feature = "std", test))] -use serde::{Serialize, Deserialize}; - pub struct Author; impl OnUnbalanced for Author { fn on_unbalanced(amount: NegativeImbalance) { @@ -132,47 +128,6 @@ impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierU } } -/// Misbehavior type which takes window length as input -/// Each variant and its data is a seperate kind -#[derive(Copy, Clone, Eq, Hash, PartialEq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub enum Misbehavior { - /// Validator is not online - Unresponsiveness(u32), - /// Unjustified vote - UnjustifiedVote(u32), - /// Rejecting set of votes - RejectSetVotes(u32), - /// Equivocation - Equivocation(u32), - /// Invalid Vote - InvalidVote(u32), - /// Invalid block - InvalidBlock(u32), - /// Parachain Invalid validity statement - ParachainInvalidity(u32), -} - -impl Default for Misbehavior { - fn default() -> Self { - Misbehavior::Unresponsiveness(10) - } -} - -impl WindowLength for Misbehavior { - fn window_length(&self) -> &u32 { - match self { - Misbehavior::Unresponsiveness(len) => len, - Misbehavior::UnjustifiedVote(len) => len, - Misbehavior::RejectSetVotes(len) => len, - Misbehavior::Equivocation(len) => len, - Misbehavior::InvalidVote(len) => len, - Misbehavior::InvalidBlock(len) => len, - Misbehavior::ParachainInvalidity(len) => len, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index f898c45ad190e..45d05c58b9c9d 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -60,7 +60,7 @@ pub use staking::StakerStatus; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{CurrencyToVoteHandler, WeightMultiplierUpdateHandler, Author, WeightToFee, Misbehavior}; +use impls::{CurrencyToVoteHandler, WeightMultiplierUpdateHandler, Author, WeightToFee}; /// Constant values used within the runtime. pub mod constants; @@ -241,7 +241,6 @@ impl staking::Trait for Runtime { impl staking::slash::Trait for Runtime { type Currency = Balances; - type SlashKind = Misbehavior; } parameter_types! { @@ -384,7 +383,6 @@ impl im_online::Trait for Runtime { } impl rolling_window::Trait for Runtime { - type MisbehaviorKind = Misbehavior; type SessionKey = primitives::sr25519::Public; } From a50bbb6b613c1f5bbd332fb7ef7c7d1bf110971d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 4 Aug 2019 18:43:41 +0200 Subject: [PATCH 65/66] add `trait` SlashingOffence, rm `WindowLength` --- srml/support/src/traits.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index b50c5e89b0a25..076087406b2cc 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -666,8 +666,9 @@ pub trait DoSlash { -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()>; } -/// A generic trait for paying out rewards -pub trait DoReward { +/// A generic trait for paying out rewards to entities that +/// has reported misbehaviors as basis for slashing +pub trait DoRewardSlashReporter { /// Payout reward to reporters fn do_reward(reporters: Reporters, total_reward: Reward, timestamp: Timestamp) -> Result<(), ()>; } @@ -678,8 +679,11 @@ pub trait AfterSlash { fn after_slash(who: Who, misconduct_level: MisconductLevel); } -/// Trait for representing window length -pub trait WindowLength { - /// Fetch window length - fn window_length(&self) -> &T; +/// Trait for representing an unique slashing offence +pub trait SlashingOffence { + /// Unique identifier of the misconduct + const ID: [u8; 16]; + + /// Window length + const WINDOW_LENGTH: u32; } From ada88eaa2d8aa3e80c6402880b216747d58652a0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 4 Aug 2019 18:45:31 +0200 Subject: [PATCH 66/66] rewrite me --- srml/rolling-window/src/lib.rs | 43 +++++++++-------- srml/staking/src/mock.rs | 28 +---------- srml/staking/src/slash.rs | 86 +++++++++++++++++++--------------- 3 files changed, 73 insertions(+), 84 deletions(-) diff --git a/srml/rolling-window/src/lib.rs b/srml/rolling-window/src/lib.rs index e9ac07e29bfc6..e6a61c53ad948 100644 --- a/srml/rolling-window/src/lib.rs +++ b/srml/rolling-window/src/lib.rs @@ -35,7 +35,7 @@ mod mock; use srml_support::{ StorageMap, Parameter, EnumerableStorageMap, decl_module, decl_storage, - traits::WindowLength + traits::SlashingOffence, }; use parity_codec::{Codec, Decode, Encode}; use rstd::vec::Vec; @@ -44,20 +44,20 @@ use srml_session::{SessionIndex, OneSessionHandler}; /// Rolling Window trait pub trait Trait: system::Trait { - /// MisbehaviorKind to report with window length - type MisbehaviorKind: Copy + Clone + Codec + MaybeSerializeDebug + WindowLength; - /// Identifier type to implement `OneSessionHandler` type SessionKey: Member + Parameter + Default + TypedKey + Decode + Encode + AsRef<[u8]>; } +type Id = [u8; 16]; +type WindowLength = u32; + decl_storage! { trait Store for Module as RollingWindow { /// Misbehavior reports /// /// It stores every unique misbehavior of a kind // TODO [#3149]: optimize how to shrink the window when sessions expire - MisbehaviorReports get(misbehavior_reports): linked_map T::MisbehaviorKind => Vec; + MisbehaviorReports get(misbehavior_reports): linked_map Id => Vec<(SessionIndex, WindowLength)>; /// Bonding Uniqueness /// @@ -76,26 +76,32 @@ decl_module! { } /// Trait for getting the number of misbehaviors in the current window -pub trait GetMisbehaviors { +pub trait GetMisbehaviors { /// Get number of misbehavior's in the current window for a kind - fn get_misbehaviors(kind: MisbehaviorKind) -> u64; + fn get_misbehaviors(id: Id) -> u64; } -impl GetMisbehaviors for Module { - fn get_misbehaviors(kind: T::MisbehaviorKind) -> u64 { - >::get(kind).len() as u64 +impl GetMisbehaviors for Module { + fn get_misbehaviors(id: Id) -> u64 { + MisbehaviorReports::get(id).len() as u64 } } /// Trait for reporting misbehavior's -pub trait MisbehaviorReporter { +pub trait MisbehaviorReporter { /// Report misbehavior for a kind - fn report_misbehavior(kind: MisbehaviorKind, footprint: Hash, current_session: SessionIndex) -> Result<(), ()>; + fn report_misbehavior( + id: Id, + window_length: WindowLength, + footprint: Hash, + current_session: SessionIndex + ) -> Result<(), ()>; } -impl MisbehaviorReporter for Module { +impl MisbehaviorReporter for Module { fn report_misbehavior( - kind: T::MisbehaviorKind, + id: Id, + window_length: WindowLength, footprint: T::Hash, current_session: SessionIndex, ) -> Result<(), ()> { @@ -103,7 +109,7 @@ impl MisbehaviorReporter for Module { Err(()) } else { >::insert(footprint, current_session); - >::mutate(kind, |entry| entry.push(current_session)); + MisbehaviorReports::mutate(id, |entry| entry.push((current_session, window_length))); Ok(()) } } @@ -119,11 +125,10 @@ impl OneSessionHandler for Module _validators: I, _queued_validators: I, ) { - for (kind, _) in >::enumerate() { - let window_length = kind.window_length(); - >::mutate(kind, |reports| { + for (kind, _) in MisbehaviorReports::enumerate() { + MisbehaviorReports::mutate(kind, |reports| { // it is guaranteed that `reported_session` happened in the same session or before `ending` - reports.retain(|reported_session| { + reports.retain(|(reported_session, window_length)| { let diff = ended_session.wrapping_sub(*reported_session); diff < *window_length }); diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index c92985a758a32..e5763fe4937b6 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -24,7 +24,7 @@ use primitives::{H256, Blake2Hasher}; use runtime_io; use session::SessionIndex; use srml_support::{assert_ok, impl_outer_origin, parameter_types, EnumerableStorageMap}; -use srml_support::traits::{Currency, Get, FindAuthor, WindowLength}; +use srml_support::traits::{Currency, Get, FindAuthor}; use crate::{ EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, Nominators, inflation @@ -199,35 +199,9 @@ impl Trait for Test { } impl srml_rolling_window::Trait for Test { - type MisbehaviorKind = Kind; type SessionKey = UintAuthorityId; } -#[derive(Debug, Copy, Clone, Encode, Decode, Serialize, Deserialize, PartialEq)] -pub enum Kind { - One, - Two, - Three, - Four, -} - -impl WindowLength for Kind { - fn window_length(&self) -> &u32 { - match self { - Kind::One => &4, - Kind::Two => &3, - Kind::Three => &2, - Kind::Four => &u32::max_value(), - } - } -} - -impl Default for Kind { - fn default() -> Self { - Kind::One - } -} - pub struct ExtBuilder { existential_deposit: u64, validator_pool: bool, diff --git a/srml/staking/src/slash.rs b/srml/staking/src/slash.rs index 5f5f9580a98ef..e92805c3ffaf2 100644 --- a/srml/staking/src/slash.rs +++ b/srml/staking/src/slash.rs @@ -21,7 +21,7 @@ use crate::Exposure; use srml_support::{ EnumerableStorageMap, StorageMap, decl_module, decl_storage, - traits::{Currency, WindowLength, DoSlash, DoReward} + traits::{Currency, DoSlash, DoRewardSlashReporter} }; use parity_codec::{HasCompact, Codec, Decode, Encode}; use rstd::{prelude::*, vec::Vec, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; @@ -31,10 +31,10 @@ type Timestamp = u128; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type Id = [u8; 16]; + /// Slashing trait pub trait Trait: system::Trait { - /// Slashing kind - type SlashKind: Copy + Clone + Codec + Default + PartialEq + MaybeSerializeDebug + WindowLength; /// Currency type Currency: Currency; } @@ -54,30 +54,29 @@ where /// A misconduct kind with timestamp when it occurred #[derive(Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct MisconductsByTime(Vec<(Timestamp, Kind)>); +pub struct MisconductsByTime(Vec<(Timestamp, Id)>); -impl MisconductsByTime { - fn contains_kind(&self, kind: Kind) -> bool { - self.0.iter().any(|(_, k)| *k == kind) +impl MisconductsByTime { + fn contains_kind(&self, id: Id) -> bool { + self.0.iter().any(|(_, k)| *k == id) } - fn multiple_misbehaviors_at_same_time(&self, time: Timestamp, kind: Kind) -> bool { - self.0.iter().any(|(t, k)| *t == time && *k != kind) + fn multiple_misbehaviors_at_same_time(&self, time: Timestamp, id: Id) -> bool { + self.0.iter().any(|(t, k)| *t == time && *k != id) } } /// State of a validator #[derive(Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct ValidatorState +pub struct ValidatorState where AccountId: Default + Ord, Balance: Default + HasCompact, - Kind: Default, { /// The misconducts the validator has conducted // TODO: replace this with BTreeSet sorted ordered by latest timestamp...smallest - misconducts: MisconductsByTime, + misconducts: MisconductsByTime, /// The rewards that the validator has received rewards: Vec<(Timestamp, Balance)>, /// Its own balance and the weight of the nominators that supports the validator @@ -90,7 +89,7 @@ decl_storage! { trait Store for Module as RollingWindow { /// Slashing history for a given validator SlashHistory get(misbehavior_reports): linked_map T::AccountId => - ValidatorState, T::SlashKind>; + ValidatorState>; } } @@ -126,7 +125,7 @@ impl Module { who: &T::AccountId, exposure: Exposure>, severity: Perbill, - kind: T::SlashKind, + kind: Id, timestamp: u128, total_slash: &mut BalanceOf, ) { @@ -168,7 +167,7 @@ impl Module { who: T::AccountId, exposure: Exposure>, severity: Perbill, - kind: T::SlashKind, + kind: Id, timestamp: u128, total_slash: &mut BalanceOf, ) { @@ -198,7 +197,7 @@ impl Module { who: &T::AccountId, exposure: &Exposure>, severity: Perbill, - kind: T::SlashKind, + kind: Id, slashed_entries: &mut Vec<(T::AccountId, Exposure>)>, total_slash: &mut BalanceOf, ) -> bool { @@ -236,7 +235,7 @@ impl Module { } } -impl DoSlash<(T::AccountId, Exposure>), Perbill, T::SlashKind, u128> for Module +impl DoSlash<(T::AccountId, Exposure>), Perbill, Id, u128> for Module { type SlashedEntries = Vec<(T::AccountId, Exposure>)>; type SlashedAmount = BalanceOf; @@ -244,7 +243,7 @@ impl DoSlash<(T::AccountId, Exposure>), Per fn do_slash( (who, exposure): (T::AccountId, Exposure>), severity: Perbill, - kind: T::SlashKind, + kind: Id, timestamp: u128, ) -> Result<(Self::SlashedEntries, Self::SlashedAmount), ()> { @@ -278,7 +277,7 @@ impl DoSlash<(T::AccountId, Exposure>), Per } } -impl DoReward, u128> for Module +impl DoRewardSlashReporter, u128> for Module where Reporters: IntoIterator, { @@ -312,7 +311,7 @@ mod tests { use srml_rolling_window::{ Module as RollingWindow, MisbehaviorReporter, GetMisbehaviors, impl_base_severity, impl_kind }; - use srml_support::{assert_ok, traits::{ReportSlash, DoSlash, AfterSlash, KeyOwnerProofSystem}}; + use srml_support::{assert_ok, traits::{ReportSlash, DoSlash, AfterSlash, KeyOwnerProofSystem, SlashingOffence}}; use std::collections::HashMap; use std::marker::PhantomData; use primitives::H256; @@ -323,11 +322,11 @@ mod tests { static EXPOSURES: RefCell>> = RefCell::new(Default::default()); static CURRENT_TIME: RefCell = RefCell::new(0); - static CURRENT_KIND: RefCell = RefCell::new(Kind::One); + static CURRENT_KIND: RefCell<[u8; 16]> = RefCell::new([0; 16]); } /// Trait for reporting slashes - pub trait ReporterTrait: srml_rolling_window::Trait + Trait { + pub trait ReporterTrait: srml_rolling_window::Trait + Trait { /// Key that identifies the owner type KeyOwner: KeyOwnerProofSystem; @@ -344,7 +343,6 @@ mod tests { } impl Trait for Test { - type SlashKind = Kind; type Currency = Balances; } @@ -424,9 +422,6 @@ mod tests { /// This should be something similar to `decl_module!` macro pub struct BabeEquivocation(PhantomData<(T, DS, DR, AS)>); - impl_base_severity!(BabeEquivocation, Perbill: Perbill::zero()); - impl_kind!(BabeEquivocation, Kind: Kind::One); - impl BabeEquivocation { pub fn as_misconduct_level(severity: Perbill) -> u8 { if severity > Perbill::from_percent(10) { @@ -441,20 +436,36 @@ mod tests { } } - impl ReportSlash for BabeEquivocation + impl SlashingOffence for BabeEquivocation { + const ID: [u8; 16] = [0; 16]; + const WINDOW_LENGTH: u32 = 5; + } + + impl ReportSlash< + T::Hash, + Reporters, + Who, + u128 + > for BabeEquivocation where T: ReporterTrait, - DS: DoSlash, - DR: DoReward, + DS: DoSlash, + DR: DoRewardSlashReporter, AS: AfterSlash, DS::SlashedAmount: rstd::fmt::Debug, DS::SlashedEntries: rstd::fmt::Debug, { - fn slash(footprint: T::Hash, reporters: Reporters, who: Who, timestamp: u128) -> Result<(), ()> { + fn slash( + footprint: T::Hash, + reporters: Reporters, + who: Who, + timestamp: u128 + ) -> Result<(), ()> { + // kind is supposed to be `const` but in this case it is mocked and we want change it + // in order to test with separate kinds let kind = get_current_misconduct_kind(); - let _base_seve = Self::base_severity(); - RollingWindow::::report_misbehavior(kind, footprint, 0)?; + RollingWindow::::report_misbehavior(kind, Self::WINDOW_LENGTH, footprint, 0)?; let num_violations = RollingWindow::::get_misbehaviors(kind); // number of validators @@ -480,7 +491,6 @@ mod tests { } } - fn get_current_time() -> u128 { CURRENT_TIME.with(|t| *t.borrow()) } @@ -489,12 +499,12 @@ mod tests { CURRENT_TIME.with(|t| *t.borrow_mut() += 1); } - fn get_current_misconduct_kind() -> Kind { + fn get_current_misconduct_kind() -> Id { CURRENT_KIND.with(|t| *t.borrow()) } - fn set_current_misconduct_kind(kind: Kind) { - CURRENT_KIND.with(|t| *t.borrow_mut() = kind); + fn set_current_misconduct_kind(id: Id) { + CURRENT_KIND.with(|t| *t.borrow_mut() = id); } #[test] @@ -858,7 +868,7 @@ mod tests { assert_eq!(Balances::free_balance(&m1), 880, "should be slashed by 12%"); assert_eq!(Balances::free_balance(&m2), 1000); - set_current_misconduct_kind(Kind::Two); + set_current_misconduct_kind([1; 16]); assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m1), vec![], 3000)); assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![], 3001)); @@ -895,7 +905,7 @@ mod tests { assert_eq!(Balances::free_balance(&m1), 999, "should be slashed by 0.12%"); assert_eq!(Balances::free_balance(&m2), 1000); - set_current_misconduct_kind(Kind::Two); + set_current_misconduct_kind([1; 16]); assert_ok!(BabeEquivocationReporter::::report_equivocation(FakeProof::new(m2), vec![], 0)); assert_eq!(Balances::free_balance(&m1), 999, "should not be slashed be slashed by Kind::Two"); assert_eq!(Balances::free_balance(&m2), 999, "should be slashed by 0.12%");