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

async backing migrations #1343

Merged
merged 19 commits into from
Sep 8, 2024
Merged
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
25 changes: 25 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -297,6 +297,7 @@ astar-xcm-benchmarks = { path = "./pallets/astar-xcm-benchmarks", default-featur
pallet-static-price-provider = { path = "./pallets/static-price-provider", default-features = false }
pallet-price-aggregator = { path = "./pallets/price-aggregator", default-features = false }
pallet-collective-proxy = { path = "./pallets/collective-proxy", default-features = false }
vesting-mbm = { path = "./pallets/vesting-mbm", default-features = false }

dapp-staking-runtime-api = { path = "./pallets/dapp-staking/rpc/runtime-api", default-features = false }

1 change: 1 addition & 0 deletions pallets/dapp-staking/Cargo.toml
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ frame-benchmarking = { workspace = true, optional = true }

[dev-dependencies]
pallet-balances = { workspace = true }
pallet-migrations = { workspace = true }

[features]
default = ["std"]
69 changes: 68 additions & 1 deletion pallets/dapp-staking/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ use super::{Pallet as DappStaking, *};
use astar_primitives::Balance;
use frame_benchmarking::v2::*;

use frame_support::assert_ok;
use frame_support::{assert_ok, migrations::SteppedMigration, weights::WeightMeter};
use frame_system::{Pallet as System, RawOrigin};
use sp_std::prelude::*;

@@ -1070,6 +1070,73 @@ mod benchmarks {
);
}

/// Benchmark a single step of mbm migration.
ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
#[benchmark]
fn step() {
let alice: T::AccountId = account("alice", 0, 1);

Ledger::<T>::set(
&alice,
AccountLedger {
locked: 1000,
unlocking: vec![
UnlockingChunk {
amount: 100,
unlock_block: 5,
},
UnlockingChunk {
amount: 100,
unlock_block: 20,
},
]
.try_into()
.unwrap(),
staked: Default::default(),
staked_future: None,
contract_stake_count: 0,
},
);
CurrentEraInfo::<T>::put(EraInfo {
total_locked: 1000,
unlocking: 200,
current_stake_amount: Default::default(),
next_stake_amount: Default::default(),
});

System::<T>::set_block_number(10u32.into());
let mut meter = WeightMeter::new();

#[block]
{
crate::migration::LazyMigration::<T, weights::SubstrateWeight<T>>::step(
None, &mut meter,
)
.unwrap();
}

assert_eq!(
Ledger::<T>::get(&alice),
AccountLedger {
locked: 1000,
unlocking: vec![
UnlockingChunk {
amount: 100,
unlock_block: 5, // already unlocked
},
UnlockingChunk {
amount: 100,
unlock_block: 30, // double remaining blocks
},
]
.try_into()
.unwrap(),
staked: Default::default(),
staked_future: None,
contract_stake_count: 0,
}
);
}

impl_benchmark_test_suite!(
Pallet,
crate::benchmarking::tests::new_test_ext(),
115 changes: 114 additions & 1 deletion pallets/dapp-staking/src/migration.rs
Original file line number Diff line number Diff line change
@@ -17,7 +17,12 @@
// along with Astar. If not, see <http://www.gnu.org/licenses/>.

use super::*;
use frame_support::{storage_alias, traits::UncheckedOnRuntimeUpgrade};
use frame_support::{
migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
storage_alias,
traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade},
weights::WeightMeter,
};

#[cfg(feature = "try-runtime")]
use sp_std::vec::Vec;
@@ -412,3 +417,111 @@ pub mod v6 {
pub period: PeriodNumber,
}
}

const PALLET_MIGRATIONS_ID: &[u8; 16] = b"dapp-staking-mbm";

pub struct LazyMigration<T, W: WeightInfo>(PhantomData<(T, W)>);

