Rust Substrate框架pallet-parameters的使用:实现链上可配置参数的动态管理与存储优化

Rust Substrate框架pallet-parameters的使用:实现链上可配置参数的动态管理与存储优化

安装

在项目目录中运行以下Cargo命令:

cargo add pallet-parameters

或者在Cargo.toml中添加以下行:

pallet-parameters = "0.12.0"

示例代码

以下是使用pallet-parameters实现链上可配置参数的完整示例:

// 在runtime/src/lib.rs中配置pallet

// 1. 引入pallet_parameters
pub use pallet_parameters;

// 2. 配置trait
impl pallet_parameters::Config for Runtime {
    type Event = Event;
    type WeightInfo = ();
}

// 3. 将pallet添加到construct_runtime宏中
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // ...其他pallet
        Parameters: pallet_parameters::{Module, Call, Storage, Event<T>},
    }
);

// 在pallet中使用参数的示例
#[frame_support::pallet]
pub mod pallet_example {
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;
    
    #[pallet::config]
    pub trait Config: frame_system::Config + pallet_parameters::Config {
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
    }
    
    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet<T>(_);
    
    #[pallet::storage]
    #[pallet::getter(fn some_value)]
    pub type SomeValue<T: Config> = StorageValue<_, u32, ValueQuery>;
    
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        ParameterUpdated(u32),
    }
    
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::weight(10_000)]
        pub fn update_parameter(
            origin: OriginFor<T>,
            new_value: u32,
        ) -> DispatchResult {
            let _ = ensure_signed(origin)?;
            
            // 使用pallet-parameters存储参数
            pallet_parameters::Pallet::<T>::set_parameter(
                b"example_parameter",
                &new_value,
            )?;
            
            // 也可以存储在自己的pallet中
            <SomeValue<T>>::put(new_value);
            
            Self::deposit_event(Event::ParameterUpdated(new_value));
            Ok(())
        }
        
        #[pallet::weight(10_000)]
        pub fn get_parameter(
            origin: OriginFor<T>,
        ) -> DispatchResultWithPostInfo {
            let _ = ensure_signed(origin)?;
            
            // 从pallet-parameters获取参数
            let param: u32 = pallet_parameters::Pallet::<T>::get_parameter(
                b"example_parameter"
            )?;
            
            log::info!("Current parameter value: {}", param);
            Ok(().into())
        }
    }
}

完整示例demo

以下是基于上述内容的完整示例实现,展示了如何使用pallet-parameters进行链上参数管理:

// runtime/src/lib.rs
pub mod runtime {
    // ...其他引入
    
    // 引入pallet_parameters
    pub use pallet_parameters;

    // 配置pallet_parameters的trait
    impl pallet_parameters::Config for Runtime {
        type Event = Event;
        type WeightInfo = pallet_parameters::weights::SubstrateWeight<Runtime>;
    }

    // 在construct_runtime!宏中添加pallet
    construct_runtime!(
        pub enum Runtime where
            Block = Block,
            NodeBlock = opaque::Block,
            UncheckedExtrinsic = UncheckedExtrinsic
        {
            System: frame_system,
            // ...其他pallets
            Parameters: pallet_parameters::{Module, Call, Storage, Event<T>},
            ExamplePallet: pallet_example,
        }
    );
}

// pallets/example/src/lib.rs
#[frame_support::pallet]
pub mod pallet_example {
    use frame_support::{pallet_prelude::*, traits::Get};
    use frame_system::pallet_prelude::*;
    
