use super::*;
use frame_support::{
migrations::VersionedMigration, pallet_prelude::ValueQuery, storage_alias,
traits::UncheckedOnRuntimeUpgrade, weights::Weight,
};
mod v0 {
use super::*;
use alloc::collections::vec_deque::VecDeque;
#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone)]
pub(super) struct EnqueuedOrder {
pub para_id: ParaId,
}
#[storage_alias]
pub(super) type SpotTraffic<T: Config> = StorageValue<Pallet<T>, FixedU128, ValueQuery>;
#[storage_alias]
pub(super) type OnDemandQueue<T: Config> =
StorageValue<Pallet<T>, VecDeque<EnqueuedOrder>, ValueQuery>;
}
mod v1 {
use super::*;
use crate::on_demand::LOG_TARGET;
pub struct UncheckedMigrateToV1<T>(core::marker::PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight: Weight = Weight::zero();
let config = configuration::ActiveConfig::<T>::get();
QueueStatus::<T>::mutate(|mut queue_status| {
Pallet::<T>::update_spot_traffic(&config, &mut queue_status);
let v0_queue = v0::OnDemandQueue::<T>::take();
v0_queue.into_iter().for_each(|enqueued_order| {
Pallet::<T>::add_on_demand_order(
queue_status,
enqueued_order.para_id,
QueuePushDirection::Back,
);
});
});
v0::OnDemandQueue::<T>::kill(); v0::SpotTraffic::<T>::kill(); weight.saturating_accrue(T::DbWeight::get().reads(1));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
weight.saturating_accrue(T::DbWeight::get().writes(2));
log::info!(target: LOG_TARGET, "Migrated on demand assigner storage to v1");
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
let n: u32 = v0::OnDemandQueue::<T>::get().len() as u32;
log::info!(
target: LOG_TARGET,
"Number of orders waiting in the queue before: {n}",
);
Ok(n.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
log::info!(target: LOG_TARGET, "Running post_upgrade()");
ensure!(
v0::OnDemandQueue::<T>::get().is_empty(),
"OnDemandQueue should be empty after the migration"
);
let expected_len = u32::decode(&mut &state[..]).unwrap();
let queue_status_size = QueueStatus::<T>::get().size();
ensure!(
expected_len == queue_status_size,
"Number of orders should be the same before and after migration"
);
let n_affinity_entries: u32 =
AffinityEntries::<T>::iter().map(|(_index, heap)| heap.len() as u32).sum();
let n_para_id_affinity: u32 = ParaIdAffinity::<T>::iter()
.map(|(_para_id, affinity)| affinity.count as u32)
.sum();
ensure!(
n_para_id_affinity == n_affinity_entries,
"Number of affinity entries should be the same as the counts in ParaIdAffinity"
);
Ok(())
}
}
}
pub type MigrateV0ToV1<T> = VersionedMigration<
0,
1,
v1::UncheckedMigrateToV1<T>,
Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
#[cfg(test)]
mod tests {
use super::{v0, v1, UncheckedOnRuntimeUpgrade, Weight};
use crate::mock::{new_test_ext, MockGenesisConfig, OnDemand, Test};
use polkadot_primitives::Id as ParaId;
#[test]
fn migration_to_v1_preserves_queue_ordering() {
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
for i in 1..=5 {
v0::OnDemandQueue::<Test>::mutate(|queue| {
queue.push_back(v0::EnqueuedOrder { para_id: ParaId::new(i) })
});
}
let old_queue = v0::OnDemandQueue::<Test>::get();
assert_eq!(old_queue.len(), 5);
assert_eq!(OnDemand::get_queue_status().size(), 0);
assert_eq!(
<v1::UncheckedMigrateToV1<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(),
Weight::zero()
);
assert_eq!(OnDemand::get_queue_status().size(), 5);
old_queue.iter().zip(OnDemand::get_free_entries().iter()).for_each(
|(old_enq, new_enq)| {
assert_eq!(old_enq.para_id, new_enq.para_id);
},
);
});
}
}