impl<T: Config, W: WeightInfo> SteppedMigration for LazyMigration<T, W> {
type Cursor = <T as frame_system::Config>::AccountId;
// Without the explicit length here the construction of the ID would not be infallible.
type Identifier = MigrationId<16>;

/// The identifier of this migration. Which should be globally unique.
fn id() -> Self::Identifier {
MigrationId {
pallet_id: *PALLET_MIGRATIONS_ID,
version_from: 0,
version_to: 1,
}
}

fn step(
mut cursor: Option<Self::Cursor>,
meter: &mut WeightMeter,
) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
let required = W::step();
// If there is not enough weight for a single step, return an error. This case can be
// problematic if it is the first migration that ran in this block. But there is nothing
// that we can do about it here.
if meter.remaining().any_lt(required) {
return Err(SteppedMigrationError::InsufficientWeight { required });
ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
}

let mut count = 0u32;
let mut migrated = 0u32;
let current_block_number =
frame_system::Pallet::<T>::block_number().saturated_into::<u32>();

// We loop here to do as much progress as possible per step.
loop {
if meter.try_consume(required).is_err() {
break;
}

let mut iter = if let Some(last_key) = cursor {
// If a cursor is provided, start iterating from the stored value
// corresponding to the last key processed in the previous step.
// Note that this only works if the old and the new map use the same way to hash
// storage keys.
Ledger::<T>::iter_from(Ledger::<T>::hashed_key_for(last_key))
} else {
// If no cursor is provided, start iterating from the beginning.
Ledger::<T>::iter()
};

// If there's a next item in the iterator, perform the migration.
if let Some((ref last_key, mut ledger)) = iter.next() {
// inc count
count.saturating_inc();

if ledger.unlocking.is_empty() {
// no unlocking for this account, nothing to update
// Return the processed key as the new cursor.
cursor = Some(last_key.clone());
continue;
}
for chunk in ledger.unlocking.iter_mut() {
Dinonard marked this conversation as resolved.
Show resolved Hide resolved
if current_block_number >= chunk.unlock_block {
continue; // chunk already unlocked
}
let remaining_blocks = chunk.unlock_block.saturating_sub(current_block_number);
Dinonard marked this conversation as resolved.
Show resolved Hide resolved
chunk.unlock_block.saturating_accrue(remaining_blocks);
}

// Override ledger
Ledger::<T>::insert(last_key, ledger);

// inc migrated
migrated.saturating_inc();

// Return the processed key as the new cursor.
cursor = Some(last_key.clone())
} else {
// Signal that the migration is complete (no more items to process).
cursor = None;
break;
}
}
log::info!(target: LOG_TARGET, "🚚 iterated {count} entries, migrated {migrated}");
Ok(cursor)
}
}

/// Double the remaining block for next era start
pub struct AdjustEraMigration<T>(PhantomData<T>);

impl<T: Config> OnRuntimeUpgrade for AdjustEraMigration<T> {
fn on_runtime_upgrade() -> Weight {
log::info!("🚚 migrated to async backing, adjust next era start");
ActiveProtocolState::<T>::mutate_exists(|maybe| {
if let Some(state) = maybe {
let current_block_number =
frame_system::Pallet::<T>::block_number().saturated_into::<u32>();
let remaining = state.next_era_start.saturating_sub(current_block_number);
state.next_era_start.saturating_accrue(remaining);
}
});
T::DbWeight::get().reads_writes(1, 1)
}
}
85 changes: 85 additions & 0 deletions pallets/dapp-staking/src/test/migrations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// This file is part of Astar.

// Copyright (C) Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// Astar is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Astar is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Astar. If not, see <http://www.gnu.org/licenses/>.

#![cfg(all(test, not(feature = "runtime-benchmarks")))]

use crate::test::mock::*;
use crate::{AccountLedger, CurrentEraInfo, EraInfo, Ledger, UnlockingChunk};
use frame_support::traits::OnRuntimeUpgrade;

#[test]
fn lazy_migrations() {
ExtBuilder::default().build_and_execute(|| {
Ledger::<Test>::set(
&1,
AccountLedger {
locked: 1000,
unlocking: vec![
UnlockingChunk {
amount: 100,
unlock_block: 5,
},
UnlockingChunk {
amount: 100,
unlock_block: 20,
},
]
.try_into()
.unwrap(),
staked: Default::default(),
staked_future: None,
contract_stake_count: 0,
},
);
CurrentEraInfo::<Test>::put(EraInfo {
total_locked: 1000,
unlocking: 200,
current_stake_amount: Default::default(),
next_stake_amount: Default::default(),
});

// go to block before migration
run_to_block(9);

// onboard MBMs
AllPalletsWithSystem::on_runtime_upgrade();
run_to_block(10);

assert_eq!(
Ledger::<Test>::get(&1),
AccountLedger {
locked: 1000,
unlocking: vec![
UnlockingChunk {
amount: 100,
unlock_block: 5, // already unlocked
},
UnlockingChunk {
amount: 100,
unlock_block: 30, // double remaining blocks
},
]
.try_into()
.unwrap(),
staked: Default::default(),
staked_future: None,
contract_stake_count: 0,
}
);
})
}
Loading
Loading