Skip to content

Commit

Permalink
[AHM] Improve account migration (#532)
Browse files Browse the repository at this point in the history
👉  To be merged into the AHM working branch.

Changes:
- Clean up AH and RC account migration code
- Add error logging
- Mint Relay blocks until no more account can be moved
- Mint AH blocks until all DMP messages are consumed

- [x] Does not require a CHANGELOG entry

---------

Signed-off-by: Oliver Tale-Yazdi <[email protected]>
  • Loading branch information
ggwpez authored Jan 8, 2025
1 parent 30d93be commit dde9960
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 241 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.

2 changes: 2 additions & 0 deletions integration-tests/ahm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub fn next_block_rc() {
log::info!(target: LOG_RC, "Next block: {:?}", now + 1);
<polkadot_runtime::RcMigrator as frame_support::traits::OnFinalize<_>>::on_finalize(now);
frame_system::Pallet::<polkadot_runtime::Runtime>::set_block_number(now + 1);
frame_system::Pallet::<polkadot_runtime::Runtime>::reset_events();
<polkadot_runtime::RcMigrator as frame_support::traits::OnInitialize<_>>::on_initialize(
now + 1,
);
Expand All @@ -69,5 +70,6 @@ pub fn next_block_ah() {
);
frame_system::Pallet::<asset_hub_polkadot_runtime::Runtime>::set_block_number(now + 1);
<asset_hub_polkadot_runtime::MessageQueue as frame_support::traits::OnInitialize<_>>::on_initialize(now + 1);
frame_system::Pallet::<polkadot_runtime::Runtime>::reset_events();
<asset_hub_polkadot_runtime::AhMigrator as frame_support::traits::OnInitialize<_>>::on_initialize(now + 1);
}
58 changes: 44 additions & 14 deletions integration-tests/ahm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use cumulus_primitives_core::{AggregateMessageOrigin, ParaId};
use frame_support::{pallet_prelude::*, traits::*, weights::WeightMeter};
use pallet_rc_migrator::RcMigrationStage;
use polkadot_primitives::InboundDownwardMessage;
use polkadot_runtime::RcMigrator;
use sp_core::H256;
use sp_io::TestExternalities;
Expand All @@ -48,34 +49,63 @@ use super::mock::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn account_migration_works() {
let Some((mut rc, mut ah)) = load_externalities().await else { return };
let para_id = ParaId::from(1000);

// Simulate 10 relay blocks and grab the DMP messages
let dmp_messages = rc.execute_with(|| {
for _ in 0..10 {
let mut dmps = Vec::new();

// Loop until no more DMPs are added and we had at least 1
loop {
next_block_rc();

let new_dmps =
runtime_parachains::dmp::DownwardMessageQueues::<Polkadot>::take(para_id);
if new_dmps.is_empty() && !dmps.is_empty() {
break;
}
dmps.extend(new_dmps);
}

// DMP:
let para_id = ParaId::from(1000);
runtime_parachains::dmp::DownwardMessageQueues::<Polkadot>::take(para_id)
dmps
});
rc.commit_all().unwrap();
log::info!("Num of RC->AH DMP messages: {}", dmp_messages.len());

// Inject the DMP messages into the Asset Hub
ah.execute_with(|| {
// We bypass `set_validation_data` and `enqueue_inbound_downward_messages` by just directly
// enqueuing them.
for msg in dmp_messages {
let bounded_msg: BoundedVec<u8, _> = msg.msg.try_into().expect("DMP message too big");
asset_hub_polkadot_runtime::MessageQueue::enqueue_message(
bounded_msg.as_bounded_slice(),
AggregateMessageOrigin::Parent,
);
}
let mut fp =
asset_hub_polkadot_runtime::MessageQueue::footprint(AggregateMessageOrigin::Parent);
enqueue_dmp(dmp_messages);

for _ in 0..10 {
// Loop until no more DMPs are queued
loop {
let new_fp =
asset_hub_polkadot_runtime::MessageQueue::footprint(AggregateMessageOrigin::Parent);
if fp == new_fp {
log::info!("AH DMP messages left: {}", fp.storage.count);
break;
}
fp = new_fp;

log::debug!("AH DMP messages left: {}", fp.storage.count);
next_block_ah();
}
// NOTE that the DMP queue is probably not empty because the snapshot that we use contains
// some overweight ones.
});
}

/// Enqueue DMP messages on the parachain side.
///
/// This bypasses `set_validation_data` and `enqueue_inbound_downward_messages` by just directly
/// enqueuing them.
fn enqueue_dmp(msgs: Vec<InboundDownwardMessage>) {
for msg in msgs {
let bounded_msg: BoundedVec<u8, _> = msg.msg.try_into().expect("DMP message too big");
asset_hub_polkadot_runtime::MessageQueue::enqueue_message(
bounded_msg.as_bounded_slice(),
AggregateMessageOrigin::Parent,
);
}
}
27 changes: 13 additions & 14 deletions pallets/ah-migrator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,26 @@ repository.workspace = true

[dependencies]
codec = { workspace = true, features = ["max-encoded-len"] }
scale-info = { workspace = true, features = ["derive"] }
serde = { features = ["derive"], optional = true, workspace = true }
log = { workspace = true }

frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
sp-io = { workspace = true }

pallet-rc-migrator = { workspace = true }
log = { workspace = true }
pallet-balances = { workspace = true }
pallet-staking = { workspace = true }
pallet-nomination-pools = { workspace = true }
pallet-preimage = { workspace = true }
pallet-rc-migrator = { workspace = true }
pallet-staking = { workspace = true }
pallet-state-trie-migration = { workspace = true }
pallet-nomination-pools = { workspace = true }

polkadot-parachain-primitives = { workspace = true }
polkadot-runtime-common = { workspace = true }
runtime-parachains = { workspace = true }
polkadot-parachain-primitives = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
serde = { features = ["derive"], optional = true, workspace = true }
sp-application-crypto = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

[features]
default = ["std"]
Expand All @@ -51,6 +49,7 @@ std = [
"runtime-parachains/std",
"scale-info/std",
"serde",
"sp-application-crypto/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
Expand Down
136 changes: 88 additions & 48 deletions pallets/ah-migrator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub use pallet::*;

use frame_support::{
pallet_prelude::*,
storage::{transactional::with_transaction_opaque_err, TransactionOutcome},
traits::{
fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold},
LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons,
Expand All @@ -44,6 +45,7 @@ use frame_support::{
use frame_system::pallet_prelude::*;
use pallet_balances::{AccountData, Reasons as LockReasons};
use pallet_rc_migrator::accounts::Account as RcAccount;
use sp_application_crypto::Ss58Codec;
use sp_runtime::{traits::Convert, AccountId32};
use sp_std::prelude::*;

Expand Down Expand Up @@ -111,66 +113,104 @@ pub mod pallet {
pub fn receive_accounts(
origin: OriginFor<T>,
accounts: Vec<RcAccount<T::AccountId, T::Balance, T::RcHoldReason, T::RcFreezeReason>>,
) -> DispatchResultWithPostInfo {
) -> DispatchResult {
ensure_root(origin)?;

Self::do_receive_accounts(accounts)?;
// TODO: publish event

Ok(())
}
}

impl<T: Config> Pallet<T> {
fn do_receive_accounts(
accounts: Vec<RcAccount<T::AccountId, T::Balance, T::RcHoldReason, T::RcFreezeReason>>,
) -> Result<(), Error<T>> {
log::debug!(target: LOG_TARGET, "Integrating {} accounts", accounts.len());

for account in accounts {
let who = account.who;
let total_balance = account.free + account.reserved;
let minted = T::Currency::mint_into(&who, total_balance)
// TODO handle error
.unwrap();
debug_assert!(minted == total_balance);

for hold in account.holds {
let _: Result<(), ()> = with_transaction_opaque_err::<(), (), _>(|| {
match Self::do_receive_account(account) {
Ok(()) => TransactionOutcome::Commit(Ok(())),
Err(_) => TransactionOutcome::Rollback(Ok(())),
}
})
.expect("Always returning Ok; qed");
}

Ok(())
}

/// MAY CHANGED STORAGE ON ERROR RETURN
fn do_receive_account(
account: RcAccount<T::AccountId, T::Balance, T::RcHoldReason, T::RcFreezeReason>,
) -> Result<(), Error<T>> {
let who = account.who;
let total_balance = account.free + account.reserved;
let minted = match T::Currency::mint_into(&who, total_balance) {
Ok(minted) => minted,
Err(e) => {
log::error!(target: LOG_TARGET, "Failed to mint into account {}: {:?}", who.to_ss58check(), e);
return Err(Error::<T>::TODO);
},
};
debug_assert!(minted == total_balance);

for hold in account.holds {
if let Err(e) =
T::Currency::hold(&T::RcToAhHoldReason::convert(hold.id), &who, hold.amount)
// TODO handle error
.unwrap();
{
log::error!(target: LOG_TARGET, "Failed to hold into account {}: {:?}", who.to_ss58check(), e);
return Err(Error::<T>::TODO);
}
}

T::Currency::reserve(&who, account.unnamed_reserve)
// TODO handle error
.unwrap();

for freeze in account.freezes {
T::Currency::set_freeze(
&T::RcToAhFreezeReason::convert(freeze.id),
&who,
freeze.amount,
)
// TODO handle error
.unwrap();
}
if let Err(e) = T::Currency::reserve(&who, account.unnamed_reserve) {
log::error!(target: LOG_TARGET, "Failed to reserve into account {}: {:?}", who.to_ss58check(), e);
return Err(Error::<T>::TODO);
}

for lock in account.locks {
T::Currency::set_lock(
lock.id,
&who,
lock.amount,
types::map_lock_reason(lock.reasons),
);
for freeze in account.freezes {
if let Err(e) = T::Currency::set_freeze(
&T::RcToAhFreezeReason::convert(freeze.id),
&who,
freeze.amount,
) {
log::error!(target: LOG_TARGET, "Failed to freeze into account {}: {:?}", who.to_ss58check(), e);
return Err(Error::<T>::TODO);
}
}

let storage_account = pallet_balances::Account::<T>::get(&who);
debug_assert!(storage_account.free == account.free);
debug_assert!(storage_account.frozen == account.frozen);
debug_assert!(storage_account.reserved == account.reserved);

(0..account.consumers).for_each(|_| {
frame_system::Pallet::<T>::inc_consumers(&who)
// TODO handle error
.unwrap();
});
(0..account.providers).for_each(|_| {
frame_system::Pallet::<T>::inc_providers(&who);
});

// TODO: publish event
for lock in account.locks {
T::Currency::set_lock(
lock.id,
&who,
lock.amount,
types::map_lock_reason(lock.reasons),
);
}

// TODO: publish event
log::debug!(
target: LOG_TARGET,
"Integrating account: {}", who.to_ss58check(),
);

// TODO run some post-migration sanity checks

Ok(().into())
// Apply all additional consumers that were excluded from the balance stuff above:
for _ in 0..account.consumers {
if let Err(e) = frame_system::Pallet::<T>::inc_consumers(&who) {
log::error!(target: LOG_TARGET, "Failed to inc consumers for account {}: {:?}", who.to_ss58check(), e);
return Err(Error::<T>::TODO);
}
}
for _ in 0..account.providers {
frame_system::Pallet::<T>::inc_providers(&who);
}

// TODO: publish event
Ok(())
}
}

Expand Down
Loading

0 comments on commit dde9960

Please sign in to comment.