    #[pallet::config]
    pub trait Config: frame_system::Config + pallet_parameters::Config {
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
    }
    
    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet<T>(_);
    
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// 参数已更新
        ParameterUpdated { key: Vec<u8>, value: Vec<u8> },
        /// 参数已读取
        ParameterRead { key: Vec<u8>, value: Vec<u8> },
    }
    
    #[pallet::error]
    pub enum Error<T> {
        /// 参数不存在
        ParameterNotFound,
        /// 参数类型不匹配
        ParameterTypeMismatch,
    }
    
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        /// 更新链上参数
        #[pallet::weight(10_000)]
        pub fn update_parameter(
            origin: OriginFor<T>,
            key: Vec<u8>,  // 参数键
            value: Vec<u8>, // 参数值
        ) -> DispatchResult {
            // 验证调用者权限
            ensure_root(origin)?;
            
            // 存储参数到pallet-parameters
            pallet_parameters::Pallet::<T>::set_parameter(&key, &value)?;
            
            // 发出事件
            Self::deposit_event(Event::ParameterUpdated {
                key: key.clone(),
                value: value.clone(),
            });
            
            Ok(())
        }
        
        /// 获取链上参数
        #[pallet::weight(10_000)]
        pub fn get_parameter(
            origin: OriginFor<T>,
            key: Vec<u8>, // 参数键
        ) -> DispatchResultWithPostInfo {
            // 验证调用者权限
            let _ = ensure_signed(origin)?;
            
            // 从pallet-parameters获取参数
            let value = pallet_parameters::Pallet::<T>::get_parameter(&key)
                .map_err(|_| Error::<T>::ParameterNotFound)?;
            
            // 发出事件
            Self::deposit_event(Event::ParameterRead {
                key: key.clone(),
                value: value.clone(),
            });
            
            log::info!("Parameter {}: {:?}", String::from_utf8_lossy(&key), value);
            Ok(().into())
        }
        
        /// 获取并解析特定类型的参数
        #[pallet::weight(10_000)]
        pub fn get_typed_parameter(
            origin: OriginFor<T>,
            key: Vec<u8>, // 参数键
        ) -> DispatchResultWithPostInfo {
            // 验证调用者权限
            let _ = ensure_signed(origin)?;
            
            // 尝试获取u32类型的参数
            let value: u32 = pallet_parameters::Pallet::<T>::get_parameter(&key)
                .map_err(|_| Error::<T>::ParameterNotFound)?;
            
            log::info!("Typed parameter {}: {}", String::from_utf8_lossy(&key), value);
            Ok(().into())
        }
    }
}

关键特性

  1. 动态参数管理

    • 使用set_parameterget_parameter方法动态管理链上参数
    • 参数可以随时更新,无需硬编码或重新部署
  2. 存储优化

    • pallet-parameters提供了高效的存储结构
    • 参数以键值对形式存储,优化存储空间使用
  3. 类型安全

    • 使用Rust的类型系统确保参数类型安全
    • 自动序列化/反序列化
  4. 权限控制

    • 可以通过Origin控制谁可以修改参数
    • 集成Substrate的权限管理系统

最佳实践

  1. 为参数使用有意义的键名
  2. 考虑参数的大小和存储成本
  3. 实现适当的权限控制
  4. 记录参数变更事件
  5. 考虑参数的版本控制

通过pallet-parameters,开发者可以轻松实现链上可配置参数,提高区块链应用的灵活性和可维护性。


1 回复

Rust Substrate框架pallet-parameters的使用:实现链上可配置参数的动态管理与存储优化

介绍

pallet-parameters是Substrate框架中的一个模块,专门用于实现区块链上的参数动态管理和存储优化。它允许链上参数在运行时(runtime)进行配置和更新,而无需硬分叉或重新编译整个区块链。

这个pallet特别适合需要频繁调整参数的应用场景,如治理系统、DeFi协议参数调整或网络费用变更等。

主要功能

  1. 提供链上参数的声明式定义
  2. 支持参数的动态更新
  3. 优化参数的存储方式
  4. 提供参数变更的事件通知
  5. 支持参数访问权限控制

使用方法

1. 在runtime中集成pallet-parameters

首先需要在runtime的Cargo.toml中添加依赖:

[dependencies]
pallet-parameters = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false }

然后在runtime的lib.rs中配置和实现:

impl pallet_parameters::Config for Runtime {
    type Event = Event;
    type WeightInfo = ();
}

construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // ... 其他pallet
        Parameters: pallet_parameters::{Module, Call, Storage, Event<T>},
    }
);

2. 定义可配置参数

在runtime中定义需要动态管理的参数:

#[frame_support::pallet]
pub mod pallet {
    use frame_support::pallet_prelude::*;
    use frame system::pallet_prelude::*;
    
    #[pallet::config]
    pub trait Config: frame_system::Config {
        // ... 其他配置
        
        #[pallet::constant]
        type MaxVotes: Get<u32>;
    }
    
    #[pallet::storage]
    #[pallet::getter(fn parameters)]
    pub type Parameters<T: Config> = StorageMap<
        _,
        Blake2_128Concat,
        Vec<u8>,
        Vec<u8>,
        ValueQuery
    >;
}

3. 设置和获取参数

设置参数

use frame_support::{dispatch::DispatchResult, storage::StorageMap};

