Rust区块链迁移工具pallet-migrations的使用,实现Substrate运行时模块的无缝升级与数据迁移
Rust区块链迁移工具pallet-migrations的使用,实现Substrate运行时模块的无缝升级与数据迁移
安装
在项目目录中运行以下Cargo命令:
cargo add pallet-migrations
或者在Cargo.toml中添加以下行:
pallet-migrations = "11.0.0"
完整示例demo
下面是一个使用pallet-migrations实现Substrate运行时模块升级和数据迁移的完整示例:
// 在runtime/src/lib.rs中
// 1. 引入必要的依赖
use frame_support::{
pallet_prelude::*,
traits::OnRuntimeUpgrade,
};
use pallet_migrations::{self as migrations};
// 2. 定义旧版本存储结构
pub mod old {
#[frame_support::storage_alias]
pub type MyOldStorage<T: Config> = StorageValue<
pallet_my_pallet::Pallet<T>,
Vec<u32>,
ValueQuery
>;
}
// 3. 定义迁移逻辑
pub struct MyMigration;
impl OnRuntimeUpgrade for MyMigration {
fn on_runtime_upgrade() -> Weight {
// 检查是否需要迁移
if migrations::StorageVersion::<Pallet<MyRuntime>>::get() == 0 {
// 执行数据迁移
old::MyOldStorage::<MyRuntime>::translate(|old_data: Vec<u32>| {
Some(old_data.into_iter().map(|x| x * 2).collect::<Vec<_>>())
});
// 更新存储版本
migrations::StorageVersion::<Pallet<MyRuntime>>::put(1);
// 返回迁移消耗的权重
Weight::from_parts(100, 0)
} else {
Weight::zero()
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
// 迁移前检查
Ok(vec![])
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
// 迁移后验证
Ok(())
}
}
// 4. 在runtime配置中集成
impl pallet_migrations::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Migrations = (MyMigration,);
}
// 5. 在construct_runtime!宏中添加pallet
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
// ...其他pallet
Migrations: pallet_migrations,
MyPallet: pallet_my_pallet,
}
);
使用说明
-
定义迁移逻辑:
- 实现
OnRuntimeUpgrade
trait来定义具体的迁移逻辑 - 使用
translate
方法转换旧存储数据到新格式 - 管理存储版本以确保迁移只执行一次
- 实现
-
测试迁移:
#[test] fn test_migration() { new_test_ext().execute_with(|| { // 设置旧版本数据 old::MyOldStorage::<Test>::put(vec![1, 2, 3]); // 执行迁移 MyMigration::on_runtime_upgrade(); // 验证迁移结果 assert_eq!(MyPallet::new_storage(), vec![2, 4, 6]); assert_eq!(migrations::StorageVersion::<Pallet<Test>>::get(), 1); }); }
-
执行迁移:
- 通过
sudo
或governance
模块调用migrations::migrate
函数 - 或者通过
on_runtime_upgrade
钩子自动执行
- 通过
pallet-migrations提供了一种安全可靠的方式来管理Substrate区块链的运行时升级和数据迁移,确保链上数据的一致性和完整性。
1 回复
Rust区块链迁移工具pallet-migrations的使用指南
概述
pallet-migrations
是Substrate框架中的一个运行时模块,专门设计用于简化区块链运行时升级和数据迁移过程。它提供了一种结构化的方式来管理运行时升级,确保在链上升级时能够平滑过渡并正确处理数据迁移。
主要特性
- 提供标准化的迁移接口
- 支持多阶段迁移
- 允许迁移失败恢复
- 提供迁移进度跟踪
- 与Substrate的链上升级机制无缝集成
基本使用方法
1. 添加依赖
首先在runtime/Cargo.toml
中添加依赖:
[dependencies]
pallet-migrations = { version = "4.0.0", default-features = false }
2. 实现配置trait
在运行时中实现pallet_migrations
的配置trait:
impl pallet_migrations::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type WeightInfo = pallet_migrations::weights::SubstrateWeight<Runtime>;
}
3. 将pallet添加到runtime
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
// ... 其他pallet
Migrations: pallet_migrations::{Pallet, Call, Storage, Event<T>},
}
);
实际应用示例
定义迁移逻辑
pub struct MyMigration;
impl OnRuntimeUpgrade for MyMigration {
fn on_runtime_upgrade() -> Weight {
// 执行数据迁移逻辑
// 例如:将旧存储结构转换为新存储结构
// 返回执行消耗的权重
Weight::from_parts(1000, 0)
}
fn pre_upgrade() -> Result<(), &'static str> {
// 升级前的准备工作
Ok(())
}
fn post_upgrade() -> Result<(), &'static str> {
// 升级后的验证工作
Ok(())
}
}
注册迁移任务
pub type Migrations = (
pallet_migrations::migrations::VersionedMigration<
0,
1,
MyMigration,
pallet_migrations::pallet::Pallet<Runtime>,
(),
>,
);
在runtime中设置迁移
parameter_types! {
pub const MigrationsTaskList: &'static [frame_support::traits::MigrationTask] = &[
frame_support::traits::MigrationTask {
name: "MyMigration",
steps: 1,
},
];
}
impl pallet_migrations::Config for Runtime {
// ... 其他配置
type MigrationsTaskList = MigrationsTaskList;
}
高级用法
多阶段迁移
pub struct Phase1Migration;
pub struct Phase2Migration;
pub type Migrations = (
pallet_migrations::migrations::VersionedMigration<
0,
1,
Phase1Migration,
pallet_migrations::pallet::Pallet<Runtime>,
(),
>,
pallet_migrations::migrations::VersionedMigration<
1,
2,
Phase2Migration,
pallet_migrations::pallet::Pallet<Runtime>,
(),
>,
);
带参数的迁移
pub struct ParametrizedMigration<Runtime>(PhantomData<Runtime>);
impl<Runtime: Config> OnRuntimeUpgrade for ParametrizedMigration<Runtime> {
// 实现细节
}
pub type Migrations = (
pallet_migrations::migrations::VersionedMigration<
0,
1,
ParametrizedMigration<Runtime>,
pallet_migrations::pallet::Pallet<Runtime>,
(u32, u64), // 迁移参数
>,
);
完整示例Demo
以下是一个完整的pallet-migrations使用示例:
// 1. 在runtime/Cargo.toml中添加依赖
/*
[dependencies]
pallet-migrations = { version = "4.0.0", default-features = false }
*/
// 2. 定义迁移逻辑
pub struct UserDataMigration;
impl OnRuntimeUpgrade for UserDataMigration {
fn on_runtime_upgrade() -> Weight {
// 迁移旧用户数据到新格式
UserData::translate(|_key, old: OldUser| {
Some(NewUser {
id: old.id,
name: old.name,
balance: old.balance.checked_mul(100).unwrap(), // 转换单位
})
});
// 返回消耗的权重
Weight::from_parts(5000, 0)
}
fn pre_upgrade() -> Result<(), &'static str> {
// 检查旧数据是否存在
if UserData::iter().count() == 0 {
return Err("No user data to migrate");
}
Ok(())
}
fn post_upgrade() -> Result<(), &'static str> {
// 验证迁移后的数据
if NewUserData::iter().count() == 0 {
return Err("Migration failed - no data migrated");
}
Ok(())
}
}
// 3. 注册迁移任务
pub type Migrations = (
pallet_migrations::migrations::VersionedMigration<
0,
1,
UserDataMigration,
pallet_migrations::pallet::Pallet<Runtime>,
(),
>,
);
// 4. 在runtime中配置
parameter_types! {
pub const MigrationsTaskList: &'static [frame_support::traits::MigrationTask] = &[
frame_support::traits::MigrationTask {
name: "UserDataMigration",
steps: 1,
},
];
}
impl pallet_migrations::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type WeightInfo = pallet_migrations::weights::SubstrateWeight<Runtime>;
type MigrationsTaskList = MigrationsTaskList;
}
// 5. 将pallet添加到runtime
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
// ... 其他pallet
Migrations: pallet_migrations::{Pallet, Call, Storage, Event<T>},
}
);
最佳实践
- 测试迁移:始终在测试网上测试迁移逻辑
- 权重计算:准确计算迁移操作的权重
- 错误处理:实现适当的错误处理和恢复机制
- 版本控制:使用语义化版本控制迁移
- 文档记录:详细记录每个迁移的目的和变更
注意事项
- 迁移操作会消耗链上资源,应尽量优化
- 复杂的迁移可能需要分多个区块执行
- 确保迁移逻辑是幂等的
- 考虑回滚场景,设计可逆的迁移方案
通过pallet-migrations
,Substrate开发者可以更安全、更可靠地管理运行时升级和数据迁移过程,大大降低了区块链升级的风险。