feat: tiki v2 migration - populate TikiHolder from UserTikis
Storage migration v1->v2 scans all UserTikis entries and populates TikiHolder for unique roles (Serok, SerokiMeclise, Xezinedar, Balyoz). Fixes empty TikiHolder on live chain despite roles being assigned. Includes try-runtime pre/post upgrade checks and unit tests.
This commit is contained in:
@@ -8,7 +8,7 @@ use pezframe_support::{
|
|||||||
use pezsp_std::marker::PhantomData;
|
use pezsp_std::marker::PhantomData;
|
||||||
|
|
||||||
/// Current storage version
|
/// Current storage version
|
||||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
|
||||||
|
|
||||||
/// Migration from v0 to v1
|
/// Migration from v0 to v1
|
||||||
/// This is a template migration that can be customized based on actual storage changes
|
/// This is a template migration that can be customized based on actual storage changes
|
||||||
@@ -26,20 +26,11 @@ pub mod v1 {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if current == StorageVersion::new(0) {
|
if current == StorageVersion::new(0) {
|
||||||
// Perform migration logic here
|
|
||||||
// Example: Iterate over storage and transform data
|
|
||||||
|
|
||||||
let migrated = 0u64;
|
let migrated = 0u64;
|
||||||
let mut weight = Weight::zero();
|
let mut weight = Weight::zero();
|
||||||
|
|
||||||
// Example: Migrate CitizenNft storage if format changed
|
// Update storage version to v1
|
||||||
// for (account, nft_id) in CitizenNft::<T>::iter() {
|
StorageVersion::new(1).put::<Pezpallet<T>>();
|
||||||
// // Transform data if needed
|
|
||||||
// migrated += 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Update storage version
|
|
||||||
STORAGE_VERSION.put::<Pezpallet<T>>();
|
|
||||||
|
|
||||||
log::info!("✅ Migrated {migrated} entries in pezpallet-tiki");
|
log::info!("✅ Migrated {migrated} entries in pezpallet-tiki");
|
||||||
|
|
||||||
@@ -125,12 +116,12 @@ pub mod v1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Example migration for future version changes
|
/// Migration v1 -> v2: Populate TikiHolder from UserTikis
|
||||||
/// This demonstrates how to handle storage item renames or format changes
|
/// Fixes: TikiHolder was empty despite unique roles (Serok) being assigned.
|
||||||
|
/// This scans all UserTikis entries and populates TikiHolder for unique roles.
|
||||||
pub mod v2 {
|
pub mod v2 {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Example: Migration when storage format changes
|
|
||||||
pub struct MigrateToV2<T>(PhantomData<T>);
|
pub struct MigrateToV2<T>(PhantomData<T>);
|
||||||
|
|
||||||
impl<T: Config> OnRuntimeUpgrade for MigrateToV2<T> {
|
impl<T: Config> OnRuntimeUpgrade for MigrateToV2<T> {
|
||||||
@@ -138,62 +129,189 @@ pub mod v2 {
|
|||||||
let current = Pezpallet::<T>::on_chain_storage_version();
|
let current = Pezpallet::<T>::on_chain_storage_version();
|
||||||
|
|
||||||
if current < StorageVersion::new(2) {
|
if current < StorageVersion::new(2) {
|
||||||
log::info!("🔄 Running migration for pezpallet-tiki to v2");
|
log::info!("Running migration for pezpallet-tiki v1 -> v2");
|
||||||
|
|
||||||
// Example migration logic
|
let mut reads = 1u64; // version read
|
||||||
// 1. Create new storage with modified format
|
let mut writes = 1u64; // version write
|
||||||
// 2. Migrate data from old storage to new
|
let mut holders_fixed = 0u32;
|
||||||
// 3. Remove old storage
|
|
||||||
// 4. Update version
|
// Scan all UserTikis to find unique role holders
|
||||||
|
for (account, tikis) in UserTikis::<T>::iter() {
|
||||||
|
reads += 1;
|
||||||
|
for tiki in tikis.iter() {
|
||||||
|
if Pezpallet::<T>::is_unique_role(tiki) {
|
||||||
|
// Only set if not already populated
|
||||||
|
if TikiHolder::<T>::get(tiki).is_none() {
|
||||||
|
TikiHolder::<T>::insert(tiki, account.clone());
|
||||||
|
holders_fixed += 1;
|
||||||
|
writes += 1;
|
||||||
|
}
|
||||||
|
reads += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For now, this is just a template
|
|
||||||
STORAGE_VERSION.put::<Pezpallet<T>>();
|
STORAGE_VERSION.put::<Pezpallet<T>>();
|
||||||
|
|
||||||
log::info!("✅ Completed migration to pezpallet-tiki v2");
|
log::info!(
|
||||||
|
"Completed pezpallet-tiki v2 migration: fixed {holders_fixed} TikiHolder entries"
|
||||||
|
);
|
||||||
|
|
||||||
T::DbWeight::get().reads_writes(1, 1)
|
T::DbWeight::get().reads_writes(reads, writes)
|
||||||
} else {
|
} else {
|
||||||
log::info!("👌 pezpallet-tiki v2 migration not needed");
|
log::info!("pezpallet-tiki v2 migration not needed, current: {current:?}");
|
||||||
T::DbWeight::get().reads(1)
|
T::DbWeight::get().reads(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn pre_upgrade() -> Result<pezsp_std::vec::Vec<u8>, pezsp_runtime::TryRuntimeError> {
|
||||||
|
use codec::Encode;
|
||||||
|
|
||||||
|
let tiki_holder_count = TikiHolder::<T>::iter().count() as u32;
|
||||||
|
log::info!("Pre-upgrade: TikiHolder entries = {tiki_holder_count}");
|
||||||
|
|
||||||
|
Ok(tiki_holder_count.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn post_upgrade(
|
||||||
|
state: pezsp_std::vec::Vec<u8>,
|
||||||
|
) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||||
|
use codec::Decode;
|
||||||
|
|
||||||
|
let pre_count: u32 =
|
||||||
|
Decode::decode(&mut &state[..]).map_err(|_| "Failed to decode pre-upgrade state")?;
|
||||||
|
let post_count = TikiHolder::<T>::iter().count() as u32;
|
||||||
|
|
||||||
|
log::info!("Post-upgrade: TikiHolder {pre_count} -> {post_count}");
|
||||||
|
|
||||||
|
// Should have at least as many entries as before
|
||||||
|
assert!(
|
||||||
|
post_count >= pre_count,
|
||||||
|
"TikiHolder entries decreased during migration"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify consistency: every unique role in UserTikis has a TikiHolder entry
|
||||||
|
for (account, tikis) in UserTikis::<T>::iter() {
|
||||||
|
for tiki in tikis.iter() {
|
||||||
|
if Pezpallet::<T>::is_unique_role(tiki) {
|
||||||
|
let holder = TikiHolder::<T>::get(tiki);
|
||||||
|
assert!(
|
||||||
|
holder.is_some(),
|
||||||
|
"Unique role missing from TikiHolder after migration"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
holder.unwrap(),
|
||||||
|
account,
|
||||||
|
"TikiHolder mismatch for unique role"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Pezpallet::<T>::on_chain_storage_version(),
|
||||||
|
STORAGE_VERSION,
|
||||||
|
"Storage version not updated"
|
||||||
|
);
|
||||||
|
|
||||||
|
log::info!("Post-upgrade checks passed for pezpallet-tiki v2");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mock::{new_test_ext, Test};
|
use crate::{
|
||||||
|
mock::{new_test_ext, Test},
|
||||||
|
pezpallet::{CitizenNft, Tiki, TikiHolder, UserTikis},
|
||||||
|
};
|
||||||
use pezframe_support::traits::OnRuntimeUpgrade;
|
use pezframe_support::traits::OnRuntimeUpgrade;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_migration_v1() {
|
fn test_migration_v1() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
// Set initial storage version to 0
|
|
||||||
StorageVersion::new(0).put::<Pezpallet<Test>>();
|
StorageVersion::new(0).put::<Pezpallet<Test>>();
|
||||||
|
|
||||||
// Run migration
|
|
||||||
let weight = v1::MigrateToV1::<Test>::on_runtime_upgrade();
|
let weight = v1::MigrateToV1::<Test>::on_runtime_upgrade();
|
||||||
|
|
||||||
// Verify version was updated
|
assert_eq!(Pezpallet::<Test>::on_chain_storage_version(), StorageVersion::new(1));
|
||||||
assert_eq!(Pezpallet::<Test>::on_chain_storage_version(), STORAGE_VERSION);
|
|
||||||
|
|
||||||
// Verify weight is non-zero
|
|
||||||
assert!(weight != Weight::zero());
|
assert!(weight != Weight::zero());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_migration_idempotent() {
|
fn test_migration_v2_populates_tiki_holder() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
StorageVersion::new(1).put::<Pezpallet<Test>>();
|
||||||
|
|
||||||
|
// Simulate on-chain state: account 1 has Serok in UserTikis but TikiHolder is empty
|
||||||
|
let account: u64 = 1;
|
||||||
|
CitizenNft::<Test>::insert(&account, 0u32);
|
||||||
|
UserTikis::<Test>::mutate(&account, |tikis| {
|
||||||
|
let _ = tikis.try_push(Tiki::Welati);
|
||||||
|
let _ = tikis.try_push(Tiki::Serok);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TikiHolder should be empty before migration
|
||||||
|
assert!(TikiHolder::<Test>::get(Tiki::Serok).is_none());
|
||||||
|
|
||||||
|
// Run migration
|
||||||
|
let weight = v2::MigrateToV2::<Test>::on_runtime_upgrade();
|
||||||
|
|
||||||
|
// TikiHolder should now have Serok -> account 1
|
||||||
|
assert_eq!(TikiHolder::<Test>::get(Tiki::Serok), Some(account));
|
||||||
|
assert_eq!(Pezpallet::<Test>::on_chain_storage_version(), STORAGE_VERSION);
|
||||||
|
assert!(weight != Weight::zero());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_migration_v2_idempotent() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
// Set current version
|
|
||||||
STORAGE_VERSION.put::<Pezpallet<Test>>();
|
STORAGE_VERSION.put::<Pezpallet<Test>>();
|
||||||
|
|
||||||
// Run migration again
|
let weight = v2::MigrateToV2::<Test>::on_runtime_upgrade();
|
||||||
let weight = v1::MigrateToV1::<Test>::on_runtime_upgrade();
|
|
||||||
|
|
||||||
// Should be a no-op
|
// Should be a no-op
|
||||||
assert_eq!(weight, pezframe_support::weights::constants::RocksDbWeight::get().reads(1));
|
assert_eq!(weight, pezframe_support::weights::constants::RocksDbWeight::get().reads(1));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_migration_v2_multiple_unique_roles() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
StorageVersion::new(1).put::<Pezpallet<Test>>();
|
||||||
|
|
||||||
|
// Account 1: Serok
|
||||||
|
CitizenNft::<Test>::insert(&1u64, 0u32);
|
||||||
|
UserTikis::<Test>::mutate(&1u64, |tikis| {
|
||||||
|
let _ = tikis.try_push(Tiki::Welati);
|
||||||
|
let _ = tikis.try_push(Tiki::Serok);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Account 2: SerokiMeclise
|
||||||
|
CitizenNft::<Test>::insert(&2u64, 1u32);
|
||||||
|
UserTikis::<Test>::mutate(&2u64, |tikis| {
|
||||||
|
let _ = tikis.try_push(Tiki::Welati);
|
||||||
|
let _ = tikis.try_push(Tiki::SerokiMeclise);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Account 3: just Welati (no unique role)
|
||||||
|
CitizenNft::<Test>::insert(&3u64, 2u32);
|
||||||
|
UserTikis::<Test>::mutate(&3u64, |tikis| {
|
||||||
|
let _ = tikis.try_push(Tiki::Welati);
|
||||||
|
});
|
||||||
|
|
||||||
|
v2::MigrateToV2::<Test>::on_runtime_upgrade();
|
||||||
|
|
||||||
|
assert_eq!(TikiHolder::<Test>::get(Tiki::Serok), Some(1u64));
|
||||||
|
assert_eq!(TikiHolder::<Test>::get(Tiki::SerokiMeclise), Some(2u64));
|
||||||
|
assert!(TikiHolder::<Test>::get(Tiki::Xezinedar).is_none());
|
||||||
|
assert!(TikiHolder::<Test>::get(Tiki::Balyoz).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user