Skip to content

Commit

Permalink
Adds target approvals buffering below a threshold (#5168)
Browse files Browse the repository at this point in the history
This PR adds target stake updates buffering to `stake-tracker`. It
exposes a configuration that defines a threshold below which the target
score *should not* be updated automatically in the target list. The
`Config::ScoreStrictUpdateThreshold` defines said threshold. If a target
stake update is below the threshold, the stake update is buffered in the
`UnsettledTargetScore` storage map while the target list is not
affected. Multiple approvals updates can be buffered for the same
target. Calling `Call::settle` will setttle the buffered approvals tally
for a given target. Setting `Config::ScoreStrictUpdateThreshold` to
`None` disables the stake approvals buffering.

- [x] benchmarks
- [x] `try-state` checks considering the unsettled score
- [x] docs

---------

Co-authored-by: command-bot <>
  • Loading branch information
gpestana authored Aug 1, 2024
1 parent 06b6821 commit 933e159
Show file tree
Hide file tree
Showing 20 changed files with 637 additions and 29 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ parameter_types! {
pub const TargetBagThresholds: &'static [u128] = &bag_thresholds::TARGET_THRESHOLDS;

pub const VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
pub const ScoreStrictUpdateThreshold: Option<u128> = Some(10);
}

type VoterBagsListInstance = pallet_bags_list::Instance1;
Expand All @@ -616,6 +617,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = VoterList;
type TargetList = TargetList;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = pallet_stake_tracker::weights::SubstrateWeight<Runtime>;
}

