Skip to content

Commit

Permalink
async backing migrations (#1343)
Browse files Browse the repository at this point in the history
* vesting mbm

* cleanup

* update weights

* fix feature

* fix test

* fix test

* enable async backing

* fix integration tests

* zepter

* revert spec change

* taplo fmt

* dapp-staking mbm

* weight step method

* update weights

* skip accounts without locked

* next_era_start and block_reward adjusment

* fix migration
  • Loading branch information
ermalkaleci authored Sep 8, 2024
1 parent f5c99e9 commit 6a6e767
Show file tree
Hide file tree
Showing 24 changed files with 1,132 additions and 76 deletions.
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
Expand Up @@ -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 }

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

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

[features]
default = ["std"]
Expand Down
69 changes: 68 additions & 1 deletion pallets/dapp-staking/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

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

/// Benchmark a single step of mbm migration.
#[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(),
Expand Down
115 changes: 114 additions & 1 deletion pallets/dapp-staking/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
}

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() {
if current_block_number >= chunk.unlock_block {
continue; // chunk already unlocked
}
let remaining_blocks = chunk.unlock_block.saturating_sub(current_block_number);
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

0 comments on commit 6a6e767

Please sign in to comment.