From 91dd3220c745516effb7783578e604f18efd962c Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 20 Nov 2022 14:15:09 +0000 Subject: [PATCH 01/32] [Enhancement] P2 Staking Tracker --- primitives/staking/src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 703f0abe80458..97af484a330e9 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -75,6 +75,25 @@ pub struct Stake { pub active: T::Balance, } +/// A generic staking event listener. Current used for implementations involved in stake tracking. +/// Note that the interface is designed in a way that the events are fired post-action, so any +/// pre-action data that is needed needs to be passed to interface methods. +/// The rest of the data can be retrieved by using `StakingInterface`. +pub trait OnStakingUpdate { + /// Track ledger updates. + fn on_update_ledger(who: &T::AccountId, old_ledger: Stake); + /// Track nominators, those reinstated and also new ones. + fn on_nominator_add(who: &T::AccountId, old_nominations: Vec); + /// Track validators, those reinstated and new. + fn on_validator_add(who: &T::AccountId); + /// Track removed validators. Either chilled or those that became nominators instead. + fn on_validator_remove(who: &T::AccountId); // only fire this event when this is an actual Validator + /// Track removed nominators. + fn on_nominator_remove(who: &T::AccountId, nominations: Vec); // only fire this if this is an actual Nominator + /// Track those participants of staking system that are kicked out for whatever reason. + fn on_reaped(who: &T::AccountId); // -> basically `kill_stash` +} + /// A generic representation of a staking implementation. /// /// This interface uses the terminology of NPoS, but it is aims to be generic enough to cover other From e8127c78125aaec026ddb17a68c944a1715f7b53 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 20 Nov 2022 15:42:29 +0000 Subject: [PATCH 02/32] Staking interface changes --- frame/nomination-pools/src/mock.rs | 4 +++- frame/staking/src/pallet/impls.rs | 4 +++- primitives/staking/src/lib.rs | 25 +++++++++++++------------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 99d521df3241b..2538a4509137e 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -116,7 +116,9 @@ impl sp_staking::StakingInterface for StakingMock { unimplemented!("method currently not used in testing") } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake( + who: &Self::AccountId, + ) -> Result, DispatchError> { match ( UnbondingBalanceMap::get().get(who).map(|v| *v), BondedBalanceMap::get().get(who).map(|v| *v), diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 9be01dd823104..900a9b036bda5 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1551,7 +1551,9 @@ impl StakingInterface for Pallet { Self::current_era().unwrap_or(Zero::zero()) } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake( + who: &Self::AccountId, + ) -> Result, DispatchError> { Self::bonded(who) .and_then(|c| Self::ledger(c)) .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 97af484a330e9..291247cdbd707 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -57,9 +57,9 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. -pub struct Stake { +pub struct Stake { /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, + pub stash: AccountId, /// The total stake that `stash` has in the staking system. This includes the /// `active` stake, and any funds currently in the process of unbonding via /// [`StakingInterface::unbond`]. @@ -69,29 +69,29 @@ pub struct Stake { /// This is only guaranteed to reflect the amount locked by the staking system. If there are /// non-staking locks on the bonded pair's balance this amount is going to be larger in /// reality. - pub total: T::Balance, + pub total: Balance, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. - pub active: T::Balance, + pub active: Balance, } /// A generic staking event listener. Current used for implementations involved in stake tracking. /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. -pub trait OnStakingUpdate { +pub trait OnStakingUpdate { /// Track ledger updates. - fn on_update_ledger(who: &T::AccountId, old_ledger: Stake); + fn on_update_ledger(who: &AccountId, old_ledger: Stake); /// Track nominators, those reinstated and also new ones. - fn on_nominator_add(who: &T::AccountId, old_nominations: Vec); + fn on_nominator_add(who: &AccountId, old_nominations: Vec); /// Track validators, those reinstated and new. - fn on_validator_add(who: &T::AccountId); + fn on_validator_add(who: &AccountId); /// Track removed validators. Either chilled or those that became nominators instead. - fn on_validator_remove(who: &T::AccountId); // only fire this event when this is an actual Validator + fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator /// Track removed nominators. - fn on_nominator_remove(who: &T::AccountId, nominations: Vec); // only fire this if this is an actual Nominator + fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator /// Track those participants of staking system that are kicked out for whatever reason. - fn on_reaped(who: &T::AccountId); // -> basically `kill_stash` + fn on_reaped(who: &AccountId); // -> basically `kill_stash` } /// A generic representation of a staking implementation. @@ -131,7 +131,8 @@ pub trait StakingInterface { fn current_era() -> EraIndex; /// Returns the stake of `who`. - fn stake(who: &Self::AccountId) -> Result, DispatchError>; + fn stake(who: &Self::AccountId) + -> Result, DispatchError>; fn total_stake(who: &Self::AccountId) -> Result { Self::stake(who).map(|s| s.total) From c809c57611831ae2185230c9ccac7b315eee785a Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 20 Nov 2022 15:42:50 +0000 Subject: [PATCH 03/32] introducing stake-tracker pallet --- Cargo.lock | 23 ++++++++++++ Cargo.toml | 1 + frame/stake-tracker/Cargo.toml | 66 +++++++++++++++++++++++++++++++++ frame/stake-tracker/src/lib.rs | 67 ++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 frame/stake-tracker/Cargo.toml create mode 100644 frame/stake-tracker/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a22cfa8ba8dd6..074161f408395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6097,6 +6097,29 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-stake-tracker" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-staking", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", + "substrate-test-utils", +] + [[package]] name = "pallet-staking" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 956c106e0dc2d..5a904a14f0d7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,7 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/stake-tracker", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml new file mode 100644 index 0000000000000..b4342de16f940 --- /dev/null +++ b/frame/stake-tracker/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "pallet-stake-tracker" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME stake tracker pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { default-features = false, path = "../../primitives/staking" } + +frame-election-provider-support = { default-features = false, path = "../election-provider-support" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } + +[dev-dependencies] +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +pallet-staking = { path = "../staking" } +pallet-balances = { path = "../balances" } +pallet-timestamp = { path = "../timestamp" } + + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-staking/std", + "sp-runtime/std", + "sp-std/std", + + "frame-election-provider-support/std", + + "frame-benchmarking/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-staking/runtime-benchmarks" +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs new file mode 100644 index 0000000000000..43f4d4f7aed6c --- /dev/null +++ b/frame/stake-tracker/src/lib.rs @@ -0,0 +1,67 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pallet::Pallet; +use pallet::Config; +use sp_staking::{OnStakingUpdate, Stake}; + +#[frame_support::pallet] +pub mod pallet { + use frame_election_provider_support::{SortedListProvider, VoteWeight}; + use sp_staking::StakingInterface; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// We only need this to be able to pass to `Stake` as the type from StakingInterface is + /// ambiguous. + type Balance: PartialEq; + type Staking: StakingInterface; + + type VoterList: SortedListProvider; + + type TargetList: SortedListProvider; + } +} + +impl OnStakingUpdate for Pallet { + fn on_update_ledger(who: &T::AccountId, old_ledger: Stake) { + todo!() + } + + fn on_nominator_add(who: &T::AccountId, old_nominations: Vec) { + todo!() + } + + fn on_validator_add(who: &T::AccountId) { + todo!() + } + + fn on_validator_remove(who: &T::AccountId) { + todo!() + } + + fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { + todo!() + } + + fn on_reaped(who: &T::AccountId) { + todo!() + } +} From 99bd9d710d6687ae54775b25e6209a15befab190 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 21 Nov 2022 21:31:41 +0000 Subject: [PATCH 04/32] some trait re-shuffling --- Cargo.lock | 1 + frame/stake-tracker/src/lib.rs | 92 +++++++++++++++++++--- frame/support/src/traits/voting.rs | 73 +---------------- primitives/staking/Cargo.toml | 2 + primitives/staking/src/currency_to_vote.rs | 69 ++++++++++++++++ primitives/staking/src/lib.rs | 22 ++++-- 6 files changed, 168 insertions(+), 91 deletions(-) create mode 100644 primitives/staking/src/currency_to_vote.rs diff --git a/Cargo.lock b/Cargo.lock index 074161f408395..0c19df70294ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9948,6 +9948,7 @@ version = "4.0.0-dev" dependencies = [ "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-runtime", "sp-std", ] diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 43f4d4f7aed6c..1112ad963c0c3 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -16,11 +16,18 @@ // limitations under the License. use crate::pallet::Pallet; +use frame_election_provider_support::VoteWeight; +use frame_support::traits::{Currency, CurrencyToVote, Defensive}; use pallet::Config; -use sp_staking::{OnStakingUpdate, Stake}; +use sp_runtime::DispatchResult; +use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; + +/// The balance type of this pallet. +pub type BalanceOf = <::Staking as StakingInterface>::Balance; #[frame_support::pallet] pub mod pallet { + use crate::*; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use sp_staking::StakingInterface; @@ -29,9 +36,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// We only need this to be able to pass to `Stake` as the type from StakingInterface is - /// ambiguous. - type Balance: PartialEq; + /// This has to come from Staking::Currency + type Currency: Currency; + type Staking: StakingInterface; type VoterList: SortedListProvider; @@ -40,28 +47,87 @@ pub mod pallet { } } -impl OnStakingUpdate for Pallet { - fn on_update_ledger(who: &T::AccountId, old_ledger: Stake) { - todo!() +impl Pallet { + /// The total balance that can be slashed from a stash account as of right now. + pub(crate) fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { + // Weight note: consider making the stake accessible through stash. + T::Staking::stake(who).map(|l| l.active).unwrap_or_default() } - fn on_nominator_add(who: &T::AccountId, old_nominations: Vec) { - todo!() + pub(crate) fn to_vote(balance: BalanceOf) -> VoteWeight { + let total_issuance = T::Currency::total_issuance(); + T::Staking::CurrencyToVote::to_vote(balance, total_issuance) + } +} + +impl OnStakingUpdate> for Pallet { + fn on_update_ledger( + who: &T::AccountId, + prev_stake: Stake>, + ) -> DispatchResult { + let prev_active = prev_stake.map(|s| s.active); + let current_stake = T::Staking::stake(who)?; + + let update_target_list = |who: &T::AccountId| { + use sp_std::cmp::Ordering; + match ledger.active.cmp(&prev_active) { + Ordering::Greater => { + let _ = T::TargetList::on_increase(who, current_stake.active - prev_active) + .defensive(); + }, + Ordering::Less => { + let _ = T::TargetList::on_decrease(who, prev_active - current_stake.active) + .defensive(); + }, + Ordering::Equal => Ok(()), + }; + }; + + // if this is a nominator + if let Some(targets) = T::Staking::nominations(¤t_stake.stash) { + // update the target list. + for target in targets { + update_target_list(&target)?; + } + + // update the voter list. + let _ = + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_stake.active)) + .defensive_proof("any nominator should have an entry in the voter list.")?; + } + + if T::Staking::is_validator(¤t_stake.stash) { + update_target_list(¤t_stake.stash)?; + + let _ = + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_stake.active)) + .defensive_proof("any validator should have an entry in the voter list.")?; + } + + Ok(()) + } + + fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) -> DispatchResult { + // if Some(nominations) = T::Staking::nominations(who) { + // return Ok(()) + // } + // T::VoterList::on_insert(who.clone(), Self::weight_of(stash)).defensive(); + Ok(()) } - fn on_validator_add(who: &T::AccountId) { + fn on_validator_add(who: &T::AccountId) -> DispatchResult { todo!() } - fn on_validator_remove(who: &T::AccountId) { + fn on_validator_remove(who: &T::AccountId) -> DispatchResult { todo!() } - fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { + fn on_nominator_remove(who: &T::AccountId, nominations: Vec) -> DispatchResult { todo!() } - fn on_reaped(who: &T::AccountId) { + fn on_reaped(who: &T::AccountId) -> DispatchResult { todo!() } } diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 49ae3163d0cd1..223e7bc3c2bac 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -20,80 +20,11 @@ use crate::dispatch::{DispatchError, Parameter}; use codec::{HasCompact, MaxEncodedLen}; -use sp_arithmetic::{ - traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, - Perbill, -}; +use sp_arithmetic::Perbill; use sp_runtime::traits::Member; use sp_std::prelude::*; -/// A trait similar to `Convert` to convert values from `B` an abstract balance type -/// into u64 and back from u128. (This conversion is used in election and other places where complex -/// calculation over balance type is needed) -/// -/// Total issuance of the currency is passed in, but an implementation of this trait may or may not -/// use it. -/// -/// # WARNING -/// -/// the total issuance being passed in implies that the implementation must be aware of the fact -/// that its values can affect the outcome. This implies that if the vote value is dependent on the -/// total issuance, it should never ber written to storage for later re-use. -pub trait CurrencyToVote { - /// Convert balance to u64. - fn to_vote(value: B, issuance: B) -> u64; - - /// Convert u128 to balance. - fn to_currency(value: u128, issuance: B) -> B; -} - -/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. -/// -/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the -/// important cases: -/// -/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that -/// the factor will not have any effect. In this case, any account's balance is also less. Thus, -/// both of the conversions are basically an `as`; Any balance can fit in u64. -/// -/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and -/// divided upon conversion. -pub struct U128CurrencyToVote; - -impl U128CurrencyToVote { - fn factor(issuance: u128) -> u128 { - (issuance / u64::MAX as u128).max(1) - } -} - -impl CurrencyToVote for U128CurrencyToVote { - fn to_vote(value: u128, issuance: u128) -> u64 { - (value / Self::factor(issuance)).saturated_into() - } - - fn to_currency(value: u128, issuance: u128) -> u128 { - value.saturating_mul(Self::factor(issuance)) - } -} - -/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. -/// -/// # Warning -/// -/// This is designed to be used mostly for testing. Use with care, and think about the consequences. -pub struct SaturatingCurrencyToVote; - -impl + UniqueSaturatedFrom> CurrencyToVote - for SaturatingCurrencyToVote -{ - fn to_vote(value: B, _: B) -> u64 { - value.unique_saturated_into() - } - - fn to_currency(value: u128, _: B) -> B { - B::unique_saturated_from(value) - } -} +pub use sp_staking::currency_to_vote::*; pub trait VoteTally { fn new(_: Class) -> Self; diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 550c1485e992c..0a364b2ba2c1b 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -16,6 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } + sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs new file mode 100644 index 0000000000000..0cdbbf17e3a54 --- /dev/null +++ b/primitives/staking/src/currency_to_vote.rs @@ -0,0 +1,69 @@ +use sp_arithmetic::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; +use sp_runtime::SaturatedConversion; +/// A trait similar to `Convert` to convert values from `B` an abstract balance type +/// into u64 and back from u128. (This conversion is used in election and other places where complex +/// calculation over balance type is needed) +/// +/// Total issuance of the currency is passed in, but an implementation of this trait may or may not +/// use it. +/// +/// # WARNING +/// +/// the total issuance being passed in implies that the implementation must be aware of the fact +/// that its values can affect the outcome. This implies that if the vote value is dependent on the +/// total issuance, it should never ber written to storage for later re-use. +pub trait CurrencyToVote { + /// Convert balance to u64. + fn to_vote(value: B, issuance: B) -> u64; + + /// Convert u128 to balance. + fn to_currency(value: u128, issuance: B) -> B; +} + +/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. +/// +/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the +/// important cases: +/// +/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that +/// the factor will not have any effect. In this case, any account's balance is also less. Thus, +/// both of the conversions are basically an `as`; Any balance can fit in u64. +/// +/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and +/// divided upon conversion. +pub struct U128CurrencyToVote; + +impl U128CurrencyToVote { + fn factor(issuance: u128) -> u128 { + (issuance / u64::MAX as u128).max(1) + } +} + +impl CurrencyToVote for U128CurrencyToVote { + fn to_vote(value: u128, issuance: u128) -> u64 { + (value / Self::factor(issuance)).saturated_into() + } + + fn to_currency(value: u128, issuance: u128) -> u128 { + value.saturating_mul(Self::factor(issuance)) + } +} + +/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. +/// +/// # Warning +/// +/// This is designed to be used mostly for testing. Use with care, and think about the consequences. +pub struct SaturatingCurrencyToVote; + +impl + UniqueSaturatedFrom> CurrencyToVote + for SaturatingCurrencyToVote +{ + fn to_vote(value: B, _: B) -> u64 { + value.unique_saturated_into() + } + + fn to_currency(value: u128, _: B) -> B { + B::unique_saturated_from(value) + } +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 291247cdbd707..d459407290bca 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -20,11 +20,14 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use crate::currency_to_vote::CurrencyToVote; use sp_runtime::{DispatchError, DispatchResult}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub mod offence; +pub mod currency_to_vote; + /// Simple index type with which we can count sessions. pub type SessionIndex = u32; @@ -81,17 +84,17 @@ pub struct Stake { /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { /// Track ledger updates. - fn on_update_ledger(who: &AccountId, old_ledger: Stake); + fn on_update_ledger(who: &AccountId, prev_stake: Stake) -> DispatchResult; /// Track nominators, those reinstated and also new ones. - fn on_nominator_add(who: &AccountId, old_nominations: Vec); + fn on_nominator_add(who: &AccountId, prev_nominations: Vec) -> DispatchResult; /// Track validators, those reinstated and new. - fn on_validator_add(who: &AccountId); + fn on_validator_add(who: &AccountId) -> DispatchResult; /// Track removed validators. Either chilled or those that became nominators instead. - fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator + fn on_validator_remove(who: &AccountId) -> DispatchResult; // only fire this event when this is an actual Validator /// Track removed nominators. - fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator + fn on_nominator_remove(who: &AccountId, nominations: Vec) -> DispatchResult; // only fire this if this is an actual Nominator /// Track those participants of staking system that are kicked out for whatever reason. - fn on_reaped(who: &AccountId); // -> basically `kill_stash` + fn on_reaped(who: &AccountId) -> DispatchResult; // -> basically `kill_stash` } /// A generic representation of a staking implementation. @@ -105,6 +108,9 @@ pub trait StakingInterface { /// AccountId type used by the staking system type AccountId; + /// whatever + type CurrencyToVote: CurrencyToVote; + /// The minimum amount required to bond in order to set nomination intentions. This does not /// necessarily mean the nomination will be counted in an election, but instead just enough to /// be stored as a nominator. In other words, this is the minimum amount to register the @@ -196,8 +202,10 @@ pub trait StakingInterface { /// Checks whether an account `staker` has been exposed in an era. fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; + /// Checks whether or not this is a validator account. + fn is_validator(who: &Self::AccountId) -> bool; + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. - #[cfg(feature = "runtime-benchmarks")] fn nominations(who: Self::AccountId) -> Option>; #[cfg(feature = "runtime-benchmarks")] From 0d4a022427d5dd3899276ed94ee643f6b5a8fa82 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 21 Nov 2022 22:35:42 +0000 Subject: [PATCH 05/32] more trait fixes --- .../nomination-pools/benchmarking/src/lib.rs | 4 +- frame/stake-tracker/src/lib.rs | 62 +++++++++---------- frame/staking/src/pallet/impls.rs | 2 +- primitives/staking/src/lib.rs | 6 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 9b063539152b7..42cdb54492db4 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -644,12 +644,12 @@ frame_benchmarking::benchmarks! { .collect(); assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); + assert!(&T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); + assert!(&T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); } impl_benchmark_test_suite!( diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 1112ad963c0c3..fd31678ecca29 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::pallet::Pallet; -use frame_election_provider_support::VoteWeight; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote, Defensive}; use pallet::Config; use sp_runtime::DispatchResult; @@ -24,6 +24,7 @@ use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; /// The balance type of this pallet. pub type BalanceOf = <::Staking as StakingInterface>::Balance; +pub type AccountOf = <::Staking as StakingInterface>::AccountId; #[frame_support::pallet] pub mod pallet { @@ -37,49 +38,50 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// This has to come from Staking::Currency - type Currency: Currency; + type Currency: Currency, Balance = BalanceOf>; type Staking: StakingInterface; - type VoterList: SortedListProvider; + type VoterList: SortedListProvider, Score = VoteWeight>; - type TargetList: SortedListProvider; + type TargetList: SortedListProvider, Score = BalanceOf>; } } impl Pallet { /// The total balance that can be slashed from a stash account as of right now. - pub(crate) fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { + pub(crate) fn slashable_balance_of(who: AccountOf) -> BalanceOf { // Weight note: consider making the stake accessible through stash. - T::Staking::stake(who).map(|l| l.active).unwrap_or_default() + T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() } pub(crate) fn to_vote(balance: BalanceOf) -> VoteWeight { let total_issuance = T::Currency::total_issuance(); - T::Staking::CurrencyToVote::to_vote(balance, total_issuance) + ::CurrencyToVote::to_vote(balance, total_issuance) } } -impl OnStakingUpdate> for Pallet { +impl OnStakingUpdate, BalanceOf> for Pallet { fn on_update_ledger( - who: &T::AccountId, - prev_stake: Stake>, + who: &AccountOf, + prev_stake: Stake, BalanceOf>, ) -> DispatchResult { - let prev_active = prev_stake.map(|s| s.active); + let prev_active = prev_stake.active; let current_stake = T::Staking::stake(who)?; + let current_active = current_stake.active; - let update_target_list = |who: &T::AccountId| { + let update_target_list = |who: &AccountOf| { use sp_std::cmp::Ordering; - match ledger.active.cmp(&prev_active) { + match current_active.cmp(&prev_active) { Ordering::Greater => { - let _ = T::TargetList::on_increase(who, current_stake.active - prev_active) - .defensive(); + let _ = + T::TargetList::on_increase(who, current_active - prev_active).defensive(); }, Ordering::Less => { - let _ = T::TargetList::on_decrease(who, prev_active - current_stake.active) - .defensive(); + let _ = + T::TargetList::on_decrease(who, prev_active - current_active).defensive(); }, - Ordering::Equal => Ok(()), + Ordering::Equal => (), }; }; @@ -87,27 +89,25 @@ impl OnStakingUpdate> for Pallet { if let Some(targets) = T::Staking::nominations(¤t_stake.stash) { // update the target list. for target in targets { - update_target_list(&target)?; + update_target_list(&target); } // update the voter list. - let _ = - T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_stake.active)) - .defensive_proof("any nominator should have an entry in the voter list.")?; + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof("any nominator should have an entry in the voter list."); } if T::Staking::is_validator(¤t_stake.stash) { - update_target_list(¤t_stake.stash)?; + update_target_list(¤t_stake.stash); - let _ = - T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_stake.active)) - .defensive_proof("any validator should have an entry in the voter list.")?; + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof("any validator should have an entry in the voter list."); } Ok(()) } - fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) -> DispatchResult { + fn on_nominator_add(who: &AccountOf, prev_nominations: Vec>) -> DispatchResult { // if Some(nominations) = T::Staking::nominations(who) { // return Ok(()) // } @@ -115,19 +115,19 @@ impl OnStakingUpdate> for Pallet { Ok(()) } - fn on_validator_add(who: &T::AccountId) -> DispatchResult { + fn on_validator_add(who: &AccountOf) -> DispatchResult { todo!() } - fn on_validator_remove(who: &T::AccountId) -> DispatchResult { + fn on_validator_remove(who: &AccountOf) -> DispatchResult { todo!() } - fn on_nominator_remove(who: &T::AccountId, nominations: Vec) -> DispatchResult { + fn on_nominator_remove(who: &AccountOf, nominations: Vec>) -> DispatchResult { todo!() } - fn on_reaped(who: &T::AccountId) -> DispatchResult { + fn on_reaped(who: &AccountOf) -> DispatchResult { todo!() } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 900a9b036bda5..42c0abb8eed82 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1606,7 +1606,7 @@ impl StakingInterface for Pallet { } #[cfg(feature = "runtime-benchmarks")] - fn nominations(who: Self::AccountId) -> Option> { + fn nominations(who: &Self::AccountId) -> Option> { Nominators::::get(who).map(|n| n.targets.into_inner()) } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index d459407290bca..bad4f9ddedaa8 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -22,7 +22,7 @@ use crate::currency_to_vote::CurrencyToVote; use sp_runtime::{DispatchError, DispatchResult}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +use sp_std::{collections::btree_map::BTreeMap, ops::Sub, vec::Vec}; pub mod offence; @@ -103,7 +103,7 @@ pub trait OnStakingUpdate { /// implementations as well. pub trait StakingInterface { /// Balance type used by the staking system. - type Balance: PartialEq; + type Balance: Sub + Ord + PartialEq + Default + Copy; /// AccountId type used by the staking system type AccountId; @@ -206,7 +206,7 @@ pub trait StakingInterface { fn is_validator(who: &Self::AccountId) -> bool; /// Get the nominations of a stash, if they are a nominator, `None` otherwise. - fn nominations(who: Self::AccountId) -> Option>; + fn nominations(who: &Self::AccountId) -> Option>; #[cfg(feature = "runtime-benchmarks")] fn add_era_stakers( From c5bb19c7ef22b12dbae6eeccf392a595ce7a80f2 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 22 Nov 2022 17:34:38 +0000 Subject: [PATCH 06/32] more code --- frame/election-provider-support/src/lib.rs | 38 +++++---- .../nomination-pools/benchmarking/src/lib.rs | 4 +- frame/stake-tracker/Cargo.toml | 2 + frame/stake-tracker/src/lib.rs | 81 +++++++++++++------ primitives/staking/src/lib.rs | 14 +++- 5 files changed, 94 insertions(+), 45 deletions(-) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 38924a18e2f54..1391363e74b0f 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -473,16 +473,9 @@ where } } -/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. -/// -/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other -/// entity. -/// -/// The scores (see [`Self::Score`]) are ascending, the higher, the better. -/// -/// Something that implements this trait will do a best-effort sort over ids, and thus can be -/// used on the implementing side of [`ElectionDataProvider`]. -pub trait SortedListProvider { +/// Basically a read-only part of the methods for [`SortedListProvider`] designed to be used in +/// scenarios where the list is updated by another entity. +pub trait ReadOnlySortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; @@ -503,6 +496,25 @@ pub trait SortedListProvider { /// Return true if the list already contains `id`. fn contains(id: &AccountId) -> bool; + /// Get the score of `id`. + fn get_score(id: &AccountId) -> Result; + + /// Check internal state of list. Only meant for debugging. + fn try_state() -> Result<(), &'static str>; +} + +/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. +/// +/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other +/// entity. +/// +/// The scores (see [`Self::Score`]) are ascending, the higher, the better. +/// +/// Something that implements this trait will do a best-effort sort over ids, and thus can be +/// used on the implementing side of [`ElectionDataProvider`]. +/// +/// Inherits [`ReadOnlySortedListProvider`], which provides basic types and read-only methods. +pub trait SortedListProvider: ReadOnlySortedListProvider { /// Hook for inserting a new id. /// /// Implementation should return an error if duplicate item is being inserted. @@ -515,9 +527,6 @@ pub trait SortedListProvider { /// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise. fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>; - /// Get the score of `id`. - fn get_score(id: &AccountId) -> Result; - /// Same as `on_update`, but incorporate some increased score. fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> { let old_score = Self::get_score(id)?; @@ -564,9 +573,6 @@ pub trait SortedListProvider { /// unbounded amount of storage accesses. fn unsafe_clear(); - /// Check internal state of list. Only meant for debugging. - fn try_state() -> Result<(), &'static str>; - /// If `who` changes by the returned amount they are guaranteed to have a worst case change /// in their list position. #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 42cdb54492db4..7325e0dadb489 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -644,12 +644,12 @@ frame_benchmarking::benchmarks! { .collect(); assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(&T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); + assert!(&T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(&T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); + assert!(&T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); } impl_benchmark_test_suite!( diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index b4342de16f940..b228ee00d4d5f 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -36,6 +36,8 @@ sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } + [features] diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index fd31678ecca29..587734cabdff5 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -15,21 +15,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::Pallet; +use crate::pallet::{ApprovalStake, Pallet}; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote, Defensive}; use pallet::Config; -use sp_runtime::DispatchResult; +use sp_runtime::{DispatchResult, Saturating}; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; /// The balance type of this pallet. pub type BalanceOf = <::Staking as StakingInterface>::Balance; -pub type AccountOf = <::Staking as StakingInterface>::AccountId; #[frame_support::pallet] pub mod pallet { use crate::*; use frame_election_provider_support::{SortedListProvider, VoteWeight}; + use frame_support::{pallet_prelude::*, Twox64Concat}; + use sp_staking::StakingInterface; #[pallet::pallet] @@ -38,19 +39,31 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// This has to come from Staking::Currency - type Currency: Currency, Balance = BalanceOf>; + type Currency: Currency>; - type Staking: StakingInterface; + type Staking: StakingInterface; - type VoterList: SortedListProvider, Score = VoteWeight>; + type VoterList: SortedListProvider; - type TargetList: SortedListProvider, Score = BalanceOf>; + type TargetList: SortedListProvider>; } + + /// The map from validator stash key to their total approval stake. Not that this map is kept up + /// to date even if a validator chilled or turned into nominator. Entries from this map are only + /// ever removed if the stash is reaped. + /// + /// NOTE: This is currently a CountedStorageMap for debugging purposes. We might actually want + /// to revisit this once this pallet starts populating the actual `TargetList` used by + /// `Staking`. + #[pallet::storage] + #[pallet::getter(fn approval_stake)] + pub type ApprovalStake = + CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf, OptionQuery>; } impl Pallet { /// The total balance that can be slashed from a stash account as of right now. - pub(crate) fn slashable_balance_of(who: AccountOf) -> BalanceOf { + pub(crate) fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() } @@ -61,35 +74,37 @@ impl Pallet { } } -impl OnStakingUpdate, BalanceOf> for Pallet { +impl OnStakingUpdate> for Pallet { fn on_update_ledger( - who: &AccountOf, - prev_stake: Stake, BalanceOf>, + who: &T::AccountId, + prev_stake: Stake>, ) -> DispatchResult { let prev_active = prev_stake.active; let current_stake = T::Staking::stake(who)?; let current_active = current_stake.active; - let update_target_list = |who: &AccountOf| { + let update_approval_stake = |who: &T::AccountId| { + let mut approval_stake = Self::approval_stake(who).unwrap_or_default(); + use sp_std::cmp::Ordering; match current_active.cmp(&prev_active) { Ordering::Greater => { - let _ = - T::TargetList::on_increase(who, current_active - prev_active).defensive(); + approval_stake = approval_stake.saturating_add(current_active - prev_active); }, Ordering::Less => { - let _ = - T::TargetList::on_decrease(who, prev_active - current_active).defensive(); + approval_stake = approval_stake.saturating_sub(prev_active - current_active); }, - Ordering::Equal => (), + Ordering::Equal => return, }; + let _ = T::TargetList::on_update(who, approval_stake).defensive(); + ApprovalStake::::set(who, Some(approval_stake)); }; // if this is a nominator if let Some(targets) = T::Staking::nominations(¤t_stake.stash) { // update the target list. for target in targets { - update_target_list(&target); + update_approval_stake(&target); } // update the voter list. @@ -98,7 +113,7 @@ impl OnStakingUpdate, BalanceOf> for Pallet { } if T::Staking::is_validator(¤t_stake.stash) { - update_target_list(¤t_stake.stash); + update_approval_stake(¤t_stake.stash); let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) .defensive_proof("any validator should have an entry in the voter list."); @@ -107,7 +122,7 @@ impl OnStakingUpdate, BalanceOf> for Pallet { Ok(()) } - fn on_nominator_add(who: &AccountOf, prev_nominations: Vec>) -> DispatchResult { + fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) -> DispatchResult { // if Some(nominations) = T::Staking::nominations(who) { // return Ok(()) // } @@ -115,19 +130,35 @@ impl OnStakingUpdate, BalanceOf> for Pallet { Ok(()) } - fn on_validator_add(who: &AccountOf) -> DispatchResult { - todo!() + /// This should only be called if that stash isn't already a validator. Note, that if we want to + /// properly track ApprovalStake here - we need to make sure we subtract the validator stash + /// balance when they chill? + /// Why? Because we don't remove ApprovalStake when a validator chills and we need to make sure + /// their self-stake is up-to-date and not applied twice. + fn on_validator_add(who: &T::AccountId) -> DispatchResult { + let self_stake = Self::slashable_balance_of(who); + let new_stake = Self::approval_stake(who).unwrap_or_default().saturating_add(self_stake); + + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)).defensive(); + + // TODO: Make sure this always works. Among other things we need to make sure that when the + // migration is run we only have active validators in TargetList and not the chilled ones. + let _ = T::TargetList::on_insert(who.clone(), new_stake).defensive(); + ApprovalStake::::set(who, Some(new_stake)); + + Ok(()) } - fn on_validator_remove(who: &AccountOf) -> DispatchResult { + fn on_validator_remove(who: &T::AccountId) -> DispatchResult { todo!() } - fn on_nominator_remove(who: &AccountOf, nominations: Vec>) -> DispatchResult { + fn on_nominator_remove(who: &T::AccountId, nominations: Vec) -> DispatchResult { todo!() } - fn on_reaped(who: &AccountOf) -> DispatchResult { + fn on_reaped(who: &T::AccountId) -> DispatchResult { todo!() } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index bad4f9ddedaa8..24e86b4233a7a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -21,7 +21,9 @@ //! approaches in general. Definitions related to sessions, slashing, etc go here. use crate::currency_to_vote::CurrencyToVote; -use sp_runtime::{DispatchError, DispatchResult}; +use codec::{Encode, EncodeLike, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{DispatchError, DispatchResult, Saturating}; use sp_std::{collections::btree_map::BTreeMap, ops::Sub, vec::Vec}; pub mod offence; @@ -103,7 +105,15 @@ pub trait OnStakingUpdate { /// implementations as well. pub trait StakingInterface { /// Balance type used by the staking system. - type Balance: Sub + Ord + PartialEq + Default + Copy; + type Balance: Sub + + Ord + + PartialEq + + Default + + Copy + + MaxEncodedLen + + FullCodec + + TypeInfo + + Saturating; /// AccountId type used by the staking system type AccountId; From 5549a78ea2367fc5cf847dbca3e57ee832519cde Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 09:52:49 +0000 Subject: [PATCH 07/32] more tracking logic --- frame/stake-tracker/src/lib.rs | 73 +++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 587734cabdff5..29450df6e180e 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -15,9 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::{ApprovalStake, Pallet}; -use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::traits::{Currency, CurrencyToVote, Defensive}; +use crate::pallet::{ApprovalStake, Error, Pallet}; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; +use frame_support::{ + defensive, + traits::{Currency, CurrencyToVote, Defensive, DefensiveOption}, +}; use pallet::Config; use sp_runtime::{DispatchResult, Saturating}; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; @@ -48,6 +51,12 @@ pub mod pallet { type TargetList: SortedListProvider>; } + #[pallet::error] + pub enum Error { + /// ApprovalStake entry does not exist. + DoesNotExist, + } + /// The map from validator stash key to their total approval stake. Not that this map is kept up /// to date even if a validator chilled or turned into nominator. Entries from this map are only /// ever removed if the stash is reaped. @@ -123,10 +132,38 @@ impl OnStakingUpdate> for Pallet { } fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) -> DispatchResult { - // if Some(nominations) = T::Staking::nominations(who) { - // return Ok(()) - // } - // T::VoterList::on_insert(who.clone(), Self::weight_of(stash)).defensive(); + let nominations = T::Staking::nominations(who).unwrap_or_default(); + let new = nominations.iter().filter(|n| !prev_nominations.contains(&n)); + let obsolete = prev_nominations.iter().filter(|n| !nominations.contains(&n)); + + let update_approval_stake = |nomination: &T::AccountId, new_stake: BalanceOf| { + ApprovalStake::::set(&nomination, Some(new_stake)); + + // TODO: this is probably not always true, but we keep track of approval stake in the + // map anyway. + if T::TargetList::contains(&nomination) { + let _ = T::TargetList::on_update(&nomination, new_stake).defensive(); + } + }; + + for nomination in new { + // Create a new entry if it does not exist + let new_stake = Self::approval_stake(&nomination) + .defensive_ok_or(Error::::DoesNotExist)? + .saturating_add(Self::slashable_balance_of(who)); + + update_approval_stake(&nomination, new_stake); + } + + for nomination in obsolete { + // we should actually fail if an old nomination was not in the map, which should never + // happen. + let new_stake = Self::approval_stake(&nomination) + .defensive_ok_or(Error::::DoesNotExist)? + .saturating_sub(Self::slashable_balance_of(who)); + + update_approval_stake(&nomination, new_stake); + } Ok(()) } @@ -151,11 +188,29 @@ impl OnStakingUpdate> for Pallet { } fn on_validator_remove(who: &T::AccountId) -> DispatchResult { - todo!() + let _ = T::TargetList::on_remove(who).defensive(); + let _ = T::VoterList::on_remove(who).defensive(); + + ApprovalStake::::mutate(who, |x: &mut Option>| { + x.map(|b| b.saturating_sub(Self::slashable_balance_of(who))) + }) + .ok_or(Error::::DoesNotExist)?; + + Ok(()) } fn on_nominator_remove(who: &T::AccountId, nominations: Vec) -> DispatchResult { - todo!() + let score = Self::slashable_balance_of(who); + let _ = T::VoterList::on_remove(who).defensive(); + for validator in nominations { + ApprovalStake::::mutate(&validator, |x: &mut Option>| { + x.map(|b| b.saturating_sub(score)) + }) + .ok_or(Error::::DoesNotExist) + .defensive(); + } + + Ok(()) } fn on_reaped(who: &T::AccountId) -> DispatchResult { From 1666bdc99a1825fa5a8a009be94e237ccbba5c9e Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 09:57:07 +0000 Subject: [PATCH 08/32] add reaped handling --- frame/stake-tracker/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 29450df6e180e..6657b0e1c61a7 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -214,6 +214,8 @@ impl OnStakingUpdate> for Pallet { } fn on_reaped(who: &T::AccountId) -> DispatchResult { - todo!() + ApprovalStake::::remove(who); + + Ok(()) } } From e7cfb35a8e98837ef3d7957bbf6f6c8c671b64f4 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 10:40:11 +0000 Subject: [PATCH 09/32] implement ReadOnlySortedListProvider --- frame/bags-list/fuzzer/src/main.rs | 2 +- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 1 + frame/bags-list/remote-tests/src/snapshot.rs | 2 +- frame/bags-list/remote-tests/src/try_state.rs | 2 +- frame/bags-list/src/lib.rs | 24 +++++---- frame/nomination-pools/src/mock.rs | 11 ++-- frame/staking/src/migrations.rs | 3 +- frame/staking/src/pallet/impls.rs | 51 ++++++++++++------- frame/staking/src/pallet/mod.rs | 3 +- 10 files changed, 63 insertions(+), 38 deletions(-) diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 9f7ca464cc2b8..9d31a0884871b 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -27,7 +27,7 @@ //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; use honggfuzz::fuzz; use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder}; diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index fc25e3b65ddb1..41424fd5e52c9 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -17,7 +17,7 @@ //! Utilities for remote-testing pallet-bags-list. -use frame_election_provider_support::ScoreProvider; +use frame_election_provider_support::{ReadOnlySortedListProvider, ScoreProvider}; use pallet_bags_list::Instance1; use sp_std::prelude::*; diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index b013472b4c90e..50e800e14cae7 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -17,6 +17,7 @@ //! Test to check the migration of the voter bag. use crate::{RuntimeT, LOG_TARGET}; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::traits::PalletInfoAccess; use pallet_staking::Nominators; use remote_externalities::{Builder, Mode, OnlineConfig}; diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index cfe065924bd92..256c42c83e042 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -16,7 +16,7 @@ //! Test to execute the snapshot using the voter bag. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index d3fb63f045a64..7ac032777caf4 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -16,7 +16,7 @@ //! Test to execute the sanity-check of the voter bag. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{ storage::generator::StorageMap, traits::{Get, PalletInfoAccess}, diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 2b48fbf0ca630..18ce39c281493 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -54,7 +54,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::FullCodec; -use frame_election_provider_support::{ScoreProvider, SortedListProvider}; +use frame_election_provider_support::{ + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, +}; use frame_system::ensure_signed; use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded, StaticLookup}; use sp_std::prelude::*; @@ -297,7 +299,7 @@ impl, I: 'static> Pallet { } } -impl, I: 'static> SortedListProvider for Pallet { +impl, I: 'static> ReadOnlySortedListProvider for Pallet { type Error = ListError; type Score = T::Score; @@ -319,15 +321,19 @@ impl, I: 'static> SortedListProvider for Pallet fn contains(id: &T::AccountId) -> bool { List::::contains(id) } + fn get_score(id: &T::AccountId) -> Result { + List::::get_score(id) + } + fn try_state() -> Result<(), &'static str> { + List::::try_state() + } +} +impl, I: 'static> SortedListProvider for Pallet { fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { List::::insert(id, score) } - fn get_score(id: &T::AccountId) -> Result { - List::::get_score(id) - } - fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { Pallet::::do_rebag(id, new_score).map(|_| ()) } @@ -346,10 +352,6 @@ impl, I: 'static> SortedListProvider for Pallet List::::unsafe_regenerate(all, score_of) } - fn try_state() -> Result<(), &'static str> { - List::::try_state() - } - fn unsafe_clear() { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. // I.e. because it can lead to many storage accesses. @@ -381,7 +383,7 @@ impl, I: 'static> SortedListProvider for Pallet } impl, I: 'static> ScoreProvider for Pallet { - type Score = as SortedListProvider>::Score; + type Score = as ReadOnlySortedListProvider>::Score; fn score(id: &T::AccountId) -> T::Score { Node::::get(id).map(|node| node.score()).unwrap_or_default() diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 2538a4509137e..eba89bcf2bcaf 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -3,7 +3,7 @@ use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; use sp_runtime::FixedU128; -use sp_staking::Stake; +use sp_staking::{currency_to_vote::U128CurrencyToVote, Stake}; pub type BlockNumber = u64; pub type AccountId = u128; @@ -107,8 +107,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - #[cfg(feature = "runtime-benchmarks")] - fn nominations(_: Self::AccountId) -> Option> { + fn nominations(_: &Self::AccountId) -> Option> { Nominations::get() } @@ -155,6 +154,12 @@ impl sp_staking::StakingInterface for StakingMock { fn set_current_era(_era: EraIndex) { unimplemented!("method currently not used in testing") } + + type CurrencyToVote = U128CurrencyToVote; + + fn is_validator(_who: &Self::AccountId) -> bool { + false + } } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index f2ccb4f8b096f..3888ca50e0789 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -210,6 +210,7 @@ pub mod v10 { pub mod v9 { use super::*; + use frame_election_provider_support::ReadOnlySortedListProvider; #[cfg(feature = "try-runtime")] use frame_support::codec::{Decode, Encode}; #[cfg(feature = "try-runtime")] @@ -282,7 +283,7 @@ pub mod v9 { pub mod v8 { use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; - use frame_election_provider_support::SortedListProvider; + use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; use frame_support::traits::Get; #[cfg(feature = "try-runtime")] diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 42c0abb8eed82..da9ca4cdda47d 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,8 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, - SortedListProvider, VoteWeight, VoterOf, + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ dispatch::WithPostDispatchInfo, @@ -1366,9 +1366,9 @@ impl ScoreProvider for Pallet { /// does not provide validators in sorted order. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list`]. pub struct UseValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseValidatorsMap { - type Score = BalanceOf; +impl ReadOnlySortedListProvider for UseValidatorsMap { type Error = (); + type Score = BalanceOf; /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { @@ -1390,13 +1390,20 @@ impl SortedListProvider for UseValidatorsMap { fn contains(id: &T::AccountId) -> bool { Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id).into()) + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } +} + +impl SortedListProvider for UseValidatorsMap { fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id).into()) - } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1412,9 +1419,7 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { - Ok(()) - } + fn unsafe_clear() { #[allow(deprecated)] Validators::::remove_all(); @@ -1430,7 +1435,8 @@ impl SortedListProvider for UseValidatorsMap { /// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list]. pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseNominatorsAndValidatorsMap { + +impl ReadOnlySortedListProvider for UseNominatorsAndValidatorsMap { type Error = (); type Score = VoteWeight; @@ -1464,13 +1470,20 @@ impl SortedListProvider for UseNominatorsAndValidatorsM fn contains(id: &T::AccountId) -> bool { Nominators::::contains_key(id) || Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id)) + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } +} + +impl SortedListProvider for UseNominatorsAndValidatorsMap { fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id)) - } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1486,9 +1499,6 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { - Ok(()) - } fn unsafe_clear() { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a @@ -1605,7 +1615,6 @@ impl StakingInterface for Pallet { Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } - #[cfg(feature = "runtime-benchmarks")] fn nominations(who: &Self::AccountId) -> Option> { Nominators::::get(who).map(|n| n.targets.into_inner()) } @@ -1628,6 +1637,12 @@ impl StakingInterface for Pallet { fn set_current_era(era: EraIndex) { CurrentEra::::put(era); } + + type CurrencyToVote = T::CurrencyToVote; + + fn is_validator(who: &Self::AccountId) -> bool { + Validators::::contains_key(who) + } } #[cfg(any(test, feature = "try-runtime"))] diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 8fddba2150370..dcdfd55dea4ce 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -18,7 +18,8 @@ //! Staking FRAME Pallet. use frame_election_provider_support::{ - ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, + ElectionProvider, ElectionProviderBase, ReadOnlySortedListProvider, SortedListProvider, + VoteWeight, }; use frame_support::{ dispatch::Codec, From 54b2e3301dfea14590b7c7f02232ddb113457c75 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 11:01:22 +0000 Subject: [PATCH 10/32] more import fixes --- frame/staking/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index dcb861e2ce419..f8a07f1090322 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{ConfigOp, Pallet as Staking}; use testing_utils::*; use codec::Decode; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; use frame_support::{ dispatch::UnfilteredDispatchable, pallet_prelude::*, From da8f780b17181dc2c0b689810d1252a1e06cefba Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 11:11:18 +0000 Subject: [PATCH 11/32] more import fixes --- frame/bags-list/src/list/tests.rs | 2 +- frame/staking/src/tests.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 966ea1a74c71c..e7512c600f580 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -20,7 +20,7 @@ use crate::{ mock::{test_utils::*, *}, ListBags, ListNodes, }; -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; fn node( diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6609b9087637d..0ef9f9768c7be 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,7 +18,9 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; -use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; +use frame_election_provider_support::{ + ElectionProvider, ReadOnlySortedListProvider, SortedListProvider, Support, +}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, @@ -5101,7 +5103,7 @@ fn change_of_max_nominations() { mod sorted_list_provider { use super::*; - use frame_election_provider_support::SortedListProvider; + use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; #[test] fn re_nominate_does_not_change_counters_or_list() { From c89d8ec7d0083640a1e4de1c489e01fd88bc8354 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 23 Nov 2022 15:01:30 +0000 Subject: [PATCH 12/32] integrate EventListener into Staking --- frame/stake-tracker/src/lib.rs | 10 +++++----- frame/staking/src/pallet/impls.rs | 20 ++++++++++++++++---- frame/staking/src/pallet/mod.rs | 11 ++++++++++- primitives/staking/src/lib.rs | 3 ++- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 6657b0e1c61a7..9b9f9d35991d7 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -84,12 +84,9 @@ impl Pallet { } impl OnStakingUpdate> for Pallet { - fn on_update_ledger( - who: &T::AccountId, - prev_stake: Stake>, - ) -> DispatchResult { + fn on_update_ledger(prev_stake: Stake>) -> DispatchResult { + let current_stake = T::Staking::stake(&prev_stake.stash)?; let prev_active = prev_stake.active; - let current_stake = T::Staking::stake(who)?; let current_active = current_stake.active; let update_approval_stake = |who: &T::AccountId| { @@ -164,6 +161,9 @@ impl OnStakingUpdate> for Pallet { update_approval_stake(&nomination, new_stake); } + let _ = + T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))) + .defensive_unwrap_or_default(); Ok(()) } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index da9ca4cdda47d..c2b495f0d14fb 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -38,7 +38,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, Stake, StakingInterface, + EraIndex, OnStakingUpdate, SessionIndex, Stake, StakingInterface, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -230,8 +230,13 @@ impl Pallet { /// /// This will also update the stash lock. pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { + let prev_ledger = Self::ledger(controller) + .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) + .unwrap_or_default(); + T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); + let _ = T::EventListener::on_update_ledger(prev_ledger); } /// Chill a stash account. @@ -620,6 +625,7 @@ impl Pallet { >::remove(stash); Self::do_remove_validator(stash); Self::do_remove_nominator(stash); + let _ = T::EventListener::on_reaped(stash); frame_system::Pallet::::dec_consumers(stash); @@ -835,12 +841,15 @@ impl Pallet { /// to `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - if !Nominators::::contains_key(who) { + let prev_nominations = Self::nominations(who); + if !prev_nominations.is_none() { // maybe update sorted list. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); + let _ = T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()) + .defensive(); debug_assert_eq!( Nominators::::count() + Validators::::count(), @@ -857,12 +866,13 @@ impl Pallet { /// `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - let outcome = if Nominators::::contains_key(who) { + let outcome = if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); + let _ = T::EventListener::on_nominator_remove(who, nominations); true } else { - false + true }; debug_assert_eq!( @@ -885,6 +895,7 @@ impl Pallet { // maybe update sorted list. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); + let _ = T::EventListener::on_validator_add(who); } Validators::::insert(who, prefs); @@ -905,6 +916,7 @@ impl Pallet { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); + let _ = T::EventListener::on_validator_remove(who); true } else { false diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index dcdfd55dea4ce..b6dae9df41d78 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -240,7 +240,16 @@ pub mod pallet { /// recorded. This implies that what comes out of iterating this list MIGHT NOT BE AN ACTIVE /// VALIDATOR. type TargetList: SortedListProvider>; - + /// This is the implementation that's going to maintain approval stake tracking, `VoterList` + /// and `TargetList` in near future. Currently running in test mode, which means it's + /// maintaining totally detached versions of both lists. + /// Once this becomes the source of truth - we should make sure `TargetList` and `VoterList` + /// are passed to Staking as `ReadOnlySortedListProvider`. + /// + /// NOTE: Once we have more implementors of EventListener - this could be done similarly to + /// `OnRuntimeUpdate` - just a tuple of all sorts of pallets implementing the trait that all + /// react to the interface methods being called or ignore them. + type EventListener: sp_staking::OnStakingUpdate>; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can /// have. Effectively determines how many unique eras a staker may be /// unbonding in. diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 24e86b4233a7a..3a8d0818d3614 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -62,6 +62,7 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. +#[derive(Default)] pub struct Stake { /// The stash account whose balance is actually locked and at stake. pub stash: AccountId, @@ -86,7 +87,7 @@ pub struct Stake { /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { /// Track ledger updates. - fn on_update_ledger(who: &AccountId, prev_stake: Stake) -> DispatchResult; + fn on_update_ledger(prev_stake: Stake) -> DispatchResult; /// Track nominators, those reinstated and also new ones. fn on_nominator_add(who: &AccountId, prev_nominations: Vec) -> DispatchResult; /// Track validators, those reinstated and new. From 7fcafe017bfd4b6291b2fd8c20d00614133eca57 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 28 Nov 2022 15:09:04 +0000 Subject: [PATCH 13/32] modify the EventListener interface for better Staking compat --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 1 + bin/node/runtime/src/lib.rs | 24 ++++++++++++++++++++++++ frame/stake-tracker/src/lib.rs | 26 ++++++++++++++++++++++---- frame/staking/src/pallet/impls.rs | 10 ++++++---- primitives/staking/src/lib.rs | 5 ++++- 6 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c19df70294ce..c2ef5a443af0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3418,6 +3418,7 @@ dependencies = [ "pallet-session", "pallet-session-benchmarking", "pallet-society", + "pallet-stake-tracker", "pallet-staking", "pallet-staking-reward-curve", "pallet-state-trie-migration", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index dcc59ce750934..028dd18e7338a 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -93,6 +93,7 @@ pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } +pallet-stake-tracker = {version = "4.0.0-dev", default-features = false, path = "../../../frame/stake-tracker"} pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index cff33e0918981..36a8a250b9f41 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -546,6 +546,13 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { type MaxValidators = ConstU32<1000>; } +impl pallet_stake_tracker::Config for Runtime { + type Currency = Balances; + type Staking = Staking; + type VoterList: pallet_staking::UseValidatorsMap; + type TargetList: pallet_staking::UseValidatorsMap; +} + impl pallet_staking::Config for Runtime { type MaxNominations = MaxNominations; type Currency = Balances; @@ -579,6 +586,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; + type EventListener = StakeTracker; } impl pallet_fast_unstake::Config for Runtime { @@ -757,6 +765,21 @@ impl pallet_bags_list::Config for Runtime { type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } +// parameter_types! { +// pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; +// } +// +// type TargetBagsListInstance = pallet_bags_list::Instance2; +// impl pallet_bags_list::Config for Runtime { +// type RuntimeEvent = RuntimeEvent; +// /// The voter bags-list is loosely kept up to date, and the real source of truth for the score +// /// of each node is the staking pallet. +// type ScoreProvider = Staking; +// type BagThresholds = BagThresholds; +// type Score = VoteWeight; +// type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +// } + parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); @@ -1682,6 +1705,7 @@ construct_runtime!( RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, FastUnstake: pallet_fast_unstake, + StakeTracker: pallet_stake_tracker, } ); diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 9b9f9d35991d7..38ebcaf5fd17a 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -16,7 +16,9 @@ // limitations under the License. use crate::pallet::{ApprovalStake, Error, Pallet}; -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, +}; use frame_support::{ defensive, traits::{Currency, CurrencyToVote, Defensive, DefensiveOption}, @@ -84,9 +86,12 @@ impl Pallet { } impl OnStakingUpdate> for Pallet { - fn on_update_ledger(prev_stake: Stake>) -> DispatchResult { - let current_stake = T::Staking::stake(&prev_stake.stash)?; - let prev_active = prev_stake.active; + fn on_update_ledger( + who: &T::AccountId, + prev_stake: Option>>, + ) -> DispatchResult { + let current_stake = T::Staking::stake(who)?; + let prev_active = prev_stake.map(|s| s.active).unwrap_or_default(); let current_active = current_stake.active; let update_approval_stake = |who: &T::AccountId| { @@ -219,3 +224,16 @@ impl OnStakingUpdate> for Pallet { Ok(()) } } + +impl ScoreProvider for Pallet { + type Score = BalanceOf; + + fn score(who: &T::AccountId) -> Self::Score { + Self::approval_stake(who).unwrap_or_default() + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_score_of(_: &T::AccountId, _: Self::Score) { + todo!() + } +} diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c2b495f0d14fb..befd845bcc503 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -230,13 +230,15 @@ impl Pallet { /// /// This will also update the stash lock. pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { - let prev_ledger = Self::ledger(controller) - .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) - .unwrap_or_default(); + let prev_ledger = Self::ledger(controller).map(|l| Stake { + stash: l.stash, + total: l.total, + active: l.active, + }); T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); - let _ = T::EventListener::on_update_ledger(prev_ledger); + let _ = T::EventListener::on_update_ledger(&ledger.stash, prev_ledger); } /// Chill a stash account. diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 3a8d0818d3614..3867e94c68feb 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -87,7 +87,10 @@ pub struct Stake { /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { /// Track ledger updates. - fn on_update_ledger(prev_stake: Stake) -> DispatchResult; + fn on_update_ledger( + who: &AccountId, + prev_stake: Option>, + ) -> DispatchResult; /// Track nominators, those reinstated and also new ones. fn on_nominator_add(who: &AccountId, prev_nominations: Vec) -> DispatchResult; /// Track validators, those reinstated and new. From bed5ad48b9be7e546878b68faef1620889cc2b2d Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 28 Nov 2022 19:44:02 +0000 Subject: [PATCH 14/32] generate bags, fix node-runtime --- Cargo.lock | 1 - bin/node/runtime/Cargo.toml | 1 + bin/node/runtime/src/lib.rs | 47 ++- bin/node/runtime/src/voter_bags.rs | 584 +++++++++++++++++++---------- frame/stake-tracker/Cargo.toml | 5 +- frame/stake-tracker/src/lib.rs | 9 +- 6 files changed, 433 insertions(+), 214 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efc5c5090cfbf..80e661a0fd110 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6119,7 +6119,6 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", "sp-io", "sp-runtime", "sp-staking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 028dd18e7338a..b5548af30f196 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -173,6 +173,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-staking/std", + "pallet-stake-tracker/std", "pallet-state-trie-migration/std", "sp-session/std", "pallet-sudo/std", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9c2eb62f42b7e..83cf66586569d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -549,8 +549,8 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { impl pallet_stake_tracker::Config for Runtime { type Currency = Balances; type Staking = Staking; - type VoterList: pallet_staking::UseValidatorsMap; - type TargetList: pallet_staking::UseValidatorsMap; + type VoterList = VoterListTracker; + type TargetList = TargetList; } impl pallet_staking::Config for Runtime { @@ -751,7 +751,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { } parameter_types! { - pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; + pub const BagThresholds: &'static [VoteWeight] = &voter_bags::THRESHOLDS; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -765,20 +765,30 @@ impl pallet_bags_list::Config for Runtime { type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } -// parameter_types! { -// pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; -// } -// -// type TargetBagsListInstance = pallet_bags_list::Instance2; -// impl pallet_bags_list::Config for Runtime { -// type RuntimeEvent = RuntimeEvent; -// /// The voter bags-list is loosely kept up to date, and the real source of truth for the score -// /// of each node is the staking pallet. -// type ScoreProvider = Staking; -// type BagThresholds = BagThresholds; -// type Score = VoteWeight; -// type WeightInfo = pallet_bags_list::weights::SubstrateWeight; -// } +/// A special VoterList instance used to test the StakeTracker. +type VoterBagsListTrackerInstance = pallet_bags_list::Instance3; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + /// The voter bags-list is loosely kept up to date, and the real source of truth for the score + /// of each node is the staking pallet. + type ScoreProvider = Staking; + type BagThresholds = BagThresholds; + type Score = VoteWeight; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} + +parameter_types! { + pub const BagThresholdsBalances: &'static [Balance] = &voter_bags::THRESHOLDS_BALANCES; +} + +type TargetBagsListInstance = pallet_bags_list::Instance2; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ScoreProvider = StakeTracker; + type BagThresholds = BagThresholdsBalances; + type Score = Balance; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; @@ -1694,6 +1704,9 @@ construct_runtime!( Uniques: pallet_uniques, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, + TargetList: pallet_bags_list::, + // A special VoterList tha is used just to see if the stake-tracker works correctly. + VoterListTracker: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, diff --git a/bin/node/runtime/src/voter_bags.rs b/bin/node/runtime/src/voter_bags.rs index eb540c27abcc7..c318b58d33168 100644 --- a/bin/node/runtime/src/voter_bags.rs +++ b/bin/node/runtime/src/voter_bags.rs @@ -17,7 +17,7 @@ //! Autogenerated bag thresholds. //! -//! Generated on 2022-08-15T19:26:59.939787+00:00 +//! Generated on 2022-11-28T19:35:48.578176+00:00 //! Arguments //! Total issuance: 100000000000000 //! Minimum balance: 100000000000000 @@ -35,195 +35,399 @@ pub const CONSTANT_RATIO: f64 = 1.0628253590743408; /// Upper thresholds delimiting the bag list. pub const THRESHOLDS: [u64; 200] = [ - 100_000_000_000_000, - 106_282_535_907_434, - 112_959_774_389_150, - 120_056_512_776_105, - 127_599_106_300_477, - 135_615_565_971_369, - 144_135_662_599_590, - 153_191_037_357_827, - 162_815_319_286_803, - 173_044_250_183_800, - 183_915_817_337_347, - 195_470_394_601_017, - 207_750_892_330_229, - 220_802_916_738_890, - 234_674_939_267_673, - 249_418_476_592_914, - 265_088_281_944_639, - 281_742_548_444_211, - 299_443_125_216_738, - 318_255_747_080_822, - 338_250_278_668_647, - 359_500_973_883_001, - 382_086_751_654_776, - 406_091_489_025_036, - 431_604_332_640_068, - 458_720_029_816_222, - 487_539_280_404_019, - 518_169_110_758_247, - 550_723_271_202_866, - 585_322_658_466_782, - 622_095_764_659_305, - 661_179_154_452_653, - 702_717_972_243_610, - 746_866_481_177_808, - 793_788_636_038_393, - 843_658_692_126_636, - 896_661_852_395_681, - 952_994_955_240_703, - 1_012_867_205_499_736, - 1_076_500_951_379_881, - 1_144_132_510_194_192, - 1_216_013_045_975_769, - 1_292_409_502_228_280, - 1_373_605_593_276_862, - 1_459_902_857_901_004, - 1_551_621_779_162_291, - 1_649_102_974_585_730, - 1_752_708_461_114_642, - 1_862_822_999_536_805, - 1_979_855_523_374_646, - 2_104_240_657_545_975, - 2_236_440_332_435_128, - 2_376_945_499_368_703, - 2_526_277_953_866_680, - 2_684_992_273_439_945, - 2_853_677_877_130_641, - 3_032_961_214_443_876, - 3_223_508_091_799_862, - 3_426_026_145_146_232, - 3_641_267_467_913_124, - 3_870_031_404_070_482, - 4_113_167_516_660_186, - 4_371_578_742_827_277, - 4_646_224_747_067_156, - 4_938_125_485_141_739, - 5_248_364_991_899_922, - 5_578_095_407_069_235, - 5_928_541_253_969_291, - 6_301_003_987_036_955, - 6_696_866_825_051_405, - 7_117_599_888_008_300, - 7_564_765_656_719_910, - 8_040_024_775_416_580, - 8_545_142_218_898_723, - 9_081_993_847_142_344, - 9_652_573_371_700_016, - 10_258_999_759_768_490, - 10_903_525_103_419_522, - 11_588_542_983_217_942, - 12_316_597_357_287_042, - 13_090_392_008_832_678, - 13_912_800_587_211_472, - 14_786_877_279_832_732, - 15_715_868_154_526_436, - 16_703_223_214_499_558, - 17_752_609_210_649_358, - 18_867_923_258_814_856, - 20_053_307_312_537_008, - 21_313_163_545_075_252, - 22_652_170_697_804_756, - 24_075_301_455_707_600, - 25_587_840_914_485_432, - 27_195_406_207_875_088, - 28_903_967_368_057_400, - 30_719_869_496_628_636, - 32_649_856_328_471_220, - 34_701_095_276_033_064, - 36_881_204_047_022_752, - 39_198_278_934_370_992, - 41_660_924_883_519_016, - 44_278_287_448_695_240, - 47_060_086_756_856_400, - 50_016_653_605_425_536, - 53_158_967_827_883_320, - 56_498_699_069_691_424, - 60_048_250_125_977_912, - 63_820_803_001_928_304, - 67_830_367_866_937_216, - 72_091_835_084_322_176, - 76_621_030_509_822_880, - 81_434_774_264_248_528, - 86_550_943_198_537_824, - 91_988_537_283_208_848, - 97_767_750_168_749_840, - 103_910_044_178_992_000, - 110_438_230_015_967_792, - 117_376_551_472_255_616, - 124_750_775_465_407_920, - 132_588_287_728_824_640, - 140_918_194_514_440_064, - 149_771_430_684_917_568, - 159_180_874_596_775_264, - 169_181_470_201_085_280, - 179_810_356_815_193_344, - 191_107_007_047_393_216, - 203_113_373_386_768_288, - 215_874_044_002_592_672, - 229_436_408_331_885_600, - 243_850_833_070_063_392, - 259_170_849_218_267_264, - 275_453_350_882_006_752, - 292_758_806_559_399_232, - 311_151_483_703_668_992, - 330_699_687_393_865_920, - 351_476_014_000_157_824, - 373_557_620_785_735_808, - 397_026_512_446_556_096, - 421_969_845_653_044_224, - 448_480_252_724_740_928, - 476_656_185_639_923_904, - 506_602_281_657_757_760, - 538_429_751_910_786_752, - 572_256_794_410_890_176, - 608_209_033_002_485_632, - 646_419_983_893_124_352, - 687_031_551_494_039_552, - 730_194_555_412_054_016, - 776_069_290_549_944_960, - 824_826_122_395_314_176, - 876_646_119_708_695_936, - 931_721_726_960_522_368, - 990_257_479_014_182_144, - 1_052_470_760_709_299_712, - 1_118_592_614_166_106_112, - 1_188_868_596_808_997_376, - 1_263_559_693_295_730_432, - 1_342_943_284_738_898_688, - 1_427_314_178_819_094_784, - 1_516_985_704_615_302_400, - 1_612_290_876_218_400_768, - 1_713_583_629_449_105_408, - 1_821_240_136_273_157_632, - 1_935_660_201_795_120_128, - 2_057_268_749_018_809_600, - 2_186_517_396_888_336_384, - 2_323_886_137_470_138_880, - 2_469_885_118_504_583_168, - 2_625_056_537_947_004_416, - 2_789_976_657_533_970_944, - 2_965_257_942_852_572_160, - 3_151_551_337_860_326_400, - 3_349_548_682_302_620_672, - 3_559_985_281_005_267_968, - 3_783_642_634_583_792_128, - 4_021_351_341_710_503_936, - 4_273_994_183_717_548_544, - 4_542_509_402_991_247_872, - 4_827_894_187_332_742_144, - 5_131_208_373_224_844_288, - 5_453_578_381_757_959_168, - 5_796_201_401_831_965_696, - 6_160_349_836_169_256_960, - 6_547_376_026_650_146_816, - 6_958_717_276_519_173_120, - 7_395_901_188_113_309_696, - 7_860_551_335_934_872_576, - 8_354_393_296_137_270_272, - 8_879_261_054_815_360_000, - 9_437_103_818_898_946_048, + 100_000_000_000_000, + 106_282_535_907_434, + 112_959_774_389_150, + 120_056_512_776_105, + 127_599_106_300_477, + 135_615_565_971_369, + 144_135_662_599_590, + 153_191_037_357_827, + 162_815_319_286_803, + 173_044_250_183_800, + 183_915_817_337_347, + 195_470_394_601_017, + 207_750_892_330_229, + 220_802_916_738_890, + 234_674_939_267_673, + 249_418_476_592_914, + 265_088_281_944_639, + 281_742_548_444_211, + 299_443_125_216_738, + 318_255_747_080_822, + 338_250_278_668_647, + 359_500_973_883_001, + 382_086_751_654_776, + 406_091_489_025_036, + 431_604_332_640_068, + 458_720_029_816_222, + 487_539_280_404_019, + 518_169_110_758_247, + 550_723_271_202_866, + 585_322_658_466_782, + 622_095_764_659_305, + 661_179_154_452_653, + 702_717_972_243_610, + 746_866_481_177_808, + 793_788_636_038_393, + 843_658_692_126_636, + 896_661_852_395_681, + 952_994_955_240_703, + 1_012_867_205_499_736, + 1_076_500_951_379_881, + 1_144_132_510_194_192, + 1_216_013_045_975_769, + 1_292_409_502_228_280, + 1_373_605_593_276_862, + 1_459_902_857_901_004, + 1_551_621_779_162_291, + 1_649_102_974_585_730, + 1_752_708_461_114_642, + 1_862_822_999_536_805, + 1_979_855_523_374_646, + 2_104_240_657_545_975, + 2_236_440_332_435_128, + 2_376_945_499_368_703, + 2_526_277_953_866_680, + 2_684_992_273_439_945, + 2_853_677_877_130_641, + 3_032_961_214_443_876, + 3_223_508_091_799_862, + 3_426_026_145_146_232, + 3_641_267_467_913_124, + 3_870_031_404_070_482, + 4_113_167_516_660_186, + 4_371_578_742_827_277, + 4_646_224_747_067_156, + 4_938_125_485_141_739, + 5_248_364_991_899_922, + 5_578_095_407_069_235, + 5_928_541_253_969_291, + 6_301_003_987_036_955, + 6_696_866_825_051_405, + 7_117_599_888_008_300, + 7_564_765_656_719_910, + 8_040_024_775_416_580, + 8_545_142_218_898_723, + 9_081_993_847_142_344, + 9_652_573_371_700_016, + 10_258_999_759_768_490, + 10_903_525_103_419_522, + 11_588_542_983_217_942, + 12_316_597_357_287_042, + 13_090_392_008_832_678, + 13_912_800_587_211_472, + 14_786_877_279_832_732, + 15_715_868_154_526_436, + 16_703_223_214_499_558, + 17_752_609_210_649_358, + 18_867_923_258_814_856, + 20_053_307_312_537_008, + 21_313_163_545_075_252, + 22_652_170_697_804_756, + 24_075_301_455_707_600, + 25_587_840_914_485_432, + 27_195_406_207_875_088, + 28_903_967_368_057_400, + 30_719_869_496_628_636, + 32_649_856_328_471_220, + 34_701_095_276_033_064, + 36_881_204_047_022_752, + 39_198_278_934_370_992, + 41_660_924_883_519_016, + 44_278_287_448_695_240, + 47_060_086_756_856_400, + 50_016_653_605_425_536, + 53_158_967_827_883_320, + 56_498_699_069_691_424, + 60_048_250_125_977_912, + 63_820_803_001_928_304, + 67_830_367_866_937_216, + 72_091_835_084_322_176, + 76_621_030_509_822_880, + 81_434_774_264_248_528, + 86_550_943_198_537_824, + 91_988_537_283_208_848, + 97_767_750_168_749_840, + 103_910_044_178_992_000, + 110_438_230_015_967_792, + 117_376_551_472_255_616, + 124_750_775_465_407_920, + 132_588_287_728_824_640, + 140_918_194_514_440_064, + 149_771_430_684_917_568, + 159_180_874_596_775_264, + 169_181_470_201_085_280, + 179_810_356_815_193_344, + 191_107_007_047_393_216, + 203_113_373_386_768_288, + 215_874_044_002_592_672, + 229_436_408_331_885_600, + 243_850_833_070_063_392, + 259_170_849_218_267_264, + 275_453_350_882_006_752, + 292_758_806_559_399_232, + 311_151_483_703_668_992, + 330_699_687_393_865_920, + 351_476_014_000_157_824, + 373_557_620_785_735_808, + 397_026_512_446_556_096, + 421_969_845_653_044_224, + 448_480_252_724_740_928, + 476_656_185_639_923_904, + 506_602_281_657_757_760, + 538_429_751_910_786_752, + 572_256_794_410_890_176, + 608_209_033_002_485_632, + 646_419_983_893_124_352, + 687_031_551_494_039_552, + 730_194_555_412_054_016, + 776_069_290_549_944_960, + 824_826_122_395_314_176, + 876_646_119_708_695_936, + 931_721_726_960_522_368, + 990_257_479_014_182_144, + 1_052_470_760_709_299_712, + 1_118_592_614_166_106_112, + 1_188_868_596_808_997_376, + 1_263_559_693_295_730_432, + 1_342_943_284_738_898_688, + 1_427_314_178_819_094_784, + 1_516_985_704_615_302_400, + 1_612_290_876_218_400_768, + 1_713_583_629_449_105_408, + 1_821_240_136_273_157_632, + 1_935_660_201_795_120_128, + 2_057_268_749_018_809_600, + 2_186_517_396_888_336_384, + 2_323_886_137_470_138_880, + 2_469_885_118_504_583_168, + 2_625_056_537_947_004_416, + 2_789_976_657_533_970_944, + 2_965_257_942_852_572_160, + 3_151_551_337_860_326_400, + 3_349_548_682_302_620_672, + 3_559_985_281_005_267_968, + 3_783_642_634_583_792_128, + 4_021_351_341_710_503_936, + 4_273_994_183_717_548_544, + 4_542_509_402_991_247_872, + 4_827_894_187_332_742_144, + 5_131_208_373_224_844_288, + 5_453_578_381_757_959_168, + 5_796_201_401_831_965_696, + 6_160_349_836_169_256_960, + 6_547_376_026_650_146_816, + 6_958_717_276_519_173_120, + 7_395_901_188_113_309_696, + 7_860_551_335_934_872_576, + 8_354_393_296_137_270_272, + 8_879_261_054_815_360_000, + 9_437_103_818_898_946_048, + 10_029_993_254_943_105_024, + 10_660_131_182_698_121_216, + 11_329_857_752_030_707_712, + 12_041_660_133_563_240_448, + 12_798_181_755_305_525_248, + 13_602_232_119_581_272_064, + 14_456_797_236_706_498_560, + 15_365_050_714_167_523_328, + 16_330_365_542_480_556_032, + 17_356_326_621_502_140_416, + 18_446_744_073_709_551_615, +]; + +/// Upper thresholds delimiting the bag list. +pub const THRESHOLDS_BALANCES: [u128; 200] = [ + 100_000_000_000_000, + 106_282_535_907_434, + 112_959_774_389_150, + 120_056_512_776_105, + 127_599_106_300_477, + 135_615_565_971_369, + 144_135_662_599_590, + 153_191_037_357_827, + 162_815_319_286_803, + 173_044_250_183_800, + 183_915_817_337_347, + 195_470_394_601_017, + 207_750_892_330_229, + 220_802_916_738_890, + 234_674_939_267_673, + 249_418_476_592_914, + 265_088_281_944_639, + 281_742_548_444_211, + 299_443_125_216_738, + 318_255_747_080_822, + 338_250_278_668_647, + 359_500_973_883_001, + 382_086_751_654_776, + 406_091_489_025_036, + 431_604_332_640_068, + 458_720_029_816_222, + 487_539_280_404_019, + 518_169_110_758_247, + 550_723_271_202_866, + 585_322_658_466_782, + 622_095_764_659_305, + 661_179_154_452_653, + 702_717_972_243_610, + 746_866_481_177_808, + 793_788_636_038_393, + 843_658_692_126_636, + 896_661_852_395_681, + 952_994_955_240_703, + 1_012_867_205_499_736, + 1_076_500_951_379_881, + 1_144_132_510_194_192, + 1_216_013_045_975_769, + 1_292_409_502_228_280, + 1_373_605_593_276_862, + 1_459_902_857_901_004, + 1_551_621_779_162_291, + 1_649_102_974_585_730, + 1_752_708_461_114_642, + 1_862_822_999_536_805, + 1_979_855_523_374_646, + 2_104_240_657_545_975, + 2_236_440_332_435_128, + 2_376_945_499_368_703, + 2_526_277_953_866_680, + 2_684_992_273_439_945, + 2_853_677_877_130_641, + 3_032_961_214_443_876, + 3_223_508_091_799_862, + 3_426_026_145_146_232, + 3_641_267_467_913_124, + 3_870_031_404_070_482, + 4_113_167_516_660_186, + 4_371_578_742_827_277, + 4_646_224_747_067_156, + 4_938_125_485_141_739, + 5_248_364_991_899_922, + 5_578_095_407_069_235, + 5_928_541_253_969_291, + 6_301_003_987_036_955, + 6_696_866_825_051_405, + 7_117_599_888_008_300, + 7_564_765_656_719_910, + 8_040_024_775_416_580, + 8_545_142_218_898_723, + 9_081_993_847_142_344, + 9_652_573_371_700_016, + 10_258_999_759_768_490, + 10_903_525_103_419_522, + 11_588_542_983_217_942, + 12_316_597_357_287_042, + 13_090_392_008_832_678, + 13_912_800_587_211_472, + 14_786_877_279_832_732, + 15_715_868_154_526_436, + 16_703_223_214_499_558, + 17_752_609_210_649_358, + 18_867_923_258_814_856, + 20_053_307_312_537_008, + 21_313_163_545_075_252, + 22_652_170_697_804_756, + 24_075_301_455_707_600, + 25_587_840_914_485_432, + 27_195_406_207_875_088, + 28_903_967_368_057_400, + 30_719_869_496_628_636, + 32_649_856_328_471_220, + 34_701_095_276_033_064, + 36_881_204_047_022_752, + 39_198_278_934_370_992, + 41_660_924_883_519_016, + 44_278_287_448_695_240, + 47_060_086_756_856_400, + 50_016_653_605_425_536, + 53_158_967_827_883_320, + 56_498_699_069_691_424, + 60_048_250_125_977_912, + 63_820_803_001_928_304, + 67_830_367_866_937_216, + 72_091_835_084_322_176, + 76_621_030_509_822_880, + 81_434_774_264_248_528, + 86_550_943_198_537_824, + 91_988_537_283_208_848, + 97_767_750_168_749_840, + 103_910_044_178_992_000, + 110_438_230_015_967_792, + 117_376_551_472_255_616, + 124_750_775_465_407_920, + 132_588_287_728_824_640, + 140_918_194_514_440_064, + 149_771_430_684_917_568, + 159_180_874_596_775_264, + 169_181_470_201_085_280, + 179_810_356_815_193_344, + 191_107_007_047_393_216, + 203_113_373_386_768_288, + 215_874_044_002_592_672, + 229_436_408_331_885_600, + 243_850_833_070_063_392, + 259_170_849_218_267_264, + 275_453_350_882_006_752, + 292_758_806_559_399_232, + 311_151_483_703_668_992, + 330_699_687_393_865_920, + 351_476_014_000_157_824, + 373_557_620_785_735_808, + 397_026_512_446_556_096, + 421_969_845_653_044_224, + 448_480_252_724_740_928, + 476_656_185_639_923_904, + 506_602_281_657_757_760, + 538_429_751_910_786_752, + 572_256_794_410_890_176, + 608_209_033_002_485_632, + 646_419_983_893_124_352, + 687_031_551_494_039_552, + 730_194_555_412_054_016, + 776_069_290_549_944_960, + 824_826_122_395_314_176, + 876_646_119_708_695_936, + 931_721_726_960_522_368, + 990_257_479_014_182_144, + 1_052_470_760_709_299_712, + 1_118_592_614_166_106_112, + 1_188_868_596_808_997_376, + 1_263_559_693_295_730_432, + 1_342_943_284_738_898_688, + 1_427_314_178_819_094_784, + 1_516_985_704_615_302_400, + 1_612_290_876_218_400_768, + 1_713_583_629_449_105_408, + 1_821_240_136_273_157_632, + 1_935_660_201_795_120_128, + 2_057_268_749_018_809_600, + 2_186_517_396_888_336_384, + 2_323_886_137_470_138_880, + 2_469_885_118_504_583_168, + 2_625_056_537_947_004_416, + 2_789_976_657_533_970_944, + 2_965_257_942_852_572_160, + 3_151_551_337_860_326_400, + 3_349_548_682_302_620_672, + 3_559_985_281_005_267_968, + 3_783_642_634_583_792_128, + 4_021_351_341_710_503_936, + 4_273_994_183_717_548_544, + 4_542_509_402_991_247_872, + 4_827_894_187_332_742_144, + 5_131_208_373_224_844_288, + 5_453_578_381_757_959_168, + 5_796_201_401_831_965_696, + 6_160_349_836_169_256_960, + 6_547_376_026_650_146_816, + 6_958_717_276_519_173_120, + 7_395_901_188_113_309_696, + 7_860_551_335_934_872_576, + 8_354_393_296_137_270_272, + 8_879_261_054_815_360_000, + 9_437_103_818_898_946_048, 10_029_993_254_943_105_024, 10_660_131_182_698_121_216, 11_329_857_752_030_707_712, diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index b228ee00d4d5f..171984b594d42 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -23,20 +23,19 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-staking = { default-features = false, path = "../../primitives/staking" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } frame-election-provider-support = { default-features = false, path = "../election-provider-support" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } + diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 38ebcaf5fd17a..d1ed0410c4365 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -15,7 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::{ApprovalStake, Error, Pallet}; +#![cfg_attr(not(feature = "std"), no_std)] + use frame_election_provider_support::{ ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, }; @@ -23,10 +24,12 @@ use frame_support::{ defensive, traits::{Currency, CurrencyToVote, Defensive, DefensiveOption}, }; -use pallet::Config; +pub use pallet::*; use sp_runtime::{DispatchResult, Saturating}; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; +use sp_std::vec::Vec; + /// The balance type of this pallet. pub type BalanceOf = <::Staking as StakingInterface>::Balance; @@ -212,7 +215,7 @@ impl OnStakingUpdate> for Pallet { x.map(|b| b.saturating_sub(score)) }) .ok_or(Error::::DoesNotExist) - .defensive(); + .defensive()?; } Ok(()) From 84ec3d44e93cb9f6ee94d526fe3fdfa555a97fc1 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 8 Dec 2022 12:50:31 +0100 Subject: [PATCH 15/32] missing license --- primitives/staking/src/currency_to_vote.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs index 0cdbbf17e3a54..d04f651ae3068 100644 --- a/primitives/staking/src/currency_to_vote.rs +++ b/primitives/staking/src/currency_to_vote.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use sp_arithmetic::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; use sp_runtime::SaturatedConversion; /// A trait similar to `Convert` to convert values from `B` an abstract balance type From f65e5bdcb6e8d301a2250dfe26bf1af86f3c7e85 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 8 Dec 2022 13:29:06 +0100 Subject: [PATCH 16/32] comment fixes --- bin/node/runtime/src/lib.rs | 8 +++++--- frame/election-provider-support/src/lib.rs | 8 ++++++-- frame/stake-tracker/src/lib.rs | 8 +++----- primitives/staking/src/lib.rs | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 83cf66586569d..cb99b424c3e61 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -765,12 +765,12 @@ impl pallet_bags_list::Config for Runtime { type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } -/// A special VoterList instance used to test the StakeTracker. +// A special VoterList instance used to test the StakeTracker. type VoterBagsListTrackerInstance = pallet_bags_list::Instance3; impl pallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; - /// The voter bags-list is loosely kept up to date, and the real source of truth for the score - /// of each node is the staking pallet. + // The voter bags-list is loosely kept up to date, and the real source of truth for the score + // of each node is the staking pallet. type ScoreProvider = Staking; type BagThresholds = BagThresholds; type Score = VoteWeight; @@ -781,6 +781,8 @@ parameter_types! { pub const BagThresholdsBalances: &'static [Balance] = &voter_bags::THRESHOLDS_BALANCES; } +// A TargetList, currently populated by StakeTracker, once this has been tested enough - this will +// be used by pallet-staking as a list of electable targets. type TargetBagsListInstance = pallet_bags_list::Instance2; impl pallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 1391363e74b0f..09692ad7cdc33 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -473,8 +473,12 @@ where } } -/// Basically a read-only part of the methods for [`SortedListProvider`] designed to be used in -/// scenarios where the list is updated by another entity. +/// A trait that defines a set of functions that allow read access to the underlying storage +/// and the types those functions depend upon. +/// +/// Initially a part of [`SortedListProvider`], it allows for restricting a consumer to read-only +/// operations. This is particularly useful in case the list is populated by one entity and +/// read by another. pub trait ReadOnlySortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index d1ed0410c4365..dd330b2a41054 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -66,9 +66,9 @@ pub mod pallet { /// to date even if a validator chilled or turned into nominator. Entries from this map are only /// ever removed if the stash is reaped. /// - /// NOTE: This is currently a CountedStorageMap for debugging purposes. We might actually want - /// to revisit this once this pallet starts populating the actual `TargetList` used by - /// `Staking`. + /// NOTE: This is currently a [`CountedStorageMap`] for debugging purposes. We might actually want + /// to revisit this once this pallet starts populating the actual [`Config::TargetList`] used by + /// [`Config::Staking`]. #[pallet::storage] #[pallet::getter(fn approval_stake)] pub type ApprovalStake = @@ -144,8 +144,6 @@ impl OnStakingUpdate> for Pallet { let update_approval_stake = |nomination: &T::AccountId, new_stake: BalanceOf| { ApprovalStake::::set(&nomination, Some(new_stake)); - // TODO: this is probably not always true, but we keep track of approval stake in the - // map anyway. if T::TargetList::contains(&nomination) { let _ = T::TargetList::on_update(&nomination, new_stake).defensive(); } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 3867e94c68feb..7be8b5913ae0a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -81,7 +81,7 @@ pub struct Stake { pub active: Balance, } -/// A generic staking event listener. Current used for implementations involved in stake tracking. +/// A generic staking event listener. Currently used for implementations involved in stake tracking. /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. From 345585ed5c91d77fb6e3e5f871399b67937e7556 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 9 Dec 2022 18:50:14 +0100 Subject: [PATCH 17/32] NominatorsHelper --- frame/staking/src/pallet/mod.rs | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index b6dae9df41d78..7ab50dd690051 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -355,6 +355,47 @@ pub mod pallet { pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; + /// A helper struct with some explicit functions for nominators that are existing in storage, but + /// cannot be decoded. See [`Nominators`] for more info. + pub struct NominatorsHelper(sp_std::marker::PhantomData); + impl NominatorsHelper { + /// True IFF nominator exists, and is decodable. + pub(crate) fn contains_decodable(who: &T::AccountId) -> bool { + Nominators::::get(who).is_some() + } + + /// True if the nominator exists, decodable or not. + pub(crate) fn contains_any(who: &T::AccountId) -> bool { + Nominators::::contains_key(who) + } + + /// True IF nominators exists, and is NOT decodable. + pub(crate) fn is_undecodable(who: &T::AccountId) -> bool { + Self::contains_any(who) && !Self::contains_decodable(who) + } + + /// Iterate over all nominators, decodable or not. + pub(crate) fn iter_all() -> impl Iterator)> { + Nominators::::iter_keys().filter_map(|who| { + if let Some(u) = Self::get_any(&who) { + Some((who, u)) + } else { + frame_support::defensive!( + "any nominator that has a key must be unbounded accessible" + ); + None + } + }) + } + + /// Get the nominator `who`, decodable or not. + pub(crate) fn get_any(who: &T::AccountId) -> Option> { + frame_support::storage::unhashed::get::>( + &Nominators::::hashed_key_for(who), + ) + } + } + /// The maximum nominator count before we stop allowing new validators to join. /// /// When this value is not set, no limits are enforced. From 09550de9d256f1b55898285b88112ff7cd157479 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 21 Dec 2022 15:38:46 +0100 Subject: [PATCH 18/32] Introduce initial TargetList migration --- Cargo.lock | 15 ++ Cargo.toml | 1 + frame/stake-tracker/migrations/Cargo.toml | 34 ++++ frame/stake-tracker/migrations/README.md | 8 + frame/stake-tracker/migrations/src/lib.rs | 235 ++++++++++++++++++++++ frame/stake-tracker/src/lib.rs | 13 +- frame/staking/src/pallet/mod.rs | 41 ---- 7 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 frame/stake-tracker/migrations/Cargo.toml create mode 100644 frame/stake-tracker/migrations/README.md create mode 100644 frame/stake-tracker/migrations/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2e091c733b588..ab0a667497999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5834,6 +5834,21 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-stake-tracker-migrations" +version = "4.0.0-dev" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-stake-tracker", + "pallet-staking", + "parity-scale-codec", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-staking" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 444c2766b808c..6761d2d546ae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,7 @@ members = [ "frame/staking/reward-curve", "frame/staking/reward-fn", "frame/stake-tracker", + "frame/stake-tracker/migrations", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/frame/stake-tracker/migrations/Cargo.toml b/frame/stake-tracker/migrations/Cargo.toml new file mode 100644 index 0000000000000..6fb287dad3f58 --- /dev/null +++ b/frame/stake-tracker/migrations/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pallet-stake-tracker-migrations" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME stake tracker migrations submodule" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +pallet-staking = { path = "../../staking", default-features = false } +pallet-stake-tracker = { path = "../", default-features = false } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } +log = { version = "0.4.17", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } + + +[features] +default = ["std"] +std = [ + "pallet-staking/std", + "pallet-stake-tracker/std", +] diff --git a/frame/stake-tracker/migrations/README.md b/frame/stake-tracker/migrations/README.md new file mode 100644 index 0000000000000..185bb33cf41c3 --- /dev/null +++ b/frame/stake-tracker/migrations/README.md @@ -0,0 +1,8 @@ +# Migrations submodule + +The Migrations submodule implements storage migrations for pallet-stake-tracker. + +## Overview + +The Migrations submodule depends on pallet-stake-tracker and pallet-staking as it needs access to +storage of both of these pallets, while avoiding direct dependency from one to another. diff --git a/frame/stake-tracker/migrations/src/lib.rs b/frame/stake-tracker/migrations/src/lib.rs new file mode 100644 index 0000000000000..8813c32f79872 --- /dev/null +++ b/frame/stake-tracker/migrations/src/lib.rs @@ -0,0 +1,235 @@ +pub(crate) const LOG_TARGET: &str = "runtime::stake-tracker"; + +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("πŸ’ΈπŸ§", $patter), $(, $values)* + ) + }; +} + +pub mod v2 {} +//TODO: Introduce try-state +pub mod v1 { + use codec::{Decode, Encode}; + use frame_election_provider_support::SortedListProvider; + use frame_support::{ + pallet_prelude::*, sp_io, storage_alias, traits::OnRuntimeUpgrade, CloneNoBound, EqNoBound, + PartialEqNoBound, + }; + use pallet_stake_tracker::{ApprovalStake, BalanceOf, Pallet}; + use pallet_staking::{Nominations, Nominators, Validators}; + use sp_runtime::Saturating; + use std::collections::BTreeMap; + + /// Migration implementation that injects all validators into sorted list. + pub struct InjectValidatorsApprovalStakeIntoTargetList(PhantomData); + + #[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, Default)] + #[codec(mel_bound())] + pub struct MigrationState { + pub last_key: Vec, + pub prefix: Vec, + pub done: bool, + } + + #[storage_alias] + type MigrationV1StateNominators = + StorageValue, MigrationState, OptionQuery>; + + #[storage_alias] + type MigrationV1StateValidators = + StorageValue, MigrationState, OptionQuery>; + + impl + InjectValidatorsApprovalStakeIntoTargetList + { + fn nominator_state() -> MigrationState { + MigrationV1StateNominators::::get().unwrap_or(MigrationState { + last_key: >::map_storage_final_prefix(), + prefix: >::map_storage_final_prefix(), + done: false, + }) + } + + fn set_nominator_state(state: MigrationState) { + MigrationV1StateNominators::::set(Some(state)) + } + + fn validator_state() -> MigrationState { + MigrationV1StateValidators::::get().unwrap_or(MigrationState { + last_key: >::map_storage_final_prefix(), + prefix: >::map_storage_final_prefix(), + done: false, + }) + } + + fn set_validator_state(state: MigrationState) { + MigrationV1StateValidators::::set(Some(state)) + } + + pub(crate) fn build_approval_stakes( + max_iterations: Weight, + ) -> (BTreeMap>, Weight) { + let mut iterations: Weight = Weight::default(); + let mut approval_stakes = BTreeMap::>::new(); + let mut leftover_weight = T::BlockWeights::get().max_block; + + let mut nominator_state = Self::nominator_state(); + + if !nominator_state.done { + // each iteration = 2 reads + 2 reads = 4 reads + while let Some(next_key) = + sp_io::storage::next_key(nominator_state.last_key.as_ref()) + { + if !next_key.starts_with(&nominator_state.prefix) { + // Nothing else to iterate. Save the state and bail. + nominator_state.done = true; + Self::set_nominator_state(nominator_state.clone()); + break + } + + // Should be safe due to the check above. + let mut account_raw = + next_key.strip_prefix(nominator_state.prefix.as_slice()).unwrap(); + let who = T::AccountId::decode(&mut account_raw).unwrap(); + + match storage::unhashed::get::>(&next_key) { + Some(nominations) => { + let stake = Pallet::::slashable_balance_of(&who); + + for target in nominations.targets { + let current = approval_stakes.entry(target).or_default(); + *current = current.saturating_add(stake); + } + + iterations = iterations.saturating_add(Weight::from_ref_time(1)); + nominator_state.last_key = next_key; + leftover_weight = + max_iterations.saturating_sub(iterations).saturating_sub( + Weight::from_ref_time(approval_stakes.len() as u64), + ); + + if leftover_weight.all_lte(Weight::default()) { + // We ran out of weight, also taking into account writing + // approval_stakes here. Save the state and bail. + Self::set_nominator_state(nominator_state.clone()); + + return (approval_stakes, leftover_weight) + } + }, + None => { + log!(warn, "an un-decodable nomination detected."); + break + }, + }; + } + } + + let mut validator_state = Self::validator_state(); + + if !validator_state.done { + // each iteration = 1 read + 2 reads = 3 reads + while let Some(next_key) = + sp_io::storage::next_key(validator_state.last_key.as_ref()) + { + if !next_key.starts_with(&validator_state.prefix) { + // Nothing else to iterate. Save the state and bail. + validator_state.done = true; + Self::set_validator_state(validator_state.clone()); + break + } + + // Should be safe due to the check above. + let mut account_raw = + next_key.strip_prefix(validator_state.prefix.as_slice()).unwrap(); + + // TODO: not sure if this works, gotta see. Should be safe as all the keys in + // Validators map are AccountId. If not - let it fail. + let who = T::AccountId::decode(&mut account_raw).unwrap(); + let stake = Pallet::::slashable_balance_of(&who); + let current = approval_stakes.entry(who).or_default(); + *current = current.saturating_add(stake); + iterations = iterations.saturating_add(Weight::from_ref_time(1)); + validator_state.last_key = next_key; + + leftover_weight = max_iterations + .saturating_sub(iterations) + .saturating_sub(Weight::from_ref_time(approval_stakes.len() as u64)); + + if leftover_weight.all_lte(Weight::default()) { + // We ran out of weight, also taking into account writing + // approval_stakes here. Save the state and bail. + Self::set_validator_state(validator_state.clone()); + + return (approval_stakes, leftover_weight) + } + } + } + + (approval_stakes, leftover_weight) + } + } + + impl OnRuntimeUpgrade + for InjectValidatorsApprovalStakeIntoTargetList + { + fn on_runtime_upgrade() -> Weight { + // We have to set this manually, because we need this migration to happen in order for + // the pallet to get all the data from staking-pallet. + let current = StorageVersion::new(1); + let onchain = Pallet::::on_chain_storage_version(); + + if current == 1 && onchain == 0 { + let max_weight = T::BlockWeights::get().max_block; + // this is an approximation + let max_iterations = max_weight.saturating_div(4); + // TODO: maybe write this in a multi-block fashion. + let (approval_stakes, leftover_weight) = + Self::build_approval_stakes(max_iterations); + + for (who, approval_stake) in approval_stakes { + if ApprovalStake::::contains_key(&who) { + ApprovalStake::::mutate(&who, |maybe_stake| { + if let Some(stake) = maybe_stake { + stake.saturating_add(approval_stake); + } + }) + } else { + ApprovalStake::::insert(&who, approval_stake) + } + } + + if leftover_weight + .all_gte(Weight::from_ref_time((ApprovalStake::::count() * 2) as u64)) || + leftover_weight == max_weight + { + for (key, value) in ApprovalStake::::iter() { + ::TargetList::on_insert(key, value) + .unwrap(); + } + } + + current.put::>(); + max_weight + } else { + log!(warn, "This migration is being executed on a wrong storage version, probably needs to be removed."); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + ensure!(StorageVersion::::get() == "0", "must upgrade linearly"); + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + ensure!(StorageVersion::::get() == "1", "must upgrade linearly"); + Ok(()) + } + } +} diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index dd330b2a41054..b3c54f02296a3 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -41,7 +41,12 @@ pub mod pallet { use sp_staking::StakingInterface; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -66,9 +71,9 @@ pub mod pallet { /// to date even if a validator chilled or turned into nominator. Entries from this map are only /// ever removed if the stash is reaped. /// - /// NOTE: This is currently a [`CountedStorageMap`] for debugging purposes. We might actually want - /// to revisit this once this pallet starts populating the actual [`Config::TargetList`] used by - /// [`Config::Staking`]. + /// NOTE: This is currently a [`CountedStorageMap`] for debugging purposes. We might actually + /// want to revisit this once this pallet starts populating the actual [`Config::TargetList`] + /// used by [`Config::Staking`]. #[pallet::storage] #[pallet::getter(fn approval_stake)] pub type ApprovalStake = @@ -77,7 +82,7 @@ pub mod pallet { impl Pallet { /// The total balance that can be slashed from a stash account as of right now. - pub(crate) fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { + pub fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 7ab50dd690051..b6dae9df41d78 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -355,47 +355,6 @@ pub mod pallet { pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; - /// A helper struct with some explicit functions for nominators that are existing in storage, but - /// cannot be decoded. See [`Nominators`] for more info. - pub struct NominatorsHelper(sp_std::marker::PhantomData); - impl NominatorsHelper { - /// True IFF nominator exists, and is decodable. - pub(crate) fn contains_decodable(who: &T::AccountId) -> bool { - Nominators::::get(who).is_some() - } - - /// True if the nominator exists, decodable or not. - pub(crate) fn contains_any(who: &T::AccountId) -> bool { - Nominators::::contains_key(who) - } - - /// True IF nominators exists, and is NOT decodable. - pub(crate) fn is_undecodable(who: &T::AccountId) -> bool { - Self::contains_any(who) && !Self::contains_decodable(who) - } - - /// Iterate over all nominators, decodable or not. - pub(crate) fn iter_all() -> impl Iterator)> { - Nominators::::iter_keys().filter_map(|who| { - if let Some(u) = Self::get_any(&who) { - Some((who, u)) - } else { - frame_support::defensive!( - "any nominator that has a key must be unbounded accessible" - ); - None - } - }) - } - - /// Get the nominator `who`, decodable or not. - pub(crate) fn get_any(who: &T::AccountId) -> Option> { - frame_support::storage::unhashed::get::>( - &Nominators::::hashed_key_for(who), - ) - } - } - /// The maximum nominator count before we stop allowing new validators to join. /// /// When this value is not set, no limits are enforced. From b9b49782f5ceb4ef5785c83cebacefa9e1ff18f5 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 21 Dec 2022 16:04:43 +0100 Subject: [PATCH 19/32] more todo --- frame/stake-tracker/migrations/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/stake-tracker/migrations/src/lib.rs b/frame/stake-tracker/migrations/src/lib.rs index 8813c32f79872..90a1efbc79a42 100644 --- a/frame/stake-tracker/migrations/src/lib.rs +++ b/frame/stake-tracker/migrations/src/lib.rs @@ -11,7 +11,9 @@ macro_rules! log { } pub mod v2 {} -//TODO: Introduce try-state +//TODO: Introduce try-state to the pallet +//TODO: Decide what to do with new networks, no harm done if this is run on an empty Staking +// storage pub mod v1 { use codec::{Decode, Encode}; use frame_election_provider_support::SortedListProvider; From 1cd145a4887db23c56d6570f413d4579ad173e6b Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 13:02:42 +0100 Subject: [PATCH 20/32] nl --- frame/staking/src/pallet/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 955812afc3f6e..4cb48394b06f2 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -243,6 +243,7 @@ pub mod pallet { /// recorded. This implies that what comes out of iterating this list MIGHT NOT BE AN ACTIVE /// VALIDATOR. type TargetList: SortedListProvider>; + /// This is the implementation that's going to maintain approval stake tracking, `VoterList` /// and `TargetList` in near future. Currently running in test mode, which means it's /// maintaining totally detached versions of both lists. @@ -253,6 +254,7 @@ pub mod pallet { /// `OnRuntimeUpdate` - just a tuple of all sorts of pallets implementing the trait that all /// react to the interface methods being called or ignore them. type EventListener: sp_staking::OnStakingUpdate>; + /// The maximum number of `unlocking` chunks a [`StakingLedger`] can /// have. Effectively determines how many unique eras a staker may be /// unbonding in. From 3297703dd73d059aa88d236b33b830a22e76694d Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 13:28:08 +0100 Subject: [PATCH 21/32] revert a change --- frame/staking/src/pallet/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a711339a16ba0..b4a67306bc196 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -910,7 +910,7 @@ impl Pallet { let _ = T::EventListener::on_nominator_remove(who, nominations); true } else { - true + false }; debug_assert_eq!( From c6189672edb4e6e348a1db9f35de373e65e1a5dd Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 13:40:43 +0100 Subject: [PATCH 22/32] fix add_era_stakers --- frame/staking/src/pallet/impls.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index b4a67306bc196..90006a98f9f1a 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -904,21 +904,13 @@ impl Pallet { /// `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - let outcome = if let Some(nominations) = Self::nominations(who) { + if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); let _ = T::EventListener::on_nominator_remove(who, nominations); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome + return true + } + false } /// This function will add a validator to the `Validators` storage map. @@ -1677,7 +1669,7 @@ impl StakingInterface for Pallet { Nominators::::get(who).map(|n| n.targets.into_inner()) } - + sp_staking::runtime_benchmarks_enabled! { fn add_era_stakers( current_era: &EraIndex, stash: &T::AccountId, @@ -1690,10 +1682,10 @@ impl StakingInterface for Pallet { let exposure = Exposure { total: Default::default(), own: Default::default(), others }; >::insert(¤t_era, &stash, &exposure); } + } - fn set_current_era(era: EraIndex) { - CurrentEra::::put(era); - } + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era); } type CurrencyToVote = T::CurrencyToVote; From 6b33afc50d5782df44c6eb307679efbe051828d8 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 13:46:27 +0100 Subject: [PATCH 23/32] review comment fixes --- frame/stake-tracker/migrations/src/lib.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frame/stake-tracker/migrations/src/lib.rs b/frame/stake-tracker/migrations/src/lib.rs index 90a1efbc79a42..9c75c4f82bc78 100644 --- a/frame/stake-tracker/migrations/src/lib.rs +++ b/frame/stake-tracker/migrations/src/lib.rs @@ -10,11 +10,14 @@ macro_rules! log { }; } +trait Runtime: pallet_stake_tracker::Config + pallet_staking::Config {} + pub mod v2 {} //TODO: Introduce try-state to the pallet //TODO: Decide what to do with new networks, no harm done if this is run on an empty Staking // storage pub mod v1 { + use crate::Runtime; use codec::{Decode, Encode}; use frame_election_provider_support::SortedListProvider; use frame_support::{ @@ -38,16 +41,14 @@ pub mod v1 { } #[storage_alias] - type MigrationV1StateNominators = + type MigrationV1StateNominators = StorageValue, MigrationState, OptionQuery>; #[storage_alias] - type MigrationV1StateValidators = + type MigrationV1StateValidators = StorageValue, MigrationState, OptionQuery>; - impl - InjectValidatorsApprovalStakeIntoTargetList - { + impl InjectValidatorsApprovalStakeIntoTargetList { fn nominator_state() -> MigrationState { MigrationV1StateNominators::::get().unwrap_or(MigrationState { last_key: >::map_storage_final_prefix(), @@ -175,9 +176,7 @@ pub mod v1 { } } - impl OnRuntimeUpgrade - for InjectValidatorsApprovalStakeIntoTargetList - { + impl OnRuntimeUpgrade for InjectValidatorsApprovalStakeIntoTargetList { fn on_runtime_upgrade() -> Weight { // We have to set this manually, because we need this migration to happen in order for // the pallet to get all the data from staking-pallet. @@ -224,6 +223,10 @@ pub mod v1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result<(), &'static str> { + ensure!( + ::TargetList::count() == 0, + "must be run on an empty TargetList instance" + ); ensure!(StorageVersion::::get() == "0", "must upgrade linearly"); Ok(()) } From bd1827569949b7e47682dc019a22b5afc8b04a9b Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 13:49:58 +0100 Subject: [PATCH 24/32] add some comments, remove redundant debug_asserts in favour of try_state --- frame/staking/src/pallet/impls.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 90006a98f9f1a..0e67c90f9b51d 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -882,17 +882,13 @@ impl Pallet { let prev_nominations = Self::nominations(who); if !prev_nominations.is_none() { // maybe update sorted list. + // TODO: Remove once we start using stake-tracker for this. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); let _ = T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()) .defensive(); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); } /// This function will remove a nominator from the `Nominators` storage map, @@ -906,6 +902,7 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); + // TODO: Remove, once we start using stake-tracker for this. let _ = T::VoterList::on_remove(who).defensive(); let _ = T::EventListener::on_nominator_remove(who, nominations); return true @@ -923,16 +920,12 @@ impl Pallet { pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { // maybe update sorted list. + // TODO: Remove once we start using stake-tracker for this. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); let _ = T::EventListener::on_validator_add(who); } Validators::::insert(who, prefs); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); } /// This function will remove a validator from the `Validators` storage map. @@ -943,21 +936,14 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_validator(who: &T::AccountId) -> bool { - let outcome = if Validators::::contains_key(who) { + if Validators::::contains_key(who) { Validators::::remove(who); + // TODO: Remove, once we start using stake-tracker for this. let _ = T::VoterList::on_remove(who).defensive(); let _ = T::EventListener::on_validator_remove(who); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome + return true + } + false } /// Register some amount of weight directly with the system pallet. From 9c70db7f677cab3b2d727e8a484eb2e62f6e9fff Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 14:14:04 +0100 Subject: [PATCH 25/32] OnStakingUpdate void --- frame/stake-tracker/src/lib.rs | 49 +++++++++++----------------------- primitives/staking/src/lib.rs | 15 +++++------ 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index b3c54f02296a3..6eecf4632ad91 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -20,12 +20,9 @@ use frame_election_provider_support::{ ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, }; -use frame_support::{ - defensive, - traits::{Currency, CurrencyToVote, Defensive, DefensiveOption}, -}; +use frame_support::traits::{Currency, CurrencyToVote, Defensive}; pub use pallet::*; -use sp_runtime::{DispatchResult, Saturating}; +use sp_runtime::Saturating; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; use sp_std::vec::Vec; @@ -94,11 +91,8 @@ impl Pallet { } impl OnStakingUpdate> for Pallet { - fn on_update_ledger( - who: &T::AccountId, - prev_stake: Option>>, - ) -> DispatchResult { - let current_stake = T::Staking::stake(who)?; + fn on_update_ledger(who: &T::AccountId, prev_stake: Option>>) { + let current_stake = T::Staking::stake(who).unwrap(); let prev_active = prev_stake.map(|s| s.active).unwrap_or_default(); let current_active = current_stake.active; @@ -137,11 +131,9 @@ impl OnStakingUpdate> for Pallet { let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) .defensive_proof("any validator should have an entry in the voter list."); } - - Ok(()) } - fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) -> DispatchResult { + fn on_nominator_add(who: &T::AccountId, prev_nominations: Vec) { let nominations = T::Staking::nominations(who).unwrap_or_default(); let new = nominations.iter().filter(|n| !prev_nominations.contains(&n)); let obsolete = prev_nominations.iter().filter(|n| !nominations.contains(&n)); @@ -157,7 +149,7 @@ impl OnStakingUpdate> for Pallet { for nomination in new { // Create a new entry if it does not exist let new_stake = Self::approval_stake(&nomination) - .defensive_ok_or(Error::::DoesNotExist)? + .unwrap_or_default() .saturating_add(Self::slashable_balance_of(who)); update_approval_stake(&nomination, new_stake); @@ -167,15 +159,14 @@ impl OnStakingUpdate> for Pallet { // we should actually fail if an old nomination was not in the map, which should never // happen. let new_stake = Self::approval_stake(&nomination) - .defensive_ok_or(Error::::DoesNotExist)? + .unwrap() .saturating_sub(Self::slashable_balance_of(who)); update_approval_stake(&nomination, new_stake); } let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))) - .defensive_unwrap_or_default(); - Ok(()) + .defensive(); } /// This should only be called if that stash isn't already a validator. Note, that if we want to @@ -183,7 +174,7 @@ impl OnStakingUpdate> for Pallet { /// balance when they chill? /// Why? Because we don't remove ApprovalStake when a validator chills and we need to make sure /// their self-stake is up-to-date and not applied twice. - fn on_validator_add(who: &T::AccountId) -> DispatchResult { + fn on_validator_add(who: &T::AccountId) { let self_stake = Self::slashable_balance_of(who); let new_stake = Self::approval_stake(who).unwrap_or_default().saturating_add(self_stake); @@ -194,40 +185,32 @@ impl OnStakingUpdate> for Pallet { // migration is run we only have active validators in TargetList and not the chilled ones. let _ = T::TargetList::on_insert(who.clone(), new_stake).defensive(); ApprovalStake::::set(who, Some(new_stake)); - - Ok(()) } - fn on_validator_remove(who: &T::AccountId) -> DispatchResult { + fn on_validator_remove(who: &T::AccountId) { let _ = T::TargetList::on_remove(who).defensive(); let _ = T::VoterList::on_remove(who).defensive(); + // This will panic if the validator is not in the map, but this should never happen! ApprovalStake::::mutate(who, |x: &mut Option>| { x.map(|b| b.saturating_sub(Self::slashable_balance_of(who))) }) - .ok_or(Error::::DoesNotExist)?; - - Ok(()) + .unwrap(); } - fn on_nominator_remove(who: &T::AccountId, nominations: Vec) -> DispatchResult { + fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { let score = Self::slashable_balance_of(who); let _ = T::VoterList::on_remove(who).defensive(); for validator in nominations { ApprovalStake::::mutate(&validator, |x: &mut Option>| { x.map(|b| b.saturating_sub(score)) }) - .ok_or(Error::::DoesNotExist) - .defensive()?; + .unwrap(); } - - Ok(()) } - fn on_reaped(who: &T::AccountId) -> DispatchResult { - ApprovalStake::::remove(who); - - Ok(()) + fn on_reaped(who: &T::AccountId) { + ApprovalStake::::remove(who) } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index eca44804056bd..42564611fb46a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -87,20 +87,17 @@ pub struct Stake { /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { /// Track ledger updates. - fn on_update_ledger( - who: &AccountId, - prev_stake: Option>, - ) -> DispatchResult; + fn on_update_ledger(who: &AccountId, prev_stake: Option>); /// Track nominators, those reinstated and also new ones. - fn on_nominator_add(who: &AccountId, prev_nominations: Vec) -> DispatchResult; + fn on_nominator_add(who: &AccountId, prev_nominations: Vec); /// Track validators, those reinstated and new. - fn on_validator_add(who: &AccountId) -> DispatchResult; + fn on_validator_add(who: &AccountId); /// Track removed validators. Either chilled or those that became nominators instead. - fn on_validator_remove(who: &AccountId) -> DispatchResult; // only fire this event when this is an actual Validator + fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator /// Track removed nominators. - fn on_nominator_remove(who: &AccountId, nominations: Vec) -> DispatchResult; // only fire this if this is an actual Nominator + fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator /// Track those participants of staking system that are kicked out for whatever reason. - fn on_reaped(who: &AccountId) -> DispatchResult; // -> basically `kill_stash` + fn on_reaped(who: &AccountId); // -> basically `kill_stash` } /// A generic representation of a staking implementation. From 9b3fdf9b23c110834bf90f713bdf7871c4a4296c Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 23 Dec 2022 15:05:03 +0100 Subject: [PATCH 26/32] some reshuffling --- Cargo.lock | 2 +- Cargo.toml | 2 +- frame/staking/src/pallet/impls.rs | 21 ++++++++++----------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d624eaf242b0..2ddc97bcd5149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5832,7 +5832,7 @@ dependencies = [ ] [[package]] -name = "pallet-stake-tracker-migrations" +name = "pallet-stake-tracker-initializer" version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", diff --git a/Cargo.toml b/Cargo.toml index 6761d2d546ae7..01bff8893bcbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,7 +141,7 @@ members = [ "frame/staking/reward-curve", "frame/staking/reward-fn", "frame/stake-tracker", - "frame/stake-tracker/migrations", + "frame/stake-tracker/initializer", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0e67c90f9b51d..807bbd549483b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -277,7 +277,7 @@ impl Pallet { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); - let _ = T::EventListener::on_update_ledger(&ledger.stash, prev_ledger); + T::EventListener::on_update_ledger(&ledger.stash, prev_ledger); } /// Chill a stash account. @@ -667,7 +667,7 @@ impl Pallet { >::remove(stash); Self::do_remove_validator(stash); Self::do_remove_nominator(stash); - let _ = T::EventListener::on_reaped(stash); + T::EventListener::on_reaped(stash); frame_system::Pallet::::dec_consumers(stash); @@ -887,8 +887,7 @@ impl Pallet { .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); - let _ = T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()) - .defensive(); + T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()) } /// This function will remove a nominator from the `Nominators` storage map, @@ -904,7 +903,7 @@ impl Pallet { Nominators::::remove(who); // TODO: Remove, once we start using stake-tracker for this. let _ = T::VoterList::on_remove(who).defensive(); - let _ = T::EventListener::on_nominator_remove(who, nominations); + T::EventListener::on_nominator_remove(who, nominations); return true } false @@ -923,7 +922,7 @@ impl Pallet { // TODO: Remove once we start using stake-tracker for this. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); - let _ = T::EventListener::on_validator_add(who); + T::EventListener::on_validator_add(who); } Validators::::insert(who, prefs); } @@ -940,7 +939,7 @@ impl Pallet { Validators::::remove(who); // TODO: Remove, once we start using stake-tracker for this. let _ = T::VoterList::on_remove(who).defensive(); - let _ = T::EventListener::on_validator_remove(who); + T::EventListener::on_validator_remove(who); return true } false @@ -1651,7 +1650,7 @@ impl StakingInterface for Pallet { Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } - fn nominations(who: Self::AccountId) -> Option> { + fn nominations(who: &Self::AccountId) -> Option> { Nominators::::get(who).map(|n| n.targets.into_inner()) } @@ -1668,10 +1667,10 @@ impl StakingInterface for Pallet { let exposure = Exposure { total: Default::default(), own: Default::default(), others }; >::insert(¤t_era, &stash, &exposure); } - } - fn set_current_era(era: EraIndex) { - CurrentEra::::put(era); + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era); + } } type CurrencyToVote = T::CurrencyToVote; From 70fceb5af26b7fce42734c60afd71658624e8d77 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 13:45:26 +0100 Subject: [PATCH 27/32] stake-tracker-migrations -> initializer --- .../stake-tracker/{migrations => initializer}/Cargo.toml | 4 ++-- frame/stake-tracker/initializer/README.md | 8 ++++++++ .../stake-tracker/{migrations => initializer}/src/lib.rs | 4 ++-- frame/stake-tracker/migrations/README.md | 8 -------- 4 files changed, 12 insertions(+), 12 deletions(-) rename frame/stake-tracker/{migrations => initializer}/Cargo.toml (92%) create mode 100644 frame/stake-tracker/initializer/README.md rename frame/stake-tracker/{migrations => initializer}/src/lib.rs (98%) delete mode 100644 frame/stake-tracker/migrations/README.md diff --git a/frame/stake-tracker/migrations/Cargo.toml b/frame/stake-tracker/initializer/Cargo.toml similarity index 92% rename from frame/stake-tracker/migrations/Cargo.toml rename to frame/stake-tracker/initializer/Cargo.toml index 6fb287dad3f58..7ea5655dd4bca 100644 --- a/frame/stake-tracker/migrations/Cargo.toml +++ b/frame/stake-tracker/initializer/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "pallet-stake-tracker-migrations" +name = "pallet-stake-tracker-initializer" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Unlicense" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME stake tracker migrations submodule" +description = "FRAME stake tracker initializer submodule" readme = "README.md" [package.metadata.docs.rs] diff --git a/frame/stake-tracker/initializer/README.md b/frame/stake-tracker/initializer/README.md new file mode 100644 index 0000000000000..3d34a26c538e6 --- /dev/null +++ b/frame/stake-tracker/initializer/README.md @@ -0,0 +1,8 @@ +# Initializer submodule + +The Initializer submodule implements storage initialization for pallet-stake-tracker. + +## Overview + +The Initializer submodule depends on pallet-stake-tracker and pallet-staking as it needs access to +storage of both of these pallets, while avoiding direct dependency from one to another. diff --git a/frame/stake-tracker/migrations/src/lib.rs b/frame/stake-tracker/initializer/src/lib.rs similarity index 98% rename from frame/stake-tracker/migrations/src/lib.rs rename to frame/stake-tracker/initializer/src/lib.rs index 9c75c4f82bc78..d693240cb4e9c 100644 --- a/frame/stake-tracker/migrations/src/lib.rs +++ b/frame/stake-tracker/initializer/src/lib.rs @@ -1,4 +1,4 @@ -pub(crate) const LOG_TARGET: &str = "runtime::stake-tracker"; +pub(crate) const LOG_TARGET: &str = "runtime::stake-tracker-initializer"; #[macro_export] macro_rules! log { @@ -10,7 +10,7 @@ macro_rules! log { }; } -trait Runtime: pallet_stake_tracker::Config + pallet_staking::Config {} +pub trait Runtime: pallet_stake_tracker::Config + pallet_staking::Config {} pub mod v2 {} //TODO: Introduce try-state to the pallet diff --git a/frame/stake-tracker/migrations/README.md b/frame/stake-tracker/migrations/README.md deleted file mode 100644 index 185bb33cf41c3..0000000000000 --- a/frame/stake-tracker/migrations/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Migrations submodule - -The Migrations submodule implements storage migrations for pallet-stake-tracker. - -## Overview - -The Migrations submodule depends on pallet-stake-tracker and pallet-staking as it needs access to -storage of both of these pallets, while avoiding direct dependency from one to another. From 9a90e4bea75982d7af94c3bbf40aa7a4b20d20cc Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 14:11:20 +0100 Subject: [PATCH 28/32] remove VoterList manipulations from Staking --- frame/staking/src/benchmarking.rs | 7 +++++-- frame/staking/src/pallet/impls.rs | 18 +++--------------- frame/staking/src/pallet/mod.rs | 21 ++------------------- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index e4fdb3ac7c38e..2aa159667af65 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -189,8 +189,11 @@ impl ListScenario { )?; // find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = - T::VoterList::score_update_worst_case(&origin_stash1, is_increase); + // TODO: This is a hack, might be great to implement this through an event-listener + let dest_weight_as_vote = >::score_update_worst_case( + &origin_stash1, + is_increase, + ); let total_issuance = T::Currency::total_issuance(); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 5e3d6f017d399..e4bcaaaf3b30b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -880,12 +880,6 @@ impl Pallet { /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { let prev_nominations = Self::nominations(who); - if !prev_nominations.is_none() { - // maybe update sorted list. - // TODO: Remove once we start using stake-tracker for this. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); - } Nominators::::insert(who, nominations); T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()) } @@ -901,8 +895,6 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); - // TODO: Remove, once we start using stake-tracker for this. - let _ = T::VoterList::on_remove(who).defensive(); T::EventListener::on_nominator_remove(who, nominations); return true } @@ -918,10 +910,6 @@ impl Pallet { /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { - // maybe update sorted list. - // TODO: Remove once we start using stake-tracker for this. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); T::EventListener::on_validator_add(who); } Validators::::insert(who, prefs); @@ -937,8 +925,6 @@ impl Pallet { pub fn do_remove_validator(who: &T::AccountId) -> bool { if Validators::::contains_key(who) { Validators::::remove(who); - // TODO: Remove, once we start using stake-tracker for this. - let _ = T::VoterList::on_remove(who).defensive(); T::EventListener::on_validator_remove(who); return true } @@ -1073,7 +1059,9 @@ impl ElectionDataProvider for Pallet { #[allow(deprecated)] >::remove_all(); - T::VoterList::unsafe_clear(); + // TODO: This is a hack, implement `clear` for EventListener instead and call + // here. + >::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d827df954c4bd..cfa0359e107fc 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -220,13 +220,14 @@ pub mod pallet { /// /// The changes to nominators are reported to this. Moreover, each validator's self-vote is /// also reported as one independent vote. + /// NOTE: Staking relies on it's event listeners to make changes to this list. /// /// To keep the load off the chain as much as possible, changes made to the staked amount /// via rewards and slashes are not reported and thus need to be manually fixed by the /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. /// /// Invariant: what comes out of this list will always be a nominator. - type VoterList: SortedListProvider; + type VoterList: ReadOnlySortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going /// to be introduced in a follow-up PR. @@ -943,13 +944,7 @@ pub mod pallet { Error::::InsufficientBond ); - // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&stash) { - let _ = - T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); - } Self::deposit_event(Event::::Bonded { stash, amount: extra }); } @@ -1046,15 +1041,8 @@ pub mod pallet { .try_push(UnlockChunk { value, era }) .map_err(|_| Error::::NoMoreChunks)?; }; - // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } - Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); } @@ -1553,12 +1541,7 @@ pub mod pallet { amount: rebonded_value, }); - // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) From 85529a306089f7b1a76de492142887623048a4eb Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 14:13:29 +0100 Subject: [PATCH 29/32] fix old migration --- frame/staking/src/migrations.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index c5afcde114384..6730876b672fa 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -383,7 +383,8 @@ pub mod v8 { if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); - let migrated = T::VoterList::unsafe_regenerate( + // NOTE: This migration has been changed retroactively as the `VoterList` is now RO. + let migrated = >::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), ); From 08118a423186fee5ee8f7fee0438a32dda6e3ff8 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 14:15:16 +0100 Subject: [PATCH 30/32] fix migration --- frame/staking/src/migrations.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 6730876b672fa..c3f81facc2f3a 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -307,9 +307,12 @@ pub mod v9 { let weight_of_cached = Pallet::::weight_of_fn(); for (v, _) in Validators::::iter() { let weight = weight_of_cached(&v); - let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) - }); + // NOTE: This migration has been changed retroactively as the VoterList is now + // ReadOnly. + let _ = >::on_insert(v.clone(), weight) + .map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) + }); } log!( @@ -383,7 +386,8 @@ pub mod v8 { if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); - // NOTE: This migration has been changed retroactively as the `VoterList` is now RO. + // NOTE: This migration has been changed retroactively as the `VoterList` is now + // ReadOnly. let migrated = >::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), From ace54bbe64ab6036fcbbaf2fe456a9fe00c16c87 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 14:30:15 +0100 Subject: [PATCH 31/32] Update frame/stake-tracker/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 6eecf4632ad91..5ce4b1b69b85a 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -165,7 +165,7 @@ impl OnStakingUpdate> for Pallet { update_approval_stake(&nomination, new_stake); } let _ = - T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))) + T::VoterList::on_insert(who, Self::to_vote(Self::slashable_balance_of(who))) .defensive(); } From ea35228cbd512fb2eeeb1f5a83e0ae372d9cdf3e Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 4 Jan 2023 14:30:25 +0100 Subject: [PATCH 32/32] Update frame/stake-tracker/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 5ce4b1b69b85a..a43aa42bf58f7 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -78,7 +78,6 @@ pub mod pallet { } impl Pallet { - /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. T::Staking::stake(&who).map(|l| l.active).unwrap_or_default()