pub fn set_parameter(key: Vec<u8>, value: Vec<u8>) -> DispatchResult {
    Parameters::<T>::insert(key, value);
    Ok(())
}

获取参数

pub fn get_parameter(key: Vec<u8>) -> Option<Vec<u8>> {
    Parameters::<T>::get(key)
}

4. 完整示例

下面是一个完整的pallet示例,展示如何定义和使用可配置参数:

#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;
    
    #[pallet::config]
    pub trait Config: frame_system::Config {
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
        
        /// 谁可以设置参数
        type AdminOrigin: EnsureOrigin<Self::Origin>;
    }
    
    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet<T>(_);
    
    #[pallet::storage]
    #[pallet::getter(fn parameters)]
    pub type Parameters<T: Config] = StorageMap<
        _,
        Blake2_128Concat,
        Vec<u8>,  // 参数名
        Vec<u8>,  // 参数值
        ValueQuery
    >;
    
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// 参数已更新
        ParameterUpdated { key: Vec<u8>, value: Vec<u8> },
    }
    
    #[pallet::error]
    pub enum Error<T> {
        /// 参数不存在
        ParameterNotFound,
    }
    
    #[pallet::call]
    impl<T: Config] Pallet<T> {
        /// 设置参数
        #[pallet::weight(10_000)]
        pub fn set_parameter(
            origin: OriginFor<T>,
            key: Vec<u8>,
            value: Vec<u8>,
        ) -> DispatchResultWithPostInfo {
            T::AdminOrigin::ensure_origin(origin)?;
            
            Parameters::<T>::insert(&key, &value);
            Self::deposit_event(Event::ParameterUpdated { key, value });
            
            Ok(().into())
        }
        
        /// 获取参数
        #[pallet::weight(10_000)]
        pub fn get_parameter(
            _origin: OriginFor<T>,
            key: Vec<u8>,
        ) -> DispatchResultWithPostInfo {
            let value = Parameters::<T>::get(&key)
                .ok_or(Error::<T>::ParameterNotFound)?;
                
            Ok(().into())
        }
    }
}

5. 使用宏简化参数定义

Substrate提供了parameter_types!宏来简化参数定义:

parameter_types! {
    pub const MaxVotes: u32 = 100;
    pub const VotingPeriod: u32 = 7 * 24 * 60 * 60;  // 7天(秒)
    pub const MinimumDeposit: Balance = 100 * DOLLARS;
}

6. 存储优化技巧

为了优化存储,可以考虑以下方法:

  1. 使用紧凑编码格式
  2. 合并相关参数
  3. 使用默认值减少存储
#[pallet::storage]
#[pallet::getter(fn config)]
pub type Config<T> = StorageValue<_, CompactConfig, ValueQuery>;

#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct CompactConfig {
    pub max_votes: u32,
    pub voting_period: u32,
    pub min_deposit: Balance,
}

impl Default for CompactConfig {
    fn default() -> Self {
        CompactConfig {
            max_votes: 100,
            voting_period: 7 * 24 * 60 * 60,
            min_deposit: 100 * DOLLARS,
        }
    }
}

最佳实践

  1. 参数分组:将相关参数组合在一起,减少存储访问次数
  2. 访问控制:严格限制参数修改权限
  3. 变更通知:确保参数变更时发出事件
  4. 版本控制:考虑为参数结构添加版本号
  5. 文档化:为每个参数添加详细文档说明

完整示例demo

以下是一个更完整的示例,展示如何在实际项目中使用pallet-parameters:

