Rust区块链工具库pallet-utility的使用,Substrate框架多功能批处理与调度模块解析

Utility Module

一个无状态的模块,提供用于调度管理的辅助功能,无需重新认证。

概述

这个模块包含两个基本功能:

  1. 批量调度:无状态操作,允许任何来源在单次调度中执行多个调用。这可用于合并提案,将set_code与相应的set_storage结合起来,或用于通过单个签名验证进行高效的多重支付。

  2. 伪名调度:无状态操作,允许签名来源从替代签名来源执行调用。每个账户有2 * 2^16个可能的"伪名"(替代账户ID),这些可以堆叠。作为密钥管理工具非常有用,当您需要多个不同的账户(例如作为许多质押账户的控制器),但由相同的底层密钥对控制。

由于代理过滤器在这个模块的所有调度中都受到尊重,因此它永远不需要被任何代理过滤。

接口

可调度函数

批量调度

  • batch - 从发送者的来源调度多个调用

伪名调度

  • as_derivative - 从派生签名来源调度一个调用

许可证:Apache-2.0

完整示例代码

// 导入必要的模块
use frame_support::{dispatch::DispatchResult, traits::Currency};
use frame_system::Config as SystemConfig;
use pallet_utility::{self as utility, Call as UtilityCall};
use sp_runtime::traits::StaticLookup;
use sp_std::prelude::*;

// 定义我们的运行时配置
pub trait Config: utility::Config + SystemConfig {}

// 实现批量调用的示例
pub fn batch_calls<T: Config>(
    origin: T::Origin,
    calls: Vec<<T as SystemConfig>::Call>,
) -> DispatchResult {
    // 创建Utility模块的batch调用
    UtilityCall::<T>::batch(calls).dispatch_bypass_filter(origin)
}

// 实现伪名调用的示例
pub fn as_derivative_call<T: Config>(
    origin: T::Origin,
    index: u16,
    call: Box<<T as SystemConfig>::Call>,
) -> DispatchResult {
    // 创建Utility模块的as_derivative调用
    UtilityCall::<T>::as_derivative(index, call).dispatch_bypass_filter(origin)
}

// 示例测试用例
#[cfg(test)]
mod tests {
    use super::*;
    use crate::mock::{new_test_ext, Runtime, System};
    use frame_support::{assert_ok, dispatch::DispatchError};
    use pallet_balances::Call as BalancesCall;
    
    #[test]
    fn test_batch_calls() {
        new_test_ext().execute_with(|| {
            let alice = 1u64;
            let bob = 2u64;
            System::set_block_number(1);
            
            // 创建批量调用
            let calls = vec![
                RuntimeCall::Balances(BalancesCall::transfer {
                    dest: bob,
                    value: 10,
                }),
                RuntimeCall::Balances(BalancesCall::transfer {
                    dest: alice,
                    value: 20,
                }),
            ];
            
            // 执行批量调用
            assert_ok!(batch_calls::<Runtime>(Origin::signed(alice), calls));
        });
    }
    
    #[test]
    fn test_as_derivative_call() {
        new_test_ext().execute_with(|| {
            let alice = 1u64;
            let bob = 2u64;
            System::set_block_number(1);
            
            // 创建派生调用
            let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer {
                dest: bob,
                value: 10,
            }));
            
            // 执行派生调用
            assert_ok!(as_derivative_call::<Runtime>(Origin::signed(alice), 0, call));
        });
    }
}

这个示例展示了如何使用pallet-utility模块的批量调度和伪名调度功能。批量调度允许在一个交易中执行多个调用,而伪名调度允许从派生账户执行调用。代码包含了基本的实现和测试用例,展示了如何使用这些功能。

扩展完整示例

下面是一个更完整的示例,展示如何在Substrate运行时中使用Utility模块:

//! 演示如何使用Utility模块的完整示例

use frame_support::{
    construct_runtime, parameter_types,
    traits::{Currency, Everything},
    weights::Weight,
};
use frame_system::EnsureRoot;
use sp_core::H256;
use sp_runtime::{
    testing::Header,
    traits::{BlakeTwo256, IdentityLookup},
    Perbill,
};

// 定义类型别名
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
type Block = frame_system::mocking::MockBlock<Runtime>;

// 构建运行时结构
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
        Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
        Utility: pallet_utility::{Pallet, Call, Event},
    }
);

// 参数类型配置
parameter_types! {
    pub const BlockHashCount: u64 = 250;
    pub const MaximumBlockWeight: Weight = 1024;
    pub const MaximumBlockLength: u32 = 2 * 1024;
    pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
}

