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,
    }
);

使用说明

  1. 定义迁移逻辑

    • 实现OnRuntimeUpgrade trait来定义具体的迁移逻辑
    • 使用translate方法转换旧存储数据到新格式
    • 管理存储版本以确保迁移只执行一次
  2. 测试迁移

    #[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);
        });
    }
    
  3. 执行迁移

    • 通过sudogovernance模块调用migrations::migrate函数
    • 或者通过on_runtime_upgrade钩子自动执行

pallet-migrations提供了一种安全可靠的方式来管理Substrate区块链的运行时升级和数据迁移,确保链上数据的一致性和完整性。


1 回复

Rust区块链迁移工具pallet-migrations的使用指南

概述

pallet-migrations是Substrate框架中的一个运行时模块,专门设计用于简化区块链运行时升级和数据迁移过程。它提供了一种结构化的方式来管理运行时升级,确保在链上升级时能够平滑过渡并正确处理数据迁移。

主要特性

  1. 提供标准化的迁移接口
  2. 支持多阶段迁移
  3. 允许迁移失败恢复
  4. 提供迁移进度跟踪
  5. 与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>},
    }
);

最佳实践

  1. 测试迁移:始终在测试网上测试迁移逻辑
  2. 权重计算:准确计算迁移操作的权重
  3. 错误处理:实现适当的错误处理和恢复机制
  4. 版本控制:使用语义化版本控制迁移
  5. 文档记录:详细记录每个迁移的目的和变更

注意事项

  1. 迁移操作会消耗链上资源,应尽量优化
  2. 复杂的迁移可能需要分多个区块执行
  3. 确保迁移逻辑是幂等的
  4. 考虑回滚场景,设计可逆的迁移方案

通过pallet-migrations,Substrate开发者可以更安全、更可靠地管理运行时升级和数据迁移过程,大大降低了区块链升级的风险。

回到顶部