#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
    dispatch::DispatchResult,
    pallet_prelude::*,
    traits::EnsureOrigin,
    weights::Weight,
};
use frame_system::pallet_prelude::*;

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    
    /// 参数模块配置trait
    #[pallet::config]
    pub trait Config: frame_system::Config {
        /// 事件类型
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
        
        /// 管理员权限
        type AdminOrigin: EnsureOrigin<Self::Origin>;
        
        /// 权重信息
        type WeightInfo: WeightInfo;
    }
    
    /// 权重信息接口
    pub trait WeightInfo {
        fn set_parameter() -> Weight;
        fn get_parameter() -> Weight;
    }
    
    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet<T>(_);
    
    /// 参数存储
    #[pallet::storage]
    #[pallet::getter(fn parameters)]
    pub type Parameters<T: Config> = StorageMap<
        _,
        Blake2_128Concat,
        Vec<u8>,  // 参数键
        Vec<u8>,  // 参数值
        ValueQuery
    >;
    
    /// 参数模块事件
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// 参数已更新事件
        ParameterUpdated { key: Vec<u8>, value: Vec<u8> },
    }
    
    /// 错误类型
    #[pallet::error]
    pub enum Error<T> {
        /// 参数不存在
        ParameterNotFound,
        /// 参数格式无效
        InvalidParameterFormat,
    }
    
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        /// 设置参数
        #[pallet::weight(T::WeightInfo::set_parameter())]
        pub fn set_parameter(
            origin: OriginFor<T>,
            key: Vec<u8>,
            value: Vec<u8>,
        ) -> DispatchResultWithPostInfo {
            // 验证调用者权限
            T::AdminOrigin::ensure_origin(origin)?;
            
            // 验证参数格式
            if key.is_empty() || value.is_empty() {
                return Err(Error::<T>::InvalidParameterFormat.into());
            }
            
            // 存储参数
            Parameters::<T>::insert(&key, &value);
            
            // 发出事件
            Self::deposit_event(Event::ParameterUpdated { key, value });
            
            Ok(().into())
        }
        
        /// 获取参数
        #[pallet::weight(T::WeightInfo::get_parameter())]
        pub fn get_parameter(
            _origin: OriginFor<T>,
            key: Vec<u8>,
        ) -> DispatchResultWithPostInfo {
            // 获取参数
            let _value = Parameters::<T>::get(&key)
                .ok_or(Error::<T>::ParameterNotFound)?;
            
            Ok(().into())
        }
    }
    
    /// 实现默认权重
    impl<T: Config> WeightInfo for Pallet<T> {
        fn set_parameter() -> Weight {
            Weight::from_ref_time(10_000)
        }
        
        fn get_parameter() -> Weight {
            Weight::from_ref_time(5_000)
        }
    }
}

/// 测试模块
#[cfg(test)]
mod tests {
    use super::*;
    use frame_support::{
        assert_ok, assert_err,
        traits::{ConstU32, ConstU64},
    };
    use sp_core::H256;
    use sp_runtime::{
        traits::{BlakeTwo256, IdentityLookup},
        BuildStorage,
    };
    
    type Block = frame_system::mocking::MockBlock<Test>;
    
    frame_support::construct_runtime!(
        pub enum Test where
            Block = Block,
            NodeBlock = Block,
            UncheckedExtrinsic = (),
        {
            System: frame_system,
            Parameters: pallet,
        }
    );
    
    impl frame_system::Config for Test {
        type BaseCallFilter = frame_support::traits::Everything;
        type BlockWeights = ();
        type BlockLength = ();
        type DbWeight = ();
        type RuntimeOrigin = RuntimeOrigin;
        type RuntimeCall = RuntimeCall;
        type Index = u64;
        type BlockNumber = u64;
        type Hash = H256;
        type Hashing = BlakeTwo256;
        type AccountId = u64;
        type Lookup = IdentityLookup<Self::AccountId>;
        type Header = sp_runtime::testing::Header;
        type RuntimeEvent = RuntimeEvent;
        type BlockHashCount = ConstU64<250>;
        type Version = ();
        type PalletInfo = PalletInfo;
        type AccountData = ();
        type OnNewAccount = ();
        type OnKilledAccount = ();
        type SystemWeightInfo = ();
        type SS58Prefix = ();
        type OnSetCode = ();
        type MaxConsumers = ConstU32<16>;
    }
    
    impl Config for Test {
        type Event = RuntimeEvent;
        type AdminOrigin = frame_system::EnsureRoot<u64>;
        type WeightInfo = ();
    }
    
    fn new_test_ext() -> sp_io::TestExternalities {
        let t = frame_system::GenesisConfig::<Test>::default()
            .build_storage()
            .unwrap();
        t.into()
    }
    
    #[test]
    fn test_set_and_get_parameter() {
        new_test_ext().execute_with(|| {
            let key = b"test_key".to_vec();
            let value = b"test_value".to_vec();
            
            // 设置参数
            assert_ok!(Parameters::set_parameter(
                RuntimeOrigin::root(),
                key.clone(),
                value.clone()
            ));
            
            // 获取参数
            assert_eq!(Parameters::parameters(key), value);
        });
    }
}

总结

pallet-parameters为Substrate区块链提供了灵活的参数管理系统,使链上配置可以在运行时动态调整,大大提高了区块链的适应性和可维护性。通过合理的设计和优化,可以在保持灵活性的同时最小化存储开销。

回到顶部