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

[AHM] Improve account migration #532

Merged
merged 2 commits into from
Jan 8, 2025
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
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
Loading