pallet_staking_reward_curve::build! {
Expand Down Expand Up @@ -667,6 +670,8 @@ impl pallet_staking::Config for Runtime {
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type VoterList = VoterList;
type TargetList = TargetList;
#[cfg(any(feature = "try-runtime", test))]
type TargetUnsettledApprovals = pallet_stake_tracker::UnsettledTargetScores<Self>;
type NominationsQuota = pallet_staking::FixedNominationsQuota<{ MaxNominations::get() }>;
type MaxUnlockingChunks = frame_support::traits::ConstU32<32>;
type HistoryDepth = frame_support::traits::ConstU32<84>;
Expand Down
7 changes: 7 additions & 0 deletions substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ impl pallet_staking::Config for Runtime {
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type VoterList = VoterList;
type TargetList = TargetList;
#[cfg(any(feature = "try-runtime", test))]
type TargetUnsettledApprovals = pallet_stake_tracker::UnsettledTargetScores<Self>;
type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
type MaxUnlockingChunks = ConstU32<32>;
type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
Expand All @@ -709,6 +711,8 @@ impl pallet_staking::Config for Runtime {

parameter_types! {
pub const VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
// disables the lazy approvals update.
pub const ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Runtime {
Expand All @@ -717,6 +721,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = VoterList;
type TargetList = TargetList;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = pallet_stake_tracker::weights::SubstrateWeight<Runtime>;
}

impl pallet_fast_unstake::Config for Runtime {
Expand Down Expand Up @@ -2646,6 +2652,7 @@ mod benches {
[pallet_session, SessionBench::<Runtime>]
[pallet_society, Society]
[pallet_staking, Staking]
[pallet_stake_tracker, StakeTracker]
[pallet_state_trie_migration, StateTrieMigration]
[pallet_sudo, Sudo]
[frame_system, SystemBench::<Runtime>]
Expand Down
5 changes: 5 additions & 0 deletions substrate/frame/delegated-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ impl pallet_staking::Config for Runtime {
type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
type TargetList = TargetBagsList;
#[cfg(any(feature = "try-runtime", test))]
type TargetUnsettledApprovals = pallet_stake_tracker::UnsettledTargetScores<Self>;
type EventListeners = (StakeTracker, Pools, DelegatedStaking);
}

Expand All @@ -129,6 +131,7 @@ impl pallet_bags_list::Config<TargetBagsListInstance> for Runtime {

parameter_types! {
pub static VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
pub static ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Runtime {
Expand All @@ -137,6 +140,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
type TargetList = TargetBagsList;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = ();
}

parameter_types! {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ impl pallet_bags_list::Config<TargetBagsListInstance> for Runtime {

parameter_types! {
pub static UpdateMode: VoterUpdateMode = VoterUpdateMode::Lazy;
pub static ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Runtime {
Expand All @@ -264,6 +265,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = VoterBagsList;
type TargetList = TargetBagsList;
type VoterUpdateMode = UpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = ();
}

pub struct BalanceToU256;
Expand Down Expand Up @@ -330,6 +333,8 @@ impl pallet_staking::Config for Runtime {
type VoterList = VoterBagsList;
type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
type TargetList = TargetBagsList;
#[cfg(any(feature = "try-runtime", test))]
type TargetUnsettledApprovals = pallet_stake_tracker::UnsettledTargetScores<Self>;
type MaxUnlockingChunks = MaxUnlockingChunks;
type EventListeners = (StakeTracker, Pools);
type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/nomination-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ impl sp_staking::StakingInterface for StakingMock {
Ok(())
}

fn validate(who: &Self::AccountId) -> sp_runtime::DispatchResult {
unimplemented!()
}

fn nominate(_: &Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
Nominations::set(&Some(nominations));
Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl pallet_staking::Config for Runtime {

parameter_types! {
pub static VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
pub static ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Runtime {
Expand All @@ -116,6 +117,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = VoterList;
type TargetList = pallet_staking::UseValidatorsMap<Self>;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = ();
}

parameter_types! {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl pallet_staking::Config for Runtime {

parameter_types! {
pub static VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
pub static ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Runtime {
Expand All @@ -108,6 +109,8 @@ impl pallet_stake_tracker::Config for Runtime {
type VoterList = VoterList;
type TargetList = pallet_staking::UseValidatorsMap<Self>;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = ();
}

parameter_types! {
Expand Down
8 changes: 2 additions & 6 deletions substrate/frame/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{migrations::v13_stake_tracker as v13, ConfigOp, Pallet as Staking};
use testing_utils::*;

use codec::Decode;
use frame_benchmarking::v2::*;
use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider};
use frame_support::{
assert_ok,
Expand All @@ -31,18 +32,13 @@ use frame_support::{
traits::{Currency, Get, Imbalance, UnfilteredDispatchable},
weights::WeightMeter,
};
use frame_system::RawOrigin;
use sp_runtime::{
traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero},
Perbill, Percent, Saturating,
};
use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex, StakingInterface};

pub use frame_benchmarking::v1::{
account, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, BenchmarkError,
};
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;

const SEED: u32 = 0;
const MAX_SPANS: u32 = 100;
const MAX_SLASHES: u32 = 1000;
Expand Down
11 changes: 11 additions & 0 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ impl OnStakingUpdate<AccountId, Balance> for EventTracker {

parameter_types! {
pub static VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Lazy;
// disables the lazy approvals update.
pub static ScoreStrictUpdateThreshold: Option<u128> = None;
}

impl pallet_stake_tracker::Config for Test {
Expand All @@ -364,6 +366,8 @@ impl pallet_stake_tracker::Config for Test {
type VoterList = VoterBagsList;
type TargetList = TargetBagsList;
type VoterUpdateMode = VoterUpdateMode;
type ScoreStrictUpdateThreshold = ScoreStrictUpdateThreshold;
type WeightInfo = ();
}

// Disabling threshold for `UpToLimitDisablingStrategy`
Expand All @@ -386,6 +390,8 @@ impl crate::pallet::pallet::Config for Test {
type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = VoterBagsList;
type TargetList = TargetBagsList;
#[cfg(any(feature = "try-runtime", test))]
type TargetUnsettledApprovals = pallet_stake_tracker::UnsettledTargetScores<Self>;
type NominationsQuota = WeightedNominationsQuota<16>;
type MaxUnlockingChunks = MaxUnlockingChunks;
type HistoryDepth = HistoryDepth;
Expand Down Expand Up @@ -559,6 +565,11 @@ impl ExtBuilder {
MaxWinners::set(max);
self
}
pub fn stake_tracker_update_threshold(self, threshold: Option<u128>) -> Self {
ScoreStrictUpdateThreshold::set(threshold);
self
}

fn build(self) -> sp_io::TestExternalities {
sp_tracing::try_init_simple();
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
Expand Down
20 changes: 20 additions & 0 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1993,6 +1993,11 @@ impl<T: Config> StakingInterface for Pallet<T> {
Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
}

fn validate(who: &Self::AccountId) -> DispatchResult {
let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
Self::validate(RawOrigin::Signed(ctrl).into(), Default::default())
}

fn desired_validator_count() -> u32 {
ValidatorCount::<T>::get()
}
Expand Down Expand Up @@ -2521,6 +2526,8 @@ impl<T: Config> Pallet<T> {
/// (active_validators + idle_validators + dangling_targets_score_with_score).
pub fn do_try_state_approvals() -> Result<(), sp_runtime::TryRuntimeError> {
use alloc::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
use pallet_stake_tracker::StakeImbalance;

let mut approvals_map: BTreeMap<T::AccountId, T::CurrencyBalance> = BTreeMap::new();

// build map of approvals stakes from the `Nominators` storage map POV.
Expand Down Expand Up @@ -2585,6 +2592,19 @@ impl<T: Config> Pallet<T> {
}
}

// sync up current unsettled score to target's approvals.
for (target, imbalance) in T::TargetUnsettledApprovals::get().into_iter() {
if let Some(approvals) = approvals_map.get_mut(&target) {
match imbalance {
StakeImbalance::Positive(score) => *approvals -= score,
StakeImbalance::Negative(score) => *approvals += score,
StakeImbalance::Zero => (),
}
} else {
return Err("unsettled approval not in the target list".into());
}
}

let mut mismatch_approvals = 0;

// compare calculated approvals per target with target list state.
Expand Down
13 changes: 13 additions & 0 deletions substrate/frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,19 @@ pub mod pallet {
#[pallet::no_default]
type TargetList: SortedListProvider<Self::AccountId, Score = BalanceOf<Self>>;

/// Getter for unsettled approvals scores of targets in stake-tracker. Only used for
/// testing and try-state.
#[cfg(any(feature = "try-runtime", test))]
#[pallet::no_default]
type TargetUnsettledApprovals: TypedGet<
Type = Vec<(
Self::AccountId,
pallet_stake_tracker::StakeImbalance<
<Self::TargetList as SortedListProvider<Self::AccountId>>::Score,
>,
)>,
>;

/// The maximum number of `unlocking` chunks a [`StakingLedger`] can
/// have. Effectively determines how many unique eras a staker may be
/// unbonding in.
Expand Down
40 changes: 39 additions & 1 deletion substrate/frame/staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7853,6 +7853,7 @@ mod stake_tracker {
use super::*;
use frame_election_provider_support::ScoreProvider;
use pallet_bags_list::Event as BagsEvent;
use pallet_stake_tracker::StakeImbalance;
use sp_staking::{StakingAccount::*, StakingInterface};

// keep tests clean;
Expand Down Expand Up @@ -8720,6 +8721,42 @@ mod stake_tracker {
);
})
}

#[test]
fn try_state_with_unsettled_score_works() {
ExtBuilder::default()
.stake_tracker_update_threshold(Some(50))
.try_state(true)
.build_and_execute(|| {
assert_eq!(Staking::status(&11), Ok(StakerStatus::Validator));
assert_eq!(TargetBagsList::score(&11), 1500);

assert_ok!(Staking::bond(
RuntimeOrigin::signed(90),
4000,
RewardDestination::Staked
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(90), vec![11]));

let vote_weight = Staking::weight_of(&90);

// confirm that vote weight of new nominator is lower than threshold.
assert!((vote_weight as u128) < ScoreStrictUpdateThreshold::get().unwrap());

// approvals of 42 did not updatee with nomination because new bond's vote weight
// was below the upadte threshold.
assert_eq!(TargetBagsList::score(&11), 1500);

// unsettled score map has been updated.
assert_eq!(
pallet_stake_tracker::UnsettledTargetScores::<Test>::get(),
vec![(11, StakeImbalance::Positive(vote_weight as u128))]
);

// he try-state checks takes into consideration the unsetttled score.
assert!(Staking::do_try_state(System::block_number()).is_ok());
})
}
}

mod ledger {
Expand Down Expand Up @@ -9318,7 +9355,8 @@ mod ledger_recovery {
assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // OK
assert_eq!(Bonded::<Test>::get(&333), Some(444)); // OK
assert!(Payee::<Test>::get(&333).is_some()); // OK
// however, ledger associated with its controller was killed.

// however, ledger associated with its controller was killed.
assert!(Ledger::<Test>::get(&444).is_none()); // NOK

// side effects on 444 - ledger, bonded, payee, lock should be completely removed.
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/staking/stake-tracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ codec = { features = [

scale-info = { features = ["derive", "serde"], workspace = true }

sp-std = { workspace = true }
sp-runtime = { features = ["serde"], workspace = true }
sp-staking = { features = ["serde"], workspace = true }

Expand Down Expand Up @@ -56,6 +57,7 @@ std = [
"sp-runtime/std",
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
"sp-tracing/std",
]

Expand Down
Loading

0 comments on commit 933e159

Please sign in to comment.