Rust区块链工具库pallet-utility的使用,Substrate框架多功能批处理与调度模块解析
Utility Module
一个无状态的模块,提供用于调度管理的辅助功能,无需重新认证。
概述
这个模块包含两个基本功能:
-
批量调度:无状态操作,允许任何来源在单次调度中执行多个调用。这可用于合并提案,将
set_code
与相应的set_storage
结合起来,或用于通过单个签名验证进行高效的多重支付。 -
伪名调度:无状态操作,允许签名来源从替代签名来源执行调用。每个账户有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));
});
}
这个完整示例展示了:
- 如何配置Utility模块作为一个pallet集成到运行时中
- 如何设置测试环境
- 如何在实际运行环境中使用批量调度和伪名调度功能
- 包含完整的类型定义和配置实现
测试用例验证了Utility模块的核心功能,包括批量执行多个调用和通过派生账户执行调用。
Rust区块链工具库pallet-utility使用指南
概述
pallet-utility
是Substrate框架中的一个多功能批处理与调度模块,它允许用户将多个调用批量处理为一个调用,或者安排调用在未来的某个区块执行。这个pallet在区块链开发中非常有用,特别是当需要优化交易费用或安排未来执行某些操作时。
主要功能
- 批量处理(batch):将多个调用组合成一个原子操作
- 批量处理带权重(batch_all):类似batch但确保全部成功或全部失败
- 强制批量处理(force_batch):即使某些调用失败也继续执行
- 调度(as_derivative):从派生账户发起调用
- 延迟执行(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
会根据批量中包含的调用数量和执行复杂度计算权重。批量调用的总权重通常小于单独执行每个调用的权重总和,因为批量处理节省了验证和调度开销。
最佳实践
- 批量大小:合理控制批量中的调用数量,避免单个区块处理时间过长
- 错误处理:
- 使用
batch_all
确保原子性(全部成功或全部失败) - 使用
force_batch
允许部分成功
- 使用
- 调度:合理安排延迟执行的区块数,考虑网络拥堵情况
- 派生账户:可用于多账户管理,减少主账户的使用频率
注意事项
- 批量调用的总费用通常比单独发送每个调用要低
- 批量中的所有调用共享相同的原始发送者
- 调度调用需要考虑区块链的最终性
- 派生账户的索引需要在合理范围内
pallet-utility
是Substrate开发中非常实用的工具,合理使用可以显著提高链上操作的效率和灵活性。