// 实现系统配置
impl frame_system::Config for Runtime {
    type BaseCallFilter = Everything;
    type BlockWeights = ();
    type BlockLength = ();
    type DbWeight = ();
    type Origin = Origin;
    type Call = Call;
    type Index = u64;
    type BlockNumber = u64;
    type Hash = H256;
    type Hashing = BlakeTwo256;
    type AccountId = u64;
    type Lookup = IdentityLookup<Self::AccountId>;
    type Header = Header;
    type Event = Event;
    type BlockHashCount = BlockHashCount;
    type Version = ();
    type PalletInfo = PalletInfo;
    type AccountData = pallet_balances::AccountData<u64>;
    type OnNewAccount = ();
    type OnKilledAccount = ();
    type SystemWeightInfo = ();
    type SS58Prefix = ();
    type OnSetCode = ();
}

// 实现余额配置
parameter_types! {
    pub const ExistentialDeposit: u64 = 1;
}

impl pallet_balances::Config for Runtime {
    type Balance = u64;
    type DustRemoval = ();
    type Event = Event;
    type ExistentialDeposit = ExistentialDeposit;
    type AccountStore = System;
    type WeightInfo = ();
    type MaxLocks = ();
    type MaxReserves = ();
    type ReserveIdentifier = [u8; 8];
}

// 实现Utility模块配置
impl pallet_utility::Config for Runtime {
    type Event = Event;
    type Call = Call;
    type WeightInfo = ();
}

// 测试环境设置
pub fn new_test_ext() -> sp_io::TestExternalities {
    let mut t = frame_system::GenesisConfig::default()
        .build_storage::<Runtime>()
        .unwrap();
    pallet_balances::GenesisConfig::<Runtime> {
        balances: vec![(1, 100), (2, 100)],
    }
    .assimilate_storage(&mut t)
    .unwrap();
    t.into()
}

// 测试Utility模块功能
#[test]
fn test_utility_module() {
    new_test_ext().execute_with(|| {
        // 测试批量调用
        let calls = vec![
            Call::Balances(pallet_balances::Call::transfer {
                dest: 2,
                value: 10,
            }),
            Call::Balances(pallet_balances::Call::transfer {
                dest: 1,
                value: 20,
            }),
        ];
        assert_ok!(Utility::batch(Origin::signed(1), calls));
        
        // 测试伪名调用
        let call = Box::new(Call::Balances(pallet_balances::Call::transfer {
            dest: 2,
            value: 5,
        }));
        assert_ok!(Utility::as_derivative(Origin::signed(1), 0, call));
    });
}

这个完整示例展示了:

  1. 如何配置Utility模块作为一个pallet集成到运行时中
  2. 如何设置测试环境
  3. 如何在实际运行环境中使用批量调度和伪名调度功能
  4. 包含完整的类型定义和配置实现

测试用例验证了Utility模块的核心功能,包括批量执行多个调用和通过派生账户执行调用。


1 回复

Rust区块链工具库pallet-utility使用指南

概述

pallet-utility是Substrate框架中的一个多功能批处理与调度模块,它允许用户将多个调用批量处理为一个调用,或者安排调用在未来的某个区块执行。这个pallet在区块链开发中非常有用,特别是当需要优化交易费用或安排未来执行某些操作时。

主要功能

  1. 批量处理(batch):将多个调用组合成一个原子操作
  2. 批量处理带权重(batch_all):类似batch但确保全部成功或全部失败
  3. 强制批量处理(force_batch):即使某些调用失败也继续执行
  4. 调度(as_derivative):从派生账户发起调用
  5. 延迟执行(schedule):安排调用在未来执行

使用方法

基本批量调用

use frame_support::dispatch::DispatchResult;

// 在你的runtime配置中引入pallet_utility
impl pallet_utility::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type RuntimeCall = RuntimeCall;
    type WeightInfo = pallet_utility::weights::SubstrateWeight<Runtime>;
}

