Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimizes MigrateAtStakeAutoCompound to run only for unpaid rounds #1881

Merged
merged 2 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 65 additions & 185 deletions pallets/parachain-staking/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,100 +39,97 @@ use alloc::format;
use frame_support::{
migration::storage_key_iter,
pallet_prelude::PhantomData,
storage,
traits::{Get, OnRuntimeUpgrade, ReservableCurrency},
weights::Weight,
};
#[cfg(feature = "try-runtime")]
use scale_info::prelude::string::String;
use sp_runtime::traits::{Saturating, Zero};
use sp_runtime::Percent;
use sp_std::collections::btree_set::BTreeSet;
use sp_std::{convert::TryInto, vec, vec::Vec};

/// Migration `AtStake` storage item to include auto-compound value
/// Migrate `AtStake` storage item to include auto-compound value for unpaid rounds.
pub struct MigrateAtStakeAutoCompound<T>(PhantomData<T>);
impl<T: Config> MigrateAtStakeAutoCompound<T> {
const PALLET_PREFIX: &'static [u8] = b"ParachainStaking";
const AT_STAKE_PREFIX: &'static [u8] = b"AtStake";
/// Get keys for the `AtStake` storage for the rounds up to `RewardPaymentDelay` rounds ago.
/// We migrate only the last unpaid rounds due to the presence of stale entries in `AtStake`
/// which significantly increase the PoV size.
fn unpaid_rounds_keys() -> impl Iterator<Item = (RoundIndex, T::AccountId, Vec<u8>)> {
let current_round = <Round<T>>::get().current;
let max_unpaid_round = current_round.saturating_sub(T::RewardPaymentDelay::get());
(max_unpaid_round..=current_round)
.into_iter()
.flat_map(|round| {
<AtStake<T>>::iter_key_prefix(round).map(move |candidate| {
let key = <AtStake<T>>::hashed_key_for(round.clone(), candidate.clone());
(round, candidate, key)
})
})
}
}
impl<T: Config> OnRuntimeUpgrade for MigrateAtStakeAutoCompound<T> {
#[allow(deprecated)]
fn on_runtime_upgrade() -> Weight {
use sp_std::collections::btree_set::BTreeSet;

log::info!(target: "MigrateAtStakeAutoCompound", "running migration to add auto-compound values");
log::info!(
target: "MigrateAtStakeAutoCompound",
"running migration to add auto-compound values"
);
let mut reads = 0u64;
let mut writes = 0u64;

let max_unpaid_round = <Round<T>>::get()
.current
.saturating_sub(T::RewardPaymentDelay::get());

// validate only from `max_unpaid_round`, since we have some stale entries and this adds
// to the PoV size during try-runtime
<AtStake<T>>::translate(
|round, candidate, old_state: OldCollatorSnapshot<T::AccountId, BalanceOf<T>>| {
reads = reads.saturating_add(1);
writes = writes.saturating_add(1);

log::info!(
target: "MigrateAtStakeAutoCompound",
"migration from old format round {:?}, candidate {:?}", round, candidate
);
Some(CollatorSnapshot {
bond: old_state.bond,
delegations: old_state
.delegations
.into_iter()
.map(|d| BondWithAutoCompound {
owner: d.owner,
amount: d.amount,
auto_compound: Percent::zero(),
})
.collect(),
total: old_state.total,
})
},
);
for (round, candidate, key) in Self::unpaid_rounds_keys() {
let old_state: OldCollatorSnapshot<T::AccountId, BalanceOf<T>> =
storage::unhashed::get(&key).expect("unable to decode value");
reads = reads.saturating_add(1);
writes = writes.saturating_add(1);
log::info!(
target: "MigrateAtStakeAutoCompound",
"migration from old format round {:?}, candidate {:?}", round, candidate
);
let new_state = CollatorSnapshot {
bond: old_state.bond,
delegations: old_state
.delegations
.into_iter()
.map(|d| BondWithAutoCompound {
owner: d.owner,
amount: d.amount,
auto_compound: Percent::zero(),
})
.collect(),
total: old_state.total,
};
storage::unhashed::put(&key, &new_state);
}

T::DbWeight::get().reads_writes(reads, writes)
}

#[allow(deprecated)]
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
use frame_support::storage;

let mut num_to_update = 0u32;
let mut rounds_candidates = vec![];
for (round, candidate, key) in Self::unpaid_rounds_keys() {
let state: OldCollatorSnapshot<T::AccountId, BalanceOf<T>> =
storage::unhashed::get(&key).expect("unable to decode value");

// validate only from `max_unpaid_round`, since we have some stale entries and this adds
// to the PoV size during try-runtime
let current_round = <Round<T>>::get().current;
let max_unpaid_round = current_round.saturating_sub(T::RewardPaymentDelay::get());
for round in max_unpaid_round..=current_round {
for candidate in <AtStake<T>>::iter_key_prefix(round) {
let key = <AtStake<T>>::hashed_key_for(round.clone(), candidate.clone());
let state: OldCollatorSnapshot<T::AccountId, BalanceOf<T>> =
storage::unhashed::get(&key).expect("unable to decode value");

num_to_update = num_to_update.saturating_add(1);
rounds_candidates.push((round.clone(), candidate.clone()));
let mut delegation_str = vec![];
for d in state.delegations {
delegation_str.push(format!(
"owner={:?}_amount={:?}_autoCompound=0%",
d.owner, d.amount
));
}
Self::set_temp_storage(
format!(
"bond={:?}_total={:?}_delegations={:?}",
state.bond, state.total, delegation_str
),
&*format!("round_{:?}_candidate_{:?}", round, candidate),
);
num_to_update = num_to_update.saturating_add(1);
rounds_candidates.push((round.clone(), candidate.clone()));
let mut delegation_str = vec![];
for d in state.delegations {
delegation_str.push(format!(
"owner={:?}_amount={:?}_autoCompound=0%",
d.owner, d.amount
));
}
Self::set_temp_storage(
format!(
"bond={:?}_total={:?}_delegations={:?}",
state.bond, state.total, delegation_str
),
&*format!("round_{:?}_candidate_{:?}", round, candidate),
);
}

rounds_candidates.sort();
Expand All @@ -145,19 +142,8 @@ impl<T: Config> OnRuntimeUpgrade for MigrateAtStakeAutoCompound<T> {
fn post_upgrade() -> Result<(), &'static str> {
let mut num_updated = 0u32;
let mut rounds_candidates = vec![];
let max_unpaid_round = <Round<T>>::get()
.current
.saturating_sub(T::RewardPaymentDelay::get());
for (round, candidate, state) in <AtStake<T>>::iter() {
if round < max_unpaid_round {
log::warn!(
target: "MigrateAtStakeAutoCompound",
"skipping storage check for round {:?}, this round entry should not exist",
round
);
continue;
}

for (round, candidate, _) in Self::unpaid_rounds_keys() {
let state = <AtStake<T>>::get(&round, &candidate);
num_updated = num_updated.saturating_add(1);
rounds_candidates.push((round.clone(), candidate.clone()));
let mut delegation_str = vec![];
Expand Down Expand Up @@ -189,112 +175,6 @@ impl<T: Config> OnRuntimeUpgrade for MigrateAtStakeAutoCompound<T> {
}
}

/// Removes old entries for paid rounds from the `AtStake` storage item.
pub struct RemovePaidRoundsFromAtStake<T>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for RemovePaidRoundsFromAtStake<T> {
#[allow(deprecated)]
fn on_runtime_upgrade() -> Weight {
use sp_std::collections::btree_set::BTreeSet;

let mut reads = 0u64;
let mut writes = 0u64;
let max_unpaid_round = <Round<T>>::get()
.current
.saturating_sub(T::RewardPaymentDelay::get());

log::info!(
target: "RemovePaidRoundsFromAtStake",
"running migration to remove entries for paid rounds < {:?}",
max_unpaid_round,
);

// Remove all the keys that are older than the last possible unpaid round. As an additional
// check we also verify that the `Points` & `DelayedPayouts` storage item have already been
// removed to avoid the risk to removing the snapshot with outstanding errors.
<AtStake<T>>::iter_keys()
.filter(|(round, _)| {
round < &max_unpaid_round
&& !<Points<T>>::contains_key(round)
&& !<DelayedPayouts<T>>::contains_key(round)
})
.map(|(round, _)| round)
.collect::<BTreeSet<_>>()
.iter()
.for_each(|round| {
writes = writes.saturating_add(1);
log::info!(target: "RemovePaidRoundsFromAtStake", "removing round {:?}", round);
<AtStake<T>>::remove_prefix(round, None);
});

T::DbWeight::get().reads_writes(reads, writes)
}

#[allow(deprecated)]
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
let max_unpaid_round = <Round<T>>::get()
.current
.saturating_sub(T::RewardPaymentDelay::get());

let rounds_to_keep = <AtStake<T>>::iter_keys()
.filter(|(round, _)| {
if round >= &max_unpaid_round {
true
} else {
let points_exist = <Points<T>>::contains_key(round);
let delayed_payouts_exist = <DelayedPayouts<T>>::contains_key(round);
if points_exist {
log::info!(
target: "RemovePaidRoundsFromAtStake",
"Points storage still exists for round {:?}, max_unpaid_round {:?}, \
entry will not be removed",
round,
max_unpaid_round
);
};
if delayed_payouts_exist {
log::info!(
target: "RemovePaidRoundsFromAtStake",
"DelayedPayouts storage still exists for round {:?}, max_unpaid_round {:?}, \
entry will not be removed",
round,
max_unpaid_round
);
};
points_exist || delayed_payouts_exist
}
})
.map(|(round, _)| round)
.collect::<BTreeSet<_>>();
Self::set_temp_storage(format!("{:?}", rounds_to_keep), "rounds_to_keep");
Ok(())
}

#[cfg(feature = "try-runtime")]
fn post_upgrade() -> Result<(), &'static str> {
let max_unpaid_round = <Round<T>>::get()
.current
.saturating_sub(T::RewardPaymentDelay::get());
let rounds_kept = <AtStake<T>>::iter_keys()
.map(|(round, _)| {
assert!(
round >= max_unpaid_round,
"unexpected stale round storage item, max_unpaid_round={:?}, got={:?}",
max_unpaid_round,
round
);
round
})
.collect::<BTreeSet<_>>();

assert_eq!(
Some(format!("{:?}", rounds_kept)),
Self::get_temp_storage("rounds_to_keep")
);
Ok(())
}
}

/// Migration to move delegator requests towards a delegation, from [DelegatorState] into
/// [DelegationScheduledRequests] storage item.
/// Additionally [DelegatorState] is migrated from [OldDelegator] to [Delegator].
Expand Down
29 changes: 1 addition & 28 deletions runtime/common/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use pallet_migrations::{GetMigrations, Migration};
use pallet_parachain_staking::{
migrations::{
MigrateAtStakeAutoCompound, PatchIncorrectDelegationSums, PurgeStaleStorage,
RemovePaidRoundsFromAtStake, SplitDelegatorStateIntoDelegationScheduledRequests,
SplitDelegatorStateIntoDelegationScheduledRequests,
},
Config as ParachainStakingConfig,
};
Expand Down Expand Up @@ -152,30 +152,6 @@ impl<T: ParachainStakingConfig> Migration for ParachainStakingMigrateAtStakeAuto
}
}

/// Migrate `AtStake` storage item to remove entries for paid-out rounds
pub struct ParachainStakingRemovePaidRoundsFromAtStake<T>(PhantomData<T>);
impl<T: ParachainStakingConfig> Migration for ParachainStakingRemovePaidRoundsFromAtStake<T> {
fn friendly_name(&self) -> &str {
"MM_Parachain_Staking_Remove_Paid_Rounds_From_At_Stake"
}

fn migrate(&self, _available_weight: Weight) -> Weight {
RemovePaidRoundsFromAtStake::<T>::on_runtime_upgrade()
}

/// Run a standard pre-runtime test. This works the same way as in a normal runtime upgrade.
#[cfg(feature = "try-runtime")]
fn pre_upgrade(&self) -> Result<(), &'static str> {
RemovePaidRoundsFromAtStake::<T>::pre_upgrade()
}

/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade.
#[cfg(feature = "try-runtime")]
fn post_upgrade(&self) -> Result<(), &'static str> {
RemovePaidRoundsFromAtStake::<T>::post_upgrade()
}
}

/// Staking split delegator state into [pallet_parachain_staking::DelegatorScheduledRequests]
pub struct ParachainStakingSplitDelegatorStateIntoDelegationScheduledRequests<T>(PhantomData<T>);
impl<T: ParachainStakingConfig> Migration
Expand Down Expand Up @@ -759,8 +735,6 @@ where
// let xcm_supported_assets = XcmPaymentSupportedAssets::<Runtime>(Default::default());

let migration_elasticity = MigrateBaseFeeElasticity::<Runtime>(Default::default());
let staking_remove_at_stake_paid_rounds =
ParachainStakingRemovePaidRoundsFromAtStake::<Runtime>(Default::default());
let staking_at_stake_auto_compound =
ParachainStakingMigrateAtStakeAutoCompound::<Runtime>(Default::default());
vec![
Expand Down Expand Up @@ -801,7 +775,6 @@ where
// completed in runtime 1600
// Box::new(xcm_transactor_transact_signed),
Box::new(migration_elasticity),
Box::new(staking_remove_at_stake_paid_rounds),
Box::new(staking_at_stake_auto_compound),
]
}
Expand Down