Substrate如何在transaction-payment pallet中获取用户调用参数自定义手续费
基于用户调用参数自定义Substrate交易手续费的实现方法
完全可以实现你要的需求,不需要依赖默认的交易长度、权重规则计算手续费。
实现原理
Substrate的transaction-payment pallet的手续费计算逻辑是完全可插拔的,没有硬编码在pallet内部。而且交易在进入函数执行阶段之前,节点已经可以拿到完整的交易Call结构体、签名信息等所有用户传入的参数,你可以通过自定义扩展的方式获取这些参数,覆盖默认的手续费计算逻辑。
具体实现方案
最常用的方式是自定义SignedExtension扩展:
SignedExtension是Substrate内置的交易预检查机制,运行时机早于交易函数执行,天生可以访问交易的全部入参。- 你只需要在自定义
SignedExtension的validate方法中匹配当前交易的调用类型,提取你需要的手续费相关参数,直接用这个参数作为最终手续费即可。
示例代码参考:
// 导入必要依赖 use frame_support::dispatch::{DispatchInfo, Dispatchable}; use sp_runtime::{ traits::{SignedExtension, TransactionValidity, TransactionValidityError}, transaction_validity::ValidTransaction, Encode, Decode, TypeInfo, }; use frame_system::Config; // 自定义手续费计算扩展 #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CustomFeeExtension<T>(sp_std::marker::PhantomData<T>); impl<T: Config + pallet_transaction_payment::Config> SignedExtension for CustomFeeExtension<T> where T::Call: Dispatchable<Info = DispatchInfo>, { const IDENTIFIER: &'static str = "CustomFeeExtension"; type AccountId = T::AccountId; type Call = T::Call; type AdditionalSigned = (); type Pre = (); fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) } fn validate( &self, _who: &Self::AccountId, call: &Self::Call, _info: &DispatchInfoOf<Self::Call>, _len: usize, ) -> TransactionValidity { // 匹配你的业务Pallet的对应调用,提取用户传入的手续费参数 if let Call::YourPallet(pallet_call) = call { match pallet_call { // 这里以transfer调用为例,假设它有一个custom_fee参数是用户指定的手续费 YourPalletCall::transfer { custom_fee, .. } | YourPalletCall::other_need_custom_fee_call { custom_fee, .. } => { // 直接将用户传入的custom_fee设置为本次交易的手续费 pallet_transaction_payment::NextFee::<T>::put(custom_fee); } } } Ok(ValidTransaction::default()) } }
完成扩展开发后只需要做两个配置即可生效:
- 把自定义的
CustomFeeExtension添加到Runtime的SignedExtra列表中,确保它会在交易验证阶段运行。 - 替换
pallet_transaction_payment的FeeCalculator关联类型,自定义计算逻辑直接读取你刚才写入的NextFee值,完全舍弃默认的长度、权重计算规则即可。
注意点
- 你用于计算手续费的参数会自动被纳入交易签名的覆盖范围,不用担心用户篡改参数少交手续费。
- 如果只有部分调用需要自定义手续费,只需要在
validate方法中匹配对应的调用类型即可,其余调用可以继续走默认的手续费计算逻辑。
内容的提问来源于stack exchange,提问作者Makam Aravind
相关产品推荐
相关产品推荐