// 批量调用示例
pub fn batch_calls(
    origin: OriginFor<T>,
    calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResult {
    pallet_utility::Pallet::<T>::batch(origin, calls)
}

实际使用示例

// 假设我们有以下三个调用需要批量处理
let call1 = Call::Balances(pallet_balances::Call::transfer {
    dest: recipient1,
    value: 100,
});
let call2 = Call::System(frame_system::Call::remark {
    remark: "Batch call example".as_bytes().to_vec(),
});
let call3 = Call::Balances(pallet_balances::Call::transfer {
    dest: recipient2,
    value: 200,
});

// 创建批量调用
let batch_call = Call::Utility(pallet_utility::Call::batch {
    calls: vec![call1, call2, call3],
});

// 提交交易
let _ = RuntimeOrigin::signed(sender).dispatch(batch_call);

延迟调度调用

// 安排在未来第100个区块执行
let schedule_call = Call::Utility(pallet_utility::Call::schedule {
    when: 100,
    maybe_periodic: None, // 可以设置周期性执行
    priority: 0,
    call: Box::new(Call::System(frame_system::Call::remark {
        remark: "Scheduled call".as_bytes().to_vec(),
    })),
});

let _ = RuntimeOrigin::signed(sender).dispatch(schedule_call);

从派生账户调用

// 从派生账户(index=0)发起调用
let derivative_call = Call::Utility(pallet_utility::Call::as_derivative {
    index: 0,
    call: Box::new(Call::System(frame_system::Call::remark {
        remark: "Derivative call".as_bytes().to_vec(),
    })),
});

let _ = RuntimeOrigin::signed(sender).dispatch(derivative_call);

完整示例demo

//! 完整演示pallet-utility的所有主要功能

use frame_support::{dispatch::DispatchResult, traits::OriginTrait};
use frame_system::Config as SystemConfig;
use sp_runtime::traits::StaticLookup;

// 1. 批量调用演示
fn demo_batch<T: pallet_utility::Config + pallet_balances::Config + SystemConfig>(
    origin: T::RuntimeOrigin,
    recipient1: <T::Lookup as StaticLookup>::Source,
    recipient2: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
    // 创建三个不同类型的调用
    let call1 = pallet_balances::Call::<T>::transfer {
        dest: recipient1,
        value: 100.into(),
    };
    let call2 = frame_system::Call::<T>::remark {
        remark: "Batch call demo".as_bytes().to_vec(),
    };
    let call3 = pallet_balances::Call::<T>::transfer {
        dest: recipient2,
        value: 200.into(),
    };

    // 使用batch_all确保原子性
    pallet_utility::Pallet::<T>::batch_all(
        origin,
        vec![
            call1.into(),
            call2.into(),
            call3.into(),
        ],
    )
}

// 2. 延迟调度调用演示
fn demo_schedule<T: pallet_utility::Config + SystemConfig>(
    origin: T::RuntimeOrigin,
    when: T::BlockNumber,
) -> DispatchResult {
    // 创建要延迟执行的调用
    let remark_call = frame_system::Call::<T>::remark {
        remark: "Delayed execution".as_bytes().to_vec(),
    };

    // 安排在未来某个区块执行
    pallet_utility::Pallet::<T>::schedule(
        origin,
        when,
        None, // 非周期性执行
        0,    // 默认优先级
        Box::new(remark_call.into()),
    )
}

// 3. 派生账户调用演示
fn demo_derivative<T: pallet_utility::Config + SystemConfig>(
    origin: T::RuntimeOrigin,
) -> DispatchResult {
    // 创建要通过派生账户执行的调用
    let remark_call = frame_system::Call::<T>::remark {
        remark: "From derivative account".as_bytes().to_vec(),
    };

    // 从索引为0的派生账户执行
    pallet_utility::Pallet::<T>::as_derivative(
        origin,
        0, // 派生账户索引
        Box::new(remark_call.into()),
    )
}

// 4. 强制批量调用演示
fn demo_force_batch<T: pallet_utility::Config + pallet_balances::Config + SystemConfig>(
    origin: T::RuntimeOrigin,
    recipient1: <T::Lookup as StaticLookup>::Source,
    recipient2: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
    // 创建可能失败的调用组合
    let call1 = pallet_balances::Call::<T>::transfer {
        dest: recipient1,
        value: 100.into(),
    };
    let call2 = frame_system::Call::<T>::remark {
        remark: "Force batch demo".as_bytes().to_vec(),
    };
    // 可能包含无效调用

    // 使用force_batch即使有失败也会继续执行
    pallet_utility::Pallet::<T>::force_batch(
        origin,
        vec![
            call1.into(),
            call2.into(),
        ],
    )
}

// 在runtime中的配置示例
impl pallet_utility::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type RuntimeCall = RuntimeCall;
    type WeightInfo = pallet_utility::weights::SubstrateWeight<Runtime>;
    type PalletsOrigin = OriginCaller;
}

权重和费用

pallet-utility会根据批量中包含的调用数量和执行复杂度计算权重。批量调用的总权重通常小于单独执行每个调用的权重总和,因为批量处理节省了验证和调度开销。

最佳实践

  1. 批量大小:合理控制批量中的调用数量,避免单个区块处理时间过长
  2. 错误处理
    • 使用batch_all确保原子性(全部成功或全部失败)
    • 使用force_batch允许部分成功
  3. 调度:合理安排延迟执行的区块数,考虑网络拥堵情况
  4. 派生账户:可用于多账户管理,减少主账户的使用频率

注意事项

  1. 批量调用的总费用通常比单独发送每个调用要低
  2. 批量中的所有调用共享相同的原始发送者
  3. 调度调用需要考虑区块链的最终性
  4. 派生账户的索引需要在合理范围内

pallet-utility是Substrate开发中非常实用的工具,合理使用可以显著提高链上操作的效率和灵活性。

回到顶部