From dabb2c0905cf0212470968261720d66da9eb3312 Mon Sep 17 00:00:00 2001 From: Benjamin Gallois <business@gallois.cc> Date: Mon, 5 Jun 2023 15:39:12 +0200 Subject: [PATCH] Offences management (nodes/rust/duniter-v2s!161) * fix cargo files * fix slashing strategy * refactore remove_member_from_blacklist * add missing test * add license * refac tests adds comments remove some repetitions reorder tests (offence test are below) rename tests * feat(pallet_grandpa) add offences handler * feat(pallet_authority_members) add offences execution * feat(pallet_offences) add pallet offences --- Cargo.lock | 6 +- pallets/README.md | 1 + pallets/authority-members/Cargo.toml | 2 + pallets/authority-members/src/benchmarking.rs | 9 + pallets/authority-members/src/impls.rs | 81 +++++ pallets/authority-members/src/lib.rs | 41 ++- pallets/authority-members/src/mock.rs | 13 + pallets/authority-members/src/tests.rs | 224 ++++++++++++- pallets/authority-members/src/weights.rs | 8 + pallets/offences/Cargo.toml | 46 +++ pallets/offences/README.md | 9 + pallets/offences/src/lib.rs | 259 +++++++++++++++ pallets/offences/src/mock.rs | 156 +++++++++ pallets/offences/src/tests.rs | 303 ++++++++++++++++++ pallets/offences/src/traits.rs | 28 ++ runtime/common/src/pallets_config.rs | 10 +- .../src/weights/pallet_authority_members.rs | 53 +-- runtime/g1/Cargo.toml | 2 +- runtime/g1/src/lib.rs | 2 +- runtime/gdev/Cargo.toml | 2 +- runtime/gdev/src/lib.rs | 2 +- runtime/gtest/Cargo.toml | 2 +- runtime/gtest/src/lib.rs | 2 +- 23 files changed, 1227 insertions(+), 34 deletions(-) create mode 100644 pallets/authority-members/src/impls.rs create mode 100644 pallets/offences/Cargo.toml create mode 100644 pallets/offences/README.md create mode 100644 pallets/offences/src/lib.rs create mode 100644 pallets/offences/src/mock.rs create mode 100644 pallets/offences/src/tests.rs create mode 100644 pallets/offences/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index f6684b603..d00e4ff66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5040,6 +5040,7 @@ dependencies = [ "frame-system", "log", "maplit", + "pallet-offences", "pallet-session", "parity-scale-codec", "scale-info", @@ -5304,8 +5305,7 @@ dependencies = [ [[package]] name = "pallet-offences" -version = "4.0.0-dev" -source = "git+https://github.com/duniter/substrate?branch=duniter-substrate-v0.9.32#7f8b8db65b441ce1d1b2ffb26ebde314b54e117c" +version = "3.0.0" dependencies = [ "frame-support", "frame-system", @@ -5314,6 +5314,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-core", + "sp-io", "sp-runtime", "sp-staking", "sp-std", diff --git a/pallets/README.md b/pallets/README.md index 718e3cdf4..cf67622f8 100644 --- a/pallets/README.md +++ b/pallets/README.md @@ -18,6 +18,7 @@ These pallets are at the core of Duniter/Ğ1 currency ## Functional pallets - **`duniter-test-parameters`** Test parameters only used in ĞDev to allow tweaking parameters more easily. +- **`offences`** Sorts offences that will be executed by the `authority-members` pallet. - **`oneshot-account`** Oneshot accounts are light accounts only used once for anonimity or convenience use case. - **`provide-randomness`** Lets blockchain users ask for a verifiable random number. - **`session-benchmarking`** Benchmarks the session pallet. diff --git a/pallets/authority-members/Cargo.toml b/pallets/authority-members/Cargo.toml index 01c379921..81cfc5435 100644 --- a/pallets/authority-members/Cargo.toml +++ b/pallets/authority-members/Cargo.toml @@ -21,6 +21,7 @@ std = [ 'frame-benchmarking/std', 'log/std', 'pallet-session/std', + 'pallet-offences/std', 'serde', 'sp-core/std', 'sp-membership/std', @@ -32,6 +33,7 @@ try-runtime = ['frame-support/try-runtime'] [dependencies] # local +pallet-offences = { path = "../offences", default-features = false } sp-membership = { path = "../../primitives/membership", default-features = false } # crates.io diff --git a/pallets/authority-members/src/benchmarking.rs b/pallets/authority-members/src/benchmarking.rs index dc7ae8358..d19e73ccf 100644 --- a/pallets/authority-members/src/benchmarking.rs +++ b/pallets/authority-members/src/benchmarking.rs @@ -68,6 +68,15 @@ benchmarks! { verify { assert_has_event::<T>(Event::<T>::MemberRemoved(id).into()); } + remove_member_from_blacklist { + let id: T::MemberId = OnlineAuthorities::<T>::get()[0]; + BlackList::<T>::mutate(|blacklist| { + blacklist.push(id); + }); + }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), id) + verify { + assert_has_event::<T>(Event::<T>::MemberRemovedFromBlackList(id).into()); + } impl_benchmark_test_suite!( Pallet, diff --git a/pallets/authority-members/src/impls.rs b/pallets/authority-members/src/impls.rs new file mode 100644 index 000000000..80f9eef04 --- /dev/null +++ b/pallets/authority-members/src/impls.rs @@ -0,0 +1,81 @@ +// Copyright 2021-2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +//! Implementation of the Slashing execution logic. +//! +//! Offences are sorted in the `offences` pallet. +//! The offences are executed here based. The offenders are disconnected and +//! can be added to a blacklist to avoid futur connection. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::type_complexity)] + +use super::pallet::*; +use frame_support::pallet_prelude::Weight; +use frame_support::traits::Get; +use pallet_offences::traits::OnOffenceHandler; +use pallet_offences::SlashStrategy; +use sp_runtime::traits::Convert; +use sp_staking::offence::OffenceDetails; +use sp_staking::SessionIndex; + +impl<T: Config> + OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight> + for Pallet<T> +where + T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>, +{ + fn on_offence( + offenders: &[OffenceDetails< + T::AccountId, + pallet_session::historical::IdentificationTuple<T>, + >], + strategy: SlashStrategy, + _slash_session: SessionIndex, + ) -> Weight { + let mut consumed_weight = Weight::from_parts(0, 0); + let mut add_db_reads_writes = |reads, writes| { + consumed_weight += T::DbWeight::get().reads_writes(reads, writes); + }; + + match strategy { + SlashStrategy::BlackList => { + for offender in offenders { + BlackList::<T>::mutate(|blacklist| { + if let Some(member_id) = T::MemberIdOf::convert(offender.offender.0.clone()) + { + if !blacklist.contains(&member_id) { + blacklist.push(member_id); + add_db_reads_writes(0, 1); + } + Self::insert_out(member_id); + add_db_reads_writes(2, 1); + } + }) + } + } + SlashStrategy::Disconnect => { + for offender in offenders { + if let Some(member_id) = T::MemberIdOf::convert(offender.offender.0.clone()) { + Self::insert_out(member_id); + add_db_reads_writes(1, 1); + } + } + } + } + consumed_weight + } +} diff --git a/pallets/authority-members/src/lib.rs b/pallets/authority-members/src/lib.rs index 22865875f..d4f299c36 100644 --- a/pallets/authority-members/src/lib.rs +++ b/pallets/authority-members/src/lib.rs @@ -30,6 +30,9 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod impls; +pub use impls::*; + pub use pallet::*; pub use types::*; pub use weights::WeightInfo; @@ -175,6 +178,11 @@ pub mod pallet { pub type MustRotateKeysBefore<T: Config> = StorageMap<_, Twox64Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>; + // Blacklist. + #[pallet::storage] + #[pallet::getter(fn blacklist)] + pub type BlackList<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>; + // HOOKS // // EVENTS // @@ -198,6 +206,9 @@ pub mod pallet { /// this member will be removed from the authority set in 2 sessions. /// [member_id] MemberRemoved(T::MemberId), + /// A member has been removed from the blacklist. + /// [member_id] + MemberRemovedFromBlackList(T::MemberId), } // ERRORS // @@ -212,6 +223,10 @@ pub mod pallet { AlreadyOutgoing, /// Not found owner key MemberIdNotFound, + /// Member is blacklisted + MemberIdBlackListed, + /// Member is not blacklisted + MemberNotBlackListed, /// Member not found MemberNotFound, /// Neither online nor scheduled @@ -264,6 +279,9 @@ pub mod pallet { let who = ensure_signed(origin)?; let member_id = Self::verify_ownership_and_membership(&who)?; + if Self::is_blacklisted(member_id) { + return Err(Error::<T>::MemberIdBlackListed.into()); + } if !Members::<T>::contains_key(member_id) { return Err(Error::<T>::MemberNotFound.into()); } @@ -340,6 +358,23 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(<T as pallet::Config>::WeightInfo::remove_member_from_blacklist())] + /// remove an identity from the blacklist + pub fn remove_member_from_blacklist( + origin: OriginFor<T>, + member_id: T::MemberId, + ) -> DispatchResultWithPostInfo { + T::RemoveMemberOrigin::ensure_origin(origin)?; + BlackList::<T>::mutate(|members_ids| { + if let Ok(index) = members_ids.binary_search(&member_id) { + members_ids.remove(index); + Self::deposit_event(Event::MemberRemovedFromBlackList(member_id)); + Ok(().into()) + } else { + Err(Error::<T>::MemberNotBlackListed.into()) + } + }) + } } // PUBLIC FUNCTIONS // @@ -452,7 +487,7 @@ pub mod pallet { not_already_inserted } /// perform outgoing authority insertion - fn insert_out(member_id: T::MemberId) -> bool { + pub fn insert_out(member_id: T::MemberId) -> bool { let not_already_inserted = OutgoingAuthorities::<T>::mutate(|members_ids| { if let Err(index) = members_ids.binary_search(&member_id) { members_ids.insert(index, member_id); @@ -489,6 +524,10 @@ pub mod pallet { .binary_search(&member_id) .is_ok() } + /// check if member is blacklisted + fn is_blacklisted(member_id: T::MemberId) -> bool { + BlackList::<T>::get().contains(&member_id) + } /// perform removal from incoming authorities fn remove_in(member_id: T::MemberId) { AuthoritiesCounter::<T>::mutate(|counter| counter.saturating_sub(1)); diff --git a/pallets/authority-members/src/mock.rs b/pallets/authority-members/src/mock.rs index 15a820816..5cc6fd83f 100644 --- a/pallets/authority-members/src/mock.rs +++ b/pallets/authority-members/src/mock.rs @@ -23,6 +23,8 @@ use frame_support::{ BasicExternalities, }; use frame_system as system; +use pallet_offences::traits::OnOffenceHandler; +use pallet_offences::SlashStrategy; use pallet_session::ShouldEndSession; use sp_core::{crypto::key_types::DUMMY, H256}; use sp_runtime::{ @@ -31,6 +33,7 @@ use sp_runtime::{ traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys}, KeyTypeId, }; +use sp_staking::offence::OffenceDetails; type AccountId = u64; type Block = frame_system::mocking::MockBlock<Test>; @@ -205,3 +208,13 @@ pub fn run_to_block(n: u64) { Session::on_initialize(System::block_number()); } } + +pub(crate) fn on_offence( + offenders: &[OffenceDetails< + AccountId, + pallet_session::historical::IdentificationTuple<Test>, + >], + slash_strategy: SlashStrategy, +) { + AuthorityMembers::on_offence(offenders, slash_strategy, 0); +} diff --git a/pallets/authority-members/src/tests.rs b/pallets/authority-members/src/tests.rs index d26c3f410..6c99f0528 100644 --- a/pallets/authority-members/src/tests.rs +++ b/pallets/authority-members/src/tests.rs @@ -17,8 +17,11 @@ use super::*; use crate::mock::*; use crate::MemberData; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_system::RawOrigin; use sp_runtime::testing::UintAuthorityId; +use sp_runtime::traits::BadOrigin; +use sp_staking::offence::OffenceDetails; const EMPTY: Vec<u64> = Vec::new(); @@ -126,6 +129,7 @@ fn test_max_keys_life_rule() { }); } +/// tests consequences of go_offline call #[test] fn test_go_offline() { new_test_ext(3).execute_with(|| { @@ -135,9 +139,11 @@ fn test_go_offline() { assert_ok!(AuthorityMembers::go_offline(RuntimeOrigin::signed(9)),); // Verify state + assert_eq!(Session::current_index(), 0); // we are currently at session 0 assert_eq!(AuthorityMembers::incoming(), EMPTY); assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]); assert_eq!(AuthorityMembers::outgoing(), vec![9]); + assert_eq!(AuthorityMembers::blacklist(), EMPTY); assert_eq!( AuthorityMembers::member(9), Some(MemberData { @@ -147,7 +153,9 @@ fn test_go_offline() { }) ); - // Member 9 should be "deprogrammed" at the next session + // Member 9 should be "deprogrammed" at the next session (session 1) + // it should be out at session 2 and + // the expiry should be 2 sessions after that (session 4) run_to_block(5); assert_eq!( AuthorityMembers::member(9), @@ -178,6 +186,7 @@ fn test_go_offline() { }); } +/// tests consequences of go_online call #[test] fn test_go_online() { new_test_ext(3).execute_with(|| { @@ -322,3 +331,214 @@ fn test_go_offline_then_go_online_in_same_session() { ); }); } + +// === offence handling tests below === + +/// test offence handling with disconnect strategy +// the offenders should be disconnected (same as go_offline) +// they should be able to go_online after +#[test] +fn test_offence_disconnect() { + new_test_ext(3).execute_with(|| { + run_to_block(1); + + on_offence( + &[OffenceDetails { + offender: (9, ()), + reporters: vec![], + }], + pallet_offences::SlashStrategy::Disconnect, + ); + on_offence( + &[OffenceDetails { + offender: (3, ()), + reporters: vec![], + }], + pallet_offences::SlashStrategy::Disconnect, + ); + + // Verify state + assert_eq!(AuthorityMembers::incoming(), EMPTY); + assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]); + assert_eq!(AuthorityMembers::outgoing(), vec![3, 9]); + assert_eq!(AuthorityMembers::blacklist(), EMPTY); + + // Member 9 and 3 should be "deprogrammed" at the next session + run_to_block(5); + assert_eq!( + AuthorityMembers::member(9), + Some(MemberData { + expire_on_session: 4, + must_rotate_keys_before: 5, + owner_key: 9, + }) + ); + assert_eq!( + AuthorityMembers::member(3), + Some(MemberData { + expire_on_session: 4, + must_rotate_keys_before: 5, + owner_key: 3, + }) + ); + assert_eq!(AuthorityMembers::members_expire_on(4), vec![3, 9],); + assert_eq!(Session::current_index(), 1); + assert_eq!(Session::validators(), vec![3, 6, 9]); + assert_eq!(Session::queued_keys().len(), 1); + assert_eq!(Session::queued_keys()[0].0, 6); + + // Member 9 and 3 should be **effectively** out at session 2 + run_to_block(10); + assert_eq!(Session::current_index(), 2); + assert_eq!(Session::validators(), vec![6]); + + // Member 9 and 3 should be removed at session 4 + run_to_block(20); + assert_eq!(Session::current_index(), 4); + assert_eq!(Session::validators(), vec![6]); + assert_eq!(AuthorityMembers::members_expire_on(4), EMPTY); + assert_eq!(AuthorityMembers::member(3), None); + assert_eq!(AuthorityMembers::member(9), None); + + // Member 9 and 3 should be allowed to set session keys and go online + run_to_block(25); + assert_ok!(AuthorityMembers::set_session_keys( + RuntimeOrigin::signed(9), + UintAuthorityId(9).into(), + )); + assert_ok!(AuthorityMembers::set_session_keys( + RuntimeOrigin::signed(3), + UintAuthorityId(3).into(), + )); + assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(9)),); + assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(3)),); + + // Report an offence again + run_to_block(35); + on_offence( + &[OffenceDetails { + offender: (3, ()), + reporters: vec![], + }], + pallet_offences::SlashStrategy::Disconnect, + ); + + // Verify state, 6 is out of life now, only 3 should be outgoing now + assert_eq!(AuthorityMembers::incoming(), EMPTY); + assert_eq!(AuthorityMembers::online(), vec![3, 9]); + assert_eq!(AuthorityMembers::outgoing(), vec![3]); + assert_eq!(AuthorityMembers::blacklist(), EMPTY); + }); +} + +/// test offence handling with blacklist strategy +// member 9 is offender, should be blacklisted +#[test] +fn test_offence_black_list() { + new_test_ext(3).execute_with(|| { + // at block 0 begins session 0 + run_to_block(1); + + on_offence( + &[OffenceDetails { + offender: (9, ()), + reporters: vec![], + }], + pallet_offences::SlashStrategy::BlackList, + ); + + // Verify state + // same as `test_go_offline` + assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]); + assert_eq!(AuthorityMembers::outgoing(), vec![9]); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); + + // Member 9 should be "deprogrammed" at the next session + run_to_block(5); + assert_eq!(AuthorityMembers::members_expire_on(4), vec![9],); + assert_eq!(Session::current_index(), 1); + assert_eq!(Session::validators(), vec![3, 6, 9]); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); // still in blacklist + + // Member 9 should be **effectively** out at session 2 + run_to_block(10); + assert_eq!(Session::current_index(), 2); + assert_eq!(Session::validators(), vec![3, 6]); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); // still in blacklist + + // Member 9 should be removed at session 4 + run_to_block(20); + assert_eq!(Session::current_index(), 4); + assert_eq!(Session::validators(), vec![3, 6]); + assert_eq!(AuthorityMembers::members_expire_on(4), EMPTY); + assert_eq!(AuthorityMembers::member(9), None); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); // still in blacklist + }); +} + +/// tests that blacklisting prevents 9 from going online +#[test] +fn test_offence_black_list_prevent_from_going_online() { + new_test_ext(3).execute_with(|| { + run_to_block(1); + + on_offence( + &[OffenceDetails { + offender: (9, ()), + reporters: vec![], + }], + pallet_offences::SlashStrategy::BlackList, + ); + + // Verify state + assert_eq!(AuthorityMembers::incoming(), EMPTY); + assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]); + assert_eq!(AuthorityMembers::outgoing(), vec![9]); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); + assert_eq!( + AuthorityMembers::member(9), + Some(MemberData { + expire_on_session: 0, + must_rotate_keys_before: 5, + owner_key: 9, + }) + ); + + // for detail, see `test_go_offline` + // Member 9 is "deprogrammed" at the next session + // Member 9 is **effectively** out at session 2 + // Member 9 is removed at session 4 + + // Member 9 should not be allowed to go online + run_to_block(25); + assert_ok!(AuthorityMembers::set_session_keys( + RuntimeOrigin::signed(9), + UintAuthorityId(9).into(), + )); + assert_err!( + AuthorityMembers::go_online(RuntimeOrigin::signed(9)), + Error::<Test>::MemberIdBlackListed + ); + + // Should not be able to auto remove from blacklist + assert_err!( + AuthorityMembers::remove_member_from_blacklist(RuntimeOrigin::signed(9), 9), + BadOrigin + ); + assert_eq!(AuthorityMembers::blacklist(), vec![9]); + + // Authorized should be able to remove from blacklist + assert_ok!(AuthorityMembers::remove_member_from_blacklist( + RawOrigin::Root.into(), + 9 + )); + assert_eq!(AuthorityMembers::blacklist(), EMPTY); + System::assert_last_event(Event::MemberRemovedFromBlackList(9).into()); + + // Authorized should not be able to remove a non-existing member from blacklist + assert_err!( + AuthorityMembers::remove_member_from_blacklist(RawOrigin::Root.into(), 9), + Error::<Test>::MemberNotBlackListed + ); + }); +} diff --git a/pallets/authority-members/src/weights.rs b/pallets/authority-members/src/weights.rs index 3b9d8a03c..7682a59d4 100644 --- a/pallets/authority-members/src/weights.rs +++ b/pallets/authority-members/src/weights.rs @@ -24,6 +24,7 @@ pub trait WeightInfo { fn go_online() -> Weight; fn set_session_keys() -> Weight; fn remove_member() -> Weight; + fn remove_member_from_blacklist() -> Weight; } // Insecure weights implementation, use it for tests only! @@ -85,4 +86,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(9 as u64)) .saturating_add(RocksDbWeight::get().writes(13 as u64)) } + // Storage: AuthorityMembers BlackList (r:1 w:1) + fn remove_member_from_blacklist() -> Weight { + // Minimum execution time: 60_023 nanoseconds. + Weight::from_ref_time(60_615_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } } diff --git a/pallets/offences/Cargo.toml b/pallets/offences/Cargo.toml new file mode 100644 index 000000000..560b22f5b --- /dev/null +++ b/pallets/offences/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-offences" +authors = ["Parity Technologies <admin@parity.io>", "Axiom-Team Developers <https://axiom-team.fr>"] +description = 'FRAME pallet to handle offences.' +edition = "2021" +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '3.0.0' +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", features = ["derive"], default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", default-features = false, optional = true } +frame-support = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +frame-system = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +pallet-balances = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +sp-runtime = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +sp-staking = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +sp-std = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } + +[dev-dependencies] +sp-core = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } +sp-io = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32' } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "serde", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", +] +runtime-benchmarks = [] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/offences/README.md b/pallets/offences/README.md new file mode 100644 index 000000000..62fa734c3 --- /dev/null +++ b/pallets/offences/README.md @@ -0,0 +1,9 @@ +# Duniter offences pallet + +This is a fork of the Substrate `offences` pallet that is modified to agree with the offence rules based on the `authority-member` pallet and not in the Substrate `staking` pallet. + +Duniter provides a basic way to process offences: +* On offences from `im-online` pallet, the offender disconnection is required. +* On other offences, the offender disconnection is required and the offender is required to be blacklisted and only an authorized origin can remove the offender from the blacklist. + +The offences triage is realized in the `offences` pallet and the slashing execution is done in the `authority-member` pallet. \ No newline at end of file diff --git a/pallets/offences/src/lib.rs b/pallets/offences/src/lib.rs new file mode 100644 index 000000000..02316c4f6 --- /dev/null +++ b/pallets/offences/src/lib.rs @@ -0,0 +1,259 @@ +// Copyright 2021-2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +use core::marker::PhantomData; + +use codec::Encode; +use frame_support::weights::Weight; +use sp_runtime::traits::Hash; +use sp_staking::offence::{Kind, Offence, OffenceDetails, OffenceError, ReportOffence}; +use sp_std::prelude::*; + +pub use pallet::*; + +pub mod traits; +use self::traits::*; + +/// A binary blob which represents a SCALE codec-encoded `O::TimeSlot`. +type OpaqueTimeSlot = Vec<u8>; + +/// A type alias for a report identifier. +type ReportIdOf<T> = <T as frame_system::Config>::Hash; + +pub enum SlashStrategy { + Disconnect, + BlackList, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet<T>(_); + + /// The pallet's config trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + /// Full identification of the validator. + type IdentificationTuple: Parameter; + /// A handler called for every offence report. + type OnOffenceHandler: OnOffenceHandler<Self::AccountId, Self::IdentificationTuple, Weight>; + } + + /// The primary structure that holds all offence records keyed by report identifiers. + #[pallet::storage] + #[pallet::getter(fn reports)] + pub type Reports<T: Config> = StorageMap< + _, + Twox64Concat, + ReportIdOf<T>, + OffenceDetails<T::AccountId, T::IdentificationTuple>, + >; + + /// A vector of reports of the same kind that happened at the same time slot. + #[pallet::storage] + pub type ConcurrentReportsIndex<T: Config> = StorageDoubleMap< + _, + Twox64Concat, + Kind, + Twox64Concat, + OpaqueTimeSlot, + Vec<ReportIdOf<T>>, + ValueQuery, + >; + + /// Events type. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// There is an offence reported of the given `kind` happened at the `session_index` and + /// (kind-specific) time slot. This event is not deposited for duplicate slashes. + /// \[kind, timeslot\]. + Offence { + kind: Kind, + timeslot: OpaqueTimeSlot, + }, + } +} + +impl<T, O> ReportOffence<T::AccountId, T::IdentificationTuple, O> for Pallet<T> +where + T: Config, + O: Offence<T::IdentificationTuple>, +{ + fn report_offence(reporters: Vec<T::AccountId>, offence: O) -> Result<(), OffenceError> { + let offenders = offence.offenders(); + let time_slot = offence.time_slot(); + + // Go through all offenders in the offence report and find all offenders that were spotted + // in unique reports. + let TriageOutcome { + concurrent_offenders, + } = match Self::triage_offence_report::<O>(reporters, &time_slot, offenders) { + Some(triage) => triage, + None => return Err(OffenceError::DuplicateReport), + }; + + // Define the slash strategy. + let slash_strategy = if O::ID == *b"im-online:offlin" { + SlashStrategy::Disconnect + } else { + SlashStrategy::BlackList + }; + + T::OnOffenceHandler::on_offence( + &concurrent_offenders, + slash_strategy, + offence.session_index(), + ); + + Self::deposit_event(Event::Offence { + kind: O::ID, + timeslot: time_slot.encode(), + }); + + Ok(()) + } + + fn is_known_offence(offenders: &[T::IdentificationTuple], time_slot: &O::TimeSlot) -> bool { + let any_unknown = offenders.iter().any(|offender| { + let report_id = Self::report_id::<O>(time_slot, offender); + !<Reports<T>>::contains_key(report_id) + }); + + !any_unknown + } +} + +impl<T: Config> Pallet<T> { + /// Compute the ID for the given report properties. + /// + /// The report id depends on the offence kind, time slot and the id of offender. + fn report_id<O: Offence<T::IdentificationTuple>>( + time_slot: &O::TimeSlot, + offender: &T::IdentificationTuple, + ) -> ReportIdOf<T> { + (O::ID, time_slot.encode(), offender).using_encoded(T::Hashing::hash) + } + + /// Triages the offence report and returns the set of offenders that was involved in unique + /// reports along with the list of the concurrent offences. + fn triage_offence_report<O: Offence<T::IdentificationTuple>>( + reporters: Vec<T::AccountId>, + time_slot: &O::TimeSlot, + offenders: Vec<T::IdentificationTuple>, + ) -> Option<TriageOutcome<T>> { + let mut storage = ReportIndexStorage::<T, O>::load(time_slot); + + let mut any_new = false; + for offender in offenders { + let report_id = Self::report_id::<O>(time_slot, &offender); + + if !<Reports<T>>::contains_key(report_id) { + any_new = true; + <Reports<T>>::insert( + report_id, + OffenceDetails { + offender, + reporters: reporters.clone(), + }, + ); + + storage.insert(report_id); + } + } + + if any_new { + // Load report details for the all reports happened at the same time. + let concurrent_offenders = storage + .concurrent_reports + .iter() + .filter_map(<Reports<T>>::get) + .collect::<Vec<_>>(); + + storage.save(); + + Some(TriageOutcome { + concurrent_offenders, + }) + } else { + None + } + } +} + +struct TriageOutcome<T: Config> { + /// Other reports for the same report kinds. + concurrent_offenders: Vec<OffenceDetails<T::AccountId, T::IdentificationTuple>>, +} + +/// An auxiliary struct for working with storage of indexes localized for a specific offence +/// kind (specified by the `O` type parameter). +/// +/// This struct is responsible for aggregating storage writes and the underlying storage should not +/// accessed directly meanwhile. +#[must_use = "The changes are not saved without called `save`"] +struct ReportIndexStorage<T: Config, O: Offence<T::IdentificationTuple>> { + opaque_time_slot: OpaqueTimeSlot, + concurrent_reports: Vec<ReportIdOf<T>>, + _phantom: PhantomData<O>, +} + +impl<T: Config, O: Offence<T::IdentificationTuple>> ReportIndexStorage<T, O> { + /// Preload indexes from the storage for the specific `time_slot` and the kind of the offence. + fn load(time_slot: &O::TimeSlot) -> Self { + let opaque_time_slot = time_slot.encode(); + + let concurrent_reports = <ConcurrentReportsIndex<T>>::get(O::ID, &opaque_time_slot); + + Self { + opaque_time_slot, + concurrent_reports, + _phantom: Default::default(), + } + } + + /// Insert a new report to the index. + fn insert(&mut self, report_id: ReportIdOf<T>) { + // Update the list of concurrent reports. + self.concurrent_reports.push(report_id); + } + + /// Dump the indexes to the storage. + fn save(self) { + <ConcurrentReportsIndex<T>>::insert( + O::ID, + &self.opaque_time_slot, + &self.concurrent_reports, + ); + } +} diff --git a/pallets/offences/src/mock.rs b/pallets/offences/src/mock.rs new file mode 100644 index 000000000..1229f7559 --- /dev/null +++ b/pallets/offences/src/mock.rs @@ -0,0 +1,156 @@ +// Copyright 2021-2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +#![cfg(test)] + +use crate::Config; +use crate::{self as pallet_offences, SlashStrategy}; +use codec::Encode; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; +use sp_staking::{ + offence::{Kind, OffenceDetails}, + SessionIndex, +}; + +pub struct OnOffenceHandler; + +parameter_types! { + pub static OnOffencePerbill: Vec<Perbill> = Default::default(); + pub static OffenceWeight: Weight = Default::default(); +} + +impl<Reporter, Offender> pallet_offences::OnOffenceHandler<Reporter, Offender, Weight> + for OnOffenceHandler +{ + fn on_offence( + _offenders: &[OffenceDetails<Reporter, Offender>], + _strategy: SlashStrategy, + _offence_session: SessionIndex, + ) -> Weight { + OffenceWeight::get() + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>; +type Block = frame_system::mocking::MockBlock<Runtime>; + +frame_support::construct_runtime!( + pub struct Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, + Offences: pallet_offences::{Pallet, Storage, Event}, + } +); + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup<Self::AccountId>; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type IdentificationTuple = u64; + type OnOffenceHandler = OnOffenceHandler; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::<Runtime>() + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub const KIND: [u8; 16] = *b"test_report_1234"; + +/// Returns all offence details for the specific `kind` happened at the specific time slot. +pub fn offence_reports(kind: Kind, time_slot: u128) -> Vec<OffenceDetails<u64, u64>> { + <crate::ConcurrentReportsIndex<Runtime>>::get(kind, &time_slot.encode()) + .into_iter() + .map(|report_id| { + <crate::Reports<Runtime>>::get(report_id) + .expect("dangling report id is found in ConcurrentReportsIndex") + }) + .collect() +} + +#[derive(Clone)] +pub struct Offence { + pub validator_set_count: u32, + pub offenders: Vec<u64>, + pub time_slot: u128, +} + +impl pallet_offences::Offence<u64> for Offence { + const ID: pallet_offences::Kind = KIND; + type TimeSlot = u128; + + fn offenders(&self) -> Vec<u64> { + self.offenders.clone() + } + + fn validator_set_count(&self) -> u32 { + self.validator_set_count + } + + fn time_slot(&self) -> u128 { + self.time_slot + } + + fn session_index(&self) -> SessionIndex { + 1 + } + + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + Perbill::from_percent(5 + offenders_count * 100 / self.validator_set_count) + } +} diff --git a/pallets/offences/src/tests.rs b/pallets/offences/src/tests.rs new file mode 100644 index 000000000..1b5fefff8 --- /dev/null +++ b/pallets/offences/src/tests.rs @@ -0,0 +1,303 @@ +// Copyright 2021-2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +#![cfg(test)] + +use super::*; +use crate::mock::{new_test_ext, offence_reports, Offence, Offences, RuntimeEvent, System, KIND}; +use frame_system::{EventRecord, Phase}; + +#[test] +fn should_report_an_authority_and_trigger_on_offence_and_add_to_blacklist() { + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5, 9], + }; + + // when + Offences::report_offence(vec![], offence).unwrap(); + + // then + assert_eq!( + offence_reports(KIND, time_slot), + vec![ + OffenceDetails { + offender: 5, + reporters: vec![] + }, + OffenceDetails { + offender: 9, + reporters: vec![] + } + ] + ); + }); +} + +#[test] +fn should_not_report_the_same_authority_twice_in_the_same_slot() { + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence.clone()).unwrap(); + + // when + // report for the second time + assert_eq!( + Offences::report_offence(vec![], offence), + Err(OffenceError::DuplicateReport) + ); + + // then + assert_eq!( + offence_reports(KIND, time_slot), + vec![OffenceDetails { + offender: 5, + reporters: vec![] + },] + ); + }); +} + +#[test] +fn should_report_in_different_time_slot() { + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let mut offence = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence.clone()).unwrap(); + System::assert_last_event( + Event::Offence { + kind: KIND, + timeslot: time_slot.encode(), + } + .into(), + ); + + // when + // report for the second time + offence.time_slot += 1; + Offences::report_offence(vec![], offence.clone()).unwrap(); + + // then + System::assert_last_event( + Event::Offence { + kind: KIND, + timeslot: offence.time_slot.encode(), + } + .into(), + ); + }); +} + +#[test] +fn should_deposit_event() { + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5], + }; + + // when + Offences::report_offence(vec![], offence).unwrap(); + + // then + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Offences(crate::Event::Offence { + kind: KIND, + timeslot: time_slot.encode() + }), + topics: vec![], + }] + ); + }); +} + +#[test] +fn doesnt_deposit_event_for_dups() { + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence.clone()).unwrap(); + + // when + // report for the second time + assert_eq!( + Offences::report_offence(vec![], offence), + Err(OffenceError::DuplicateReport) + ); + + // then + // there is only one event. + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Offences(crate::Event::Offence { + kind: KIND, + timeslot: time_slot.encode() + }), + topics: vec![], + }] + ); + }); +} + +#[test] +fn reports_if_an_offence_is_dup() { + new_test_ext().execute_with(|| { + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence = |time_slot, offenders| Offence { + validator_set_count: 5, + time_slot, + offenders, + }; + + let mut test_offence = offence(time_slot, vec![0]); + + // the report for authority 0 at time slot 42 should not be a known + // offence + assert!( + !<Offences as ReportOffence<_, _, Offence>>::is_known_offence( + &test_offence.offenders, + &test_offence.time_slot + ) + ); + + // we report an offence for authority 0 at time slot 42 + Offences::report_offence(vec![], test_offence.clone()).unwrap(); + + // the same report should be a known offence now + assert!( + <Offences as ReportOffence<_, _, Offence>>::is_known_offence( + &test_offence.offenders, + &test_offence.time_slot + ) + ); + + // and reporting it again should yield a duplicate report error + assert_eq!( + Offences::report_offence(vec![], test_offence.clone()), + Err(OffenceError::DuplicateReport) + ); + + // after adding a new offender to the offence report + test_offence.offenders.push(1); + + // it should not be a known offence anymore + assert!( + !<Offences as ReportOffence<_, _, Offence>>::is_known_offence( + &test_offence.offenders, + &test_offence.time_slot + ) + ); + + // and reporting it again should work without any error + assert_eq!( + Offences::report_offence(vec![], test_offence.clone()), + Ok(()) + ); + + // creating a new offence for the same authorities on the next slot + // should be considered a new offence and thefore not known + let test_offence_next_slot = offence(time_slot + 1, vec![0, 1]); + assert!( + !<Offences as ReportOffence<_, _, Offence>>::is_known_offence( + &test_offence_next_slot.offenders, + &test_offence_next_slot.time_slot + ) + ); + }); +} + +#[test] +fn should_properly_count_offences() { + // We report two different authorities for the same issue. Ultimately, the 1st authority + // should have `count` equal 2 and the count of the 2nd one should be equal to 1. + new_test_ext().execute_with(|| { + // given + let time_slot = 42; + assert_eq!(offence_reports(KIND, time_slot), vec![]); + + let offence1 = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![5], + }; + let offence2 = Offence { + validator_set_count: 5, + time_slot, + offenders: vec![4], + }; + Offences::report_offence(vec![], offence1).unwrap(); + + // when + // report for the second time + Offences::report_offence(vec![], offence2).unwrap(); + + // then + // the 1st authority should have count 2 and the 2nd one should be reported only once. + assert_eq!( + offence_reports(KIND, time_slot), + vec![ + OffenceDetails { + offender: 5, + reporters: vec![] + }, + OffenceDetails { + offender: 4, + reporters: vec![] + }, + ] + ); + }); +} diff --git a/pallets/offences/src/traits.rs b/pallets/offences/src/traits.rs new file mode 100644 index 000000000..69dfe649d --- /dev/null +++ b/pallets/offences/src/traits.rs @@ -0,0 +1,28 @@ +// Copyright 2021-2023 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. + +use sp_staking::{offence::OffenceDetails, SessionIndex}; + +use crate::SlashStrategy; + +pub trait OnOffenceHandler<Reporter, Offender, Res> { + // Required method + fn on_offence( + offenders: &[OffenceDetails<Reporter, Offender>], + slash_strategy: SlashStrategy, + session: SessionIndex, + ) -> Res; +} diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index bb1315bd6..f289dfac1 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -235,7 +235,7 @@ macro_rules! pallets_config { impl pallet_offences::Config for Runtime { type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>; - type OnOffenceHandler = (); + type OnOffenceHandler = AuthorityMembers; } impl pallet_session::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -255,7 +255,7 @@ macro_rules! pallets_config { impl pallet_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type KeyOwnerProofSystem = (); + type KeyOwnerProofSystem = Historical; type KeyOwnerProof = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, GrandpaId)>>::Proof; @@ -265,7 +265,11 @@ macro_rules! pallets_config { GrandpaId, )>>::IdentificationTuple; - type HandleEquivocation = (); + type HandleEquivocation = pallet_grandpa::EquivocationHandler< + Self::KeyOwnerIdentification, + Offences, + ReportLongevity, + >; type WeightInfo = common_runtime::weights::pallet_grandpa::WeightInfo<Runtime>; diff --git a/runtime/common/src/weights/pallet_authority_members.rs b/runtime/common/src/weights/pallet_authority_members.rs index 06b47a3e5..679df9183 100644 --- a/runtime/common/src/weights/pallet_authority_members.rs +++ b/runtime/common/src/weights/pallet_authority_members.rs @@ -17,24 +17,29 @@ //! Autogenerated weights for `pallet_authority_members` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-04-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-05-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `benjamin-xps139380`, CPU: `Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("gdev-benchmark"), DB CACHE: 1024 // Executed Command: -// target/release/duniter +// ./target/release/duniter // benchmark // pallet -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_authority_members -// --extrinsic=* +// --chain +// gdev-benchmark // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --header=./file_header.txt -// --output=./runtime/common/src/weights/ +// --pallet +// pallet-authority-members +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --output=runtime/common/src/weights/ +// --header +// file_header.txt #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,13 +59,14 @@ impl<T: frame_system::Config> pallet_authority_members::WeightInfo for WeightInf // Storage: AuthorityMembers OnlineAuthorities (r:1 w:0) // Storage: AuthorityMembers AuthoritiesCounter (r:1 w:1) fn go_offline() -> Weight { - // Minimum execution time: 116_372 nanoseconds. - Weight::from_ref_time(120_732_000 as u64) + // Minimum execution time: 106_597 nanoseconds. + Weight::from_ref_time(109_880_000 as u64) .saturating_add(T::DbWeight::get().reads(7 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Identity IdentityIndexOf (r:1 w:0) // Storage: SmithMembership Membership (r:1 w:0) + // Storage: AuthorityMembers BlackList (r:1 w:0) // Storage: AuthorityMembers Members (r:1 w:0) // Storage: Session NextKeys (r:1 w:0) // Storage: AuthorityMembers IncomingAuthorities (r:1 w:1) @@ -68,9 +74,9 @@ impl<T: frame_system::Config> pallet_authority_members::WeightInfo for WeightInf // Storage: AuthorityMembers OnlineAuthorities (r:1 w:0) // Storage: AuthorityMembers AuthoritiesCounter (r:1 w:1) fn go_online() -> Weight { - // Minimum execution time: 144_006 nanoseconds. - Weight::from_ref_time(157_859_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) + // Minimum execution time: 132_009 nanoseconds. + Weight::from_ref_time(204_473_000 as u64) + .saturating_add(T::DbWeight::get().reads(9 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Identity IdentityIndexOf (r:1 w:0) @@ -82,8 +88,8 @@ impl<T: frame_system::Config> pallet_authority_members::WeightInfo for WeightInf // Storage: AuthorityMembers Members (r:1 w:1) // Storage: AuthorityMembers MustRotateKeysBefore (r:1 w:1) fn set_session_keys() -> Weight { - // Minimum execution time: 175_589 nanoseconds. - Weight::from_ref_time(178_397_000 as u64) + // Minimum execution time: 161_156 nanoseconds. + Weight::from_ref_time(182_210_000 as u64) .saturating_add(T::DbWeight::get().reads(11 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } @@ -98,9 +104,16 @@ impl<T: frame_system::Config> pallet_authority_members::WeightInfo for WeightInf // Storage: SmithMembership CounterForMembership (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn remove_member() -> Weight { - // Minimum execution time: 242_728 nanoseconds. - Weight::from_ref_time(296_778_000 as u64) + // Minimum execution time: 225_027 nanoseconds. + Weight::from_ref_time(243_550_000 as u64) .saturating_add(T::DbWeight::get().reads(9 as u64)) .saturating_add(T::DbWeight::get().writes(13 as u64)) } + // Storage: AuthorityMembers BlackList (r:1 w:1) + fn remove_member_from_blacklist() -> Weight { + // Minimum execution time: 60_023 nanoseconds. + Weight::from_ref_time(60_615_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } } diff --git a/runtime/g1/Cargo.toml b/runtime/g1/Cargo.toml index 03f057737..93b8f3464 100644 --- a/runtime/g1/Cargo.toml +++ b/runtime/g1/Cargo.toml @@ -123,6 +123,7 @@ pallet-provide-randomness = { path = '../../pallets/provide-randomness', default pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } +pallet-offences = { path = '../../pallets/offences', default-features = false } # crates.io codec = { package = "parity-scale-codec", version = "3.1.5", features = ["derive"], default-features = false } @@ -147,7 +148,6 @@ pallet-balances = { git = 'https://github.com/duniter/substrate', branch = 'duni pallet-collective = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-grandpa = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-im-online = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } -pallet-offences = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-multisig = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-preimage = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-proxy = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs index c6b533d68..fdb472726 100644 --- a/runtime/g1/src/lib.rs +++ b/runtime/g1/src/lib.rs @@ -259,7 +259,7 @@ construct_runtime!( Offences: pallet_offences::{Pallet, Storage, Event} = 12, Historical: session_historical::{Pallet} = 13, Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 14, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event} = 15, + Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned} = 15, ImOnline: pallet_im_online::{Pallet, Call, Storage, Event<T>, ValidateUnsigned, Config<T>} = 16, AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config} = 17, diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml index a30d763c4..8c8fd7788 100644 --- a/runtime/gdev/Cargo.toml +++ b/runtime/gdev/Cargo.toml @@ -143,6 +143,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false } pallet-identity = { path = '../../pallets/identity', default-features = false } pallet-membership = { path = '../../pallets/membership', default-features = false } +pallet-offences = { path = '../../pallets/offences', default-features = false } pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false } pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } @@ -172,7 +173,6 @@ pallet-balances = { git = 'https://github.com/duniter/substrate', branch = 'duni pallet-collective = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-grandpa = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-im-online = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } -pallet-offences = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-multisig = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-preimage = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-proxy = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 90350ab3d..770191a20 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -318,7 +318,7 @@ construct_runtime!( Offences: pallet_offences::{Pallet, Storage, Event} = 12, Historical: session_historical::{Pallet} = 13, Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 14, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event} = 15, + Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned} = 15, ImOnline: pallet_im_online::{Pallet, Call, Storage, Event<T>, ValidateUnsigned, Config<T>} = 16, AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config} = 17, diff --git a/runtime/gtest/Cargo.toml b/runtime/gtest/Cargo.toml index a3802c838..43ff07fb3 100644 --- a/runtime/gtest/Cargo.toml +++ b/runtime/gtest/Cargo.toml @@ -146,6 +146,7 @@ pallet-universal-dividend = { path = '../../pallets/universal-dividend', default pallet-session-benchmarking = { path = '../../pallets/session-benchmarking', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } +pallet-offences = { path = '../../pallets/offences', default-features = false } # crates.io codec = { package = "parity-scale-codec", version = "3.1.5", features = ["derive"], default-features = false } @@ -169,7 +170,6 @@ pallet-balances = { git = 'https://github.com/duniter/substrate', branch = 'duni pallet-collective = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-grandpa = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-im-online = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } -pallet-offences = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-multisig = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-preimage = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } pallet-proxy = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32', default-features = false } diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs index 3b94d4add..29ec85aae 100644 --- a/runtime/gtest/src/lib.rs +++ b/runtime/gtest/src/lib.rs @@ -275,7 +275,7 @@ construct_runtime!( Offences: pallet_offences::{Pallet, Storage, Event} = 12, Historical: session_historical::{Pallet} = 13, Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 14, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event} = 15, + Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned} = 15, ImOnline: pallet_im_online::{Pallet, Call, Storage, Event<T>, ValidateUnsigned, Config<T>} = 16, AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config} = 17, -- GitLab