news 2026/9/26 2:00:23

Substrate Glutton Pallet 深度解析:用 `on_idle` 精准消耗区块权重,把链压榨到极限

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Substrate Glutton Pallet 深度解析:用 `on_idle` 精准消耗区块权重,把链压榨到极限
  • 区块链
  • 开发框架
  • 后端

【免费下载链接】substrate

Substrate: The platform for blockchain innovators

项目地址:https://gitcode.com/gh_mirrors/su/substrate
点击查看免费下载

导读

pallet-glutton(Glutton Pallet)是 Substrate 生态中一个专为测试场景设计的有趣 FRAME 模块:它的名字源于其"贪婪吞噬资源"的特性——可以在每个区块的on_idle阶段,按比例消耗链上剩余的计算权重(ref_time)与存储证明权重(proof_size),从而在实战环境中把平行链(parachain)及其中继链(relay chain)推至理论极限。阅读本文后,你将掌握 Glutton 的初始化、set_compute/set_storage两个核心控制接口、on_idle的权重吞噬原理、Genesis 配置方式,以及如何结合源码与测试用例进行实操验证。注意:该 Pallet 仅用于测试,严禁部署在任何承载真实价值的链上。


一、Glutton 是什么:为什么需要"资源吞金兽"

在区块链开发中,有一个非常现实的问题:如何在实际运行环境中验证一条链的理论吞吐上限?普通单元测试往往无法模拟真实网络负载、区块生产节奏与资源争抢。Substrate 在 frame/glutton 中提供的pallet-glutton正是为此而生。

从其 README 的第一行就能看到官方明确的警告:

DO NOT USE ON VALUE-BEARING CHAINS. THIS PALLET IS ONLY INTENDED FOR TESTING USAGE.

也就是说,这个 Pallet 唯一的目的就是把平行链与中继链压到极限,用实践检验理论极限(theoretical limits)。它的实现思路非常精巧:

  • 它本身不执行任何有业务意义的逻辑,只负责"浪费"区块权重;
  • 它通过 FRAME 的on_idlehook 工作,在区块执行完正常交易后,按设定的比例吞噬"剩余可用权重";
  • 消耗的对象有两个维度:ref_time(计算时间)与proof_size(存储证明大小),对应 Substrate 2D 权重模型中的两个分量。

从 源码模块文档 中可以确认其定位:Pallet that consumes ref_time and proof_size of a block. Based on the Compute and Storage parameters the pallet consumes the adequate amount of weight.


二、核心机制:on_idle与权重吞噬原理

2.1 FRAME 的on_idlehook

on_idle是 FRAME 提供给每个 Pallet 的区块生命周期 hook 之一。在 frame/support/src/traits/hooks.rs 中定义如下:

pub trait OnIdle<BlockNumber> { /// See [`Hooks::on_idle`]. fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { Weight::zero() } }

它的语义是:区块在执行完所有交易(extrinsics)之后、还有剩余权重(remaining_weight)时被调用,返回值表示该 hook 实际消耗了多少权重。多个 Pallet 的on_idle会按照元组顺序被依次调用,后一个 Pallet 只能看到前一个消耗后剩下的权重(见 hooks.rs 中的元组实现)。Glutton 正是利用了这一机制——它专门"吃掉"别人用不完的权重。

2.2 Glutton 的on_idle实现

Glutton 在 frame/glutton/src/lib.rs#L177-L210 中实现了on_idle:

fn on_idle(_: BlockNumberFor<T>, remaining_weight: Weight) -> Weight { let mut meter = WeightMeter::from_limit(remaining_weight); if meter.try_consume(T::WeightInfo::empty_on_idle()).is_err() { return T::WeightInfo::empty_on_idle() } let proof_size_limit = Storage::<T>::get().saturating_mul_int(meter.remaining().proof_size()); let computation_weight_limit = Compute::<T>::get().saturating_mul_int(meter.remaining().ref_time()); let mut meter = WeightMeter::from_limit(Weight::from_parts( computation_weight_limit, proof_size_limit, )); Self::waste_at_most_proof_size(&mut meter); Self::waste_at_most_ref_time(&mut meter); meter.consumed() }

工作流程分为四步:

  1. 扣除基础开销:先用WeightMeter包装剩余权重,尝试消耗empty_on_idle()这一基础成本(用于读取Compute/Storage两个存储项的权重)。如果剩余权重连这一步都不够,就直接返回empty_on_idle(),保证on_idle本身的开销被如实上报。
  2. 按比例计算消耗上限:从存储中读取Compute与Storage两个比例值,分别乘以meter.remaining()的ref_time与proof_size,得到本次允许消耗的"计算权重上限"与"证明大小上限"。这意味着 Glutton 消耗的是基于当前剩余权重的一定比例,而非固定值。
  3. 先浪费 proof size,再浪费 ref time:分别调用waste_at_most_proof_size与waste_at_most_ref_time,尽量逼近(但不超过)各自的上限。
  4. 返回实际消耗:最终返回meter.consumed(),让区块生产者在结算时知道 Glutton 用掉了多少权重。

2.3 两个"浪费"函数:贴近上限的精细控制

证明大小消耗(waste_at_most_proof_size)在 lib.rs#L283-L312:

pub(crate) fn waste_at_most_proof_size(meter: &mut WeightMeter) { let Ok(n) = Self::calculate_proof_size_iters(&meter) else { return }; meter.consume(T::WeightInfo::waste_proof_size_some(n)); (0..n).for_each(|i| { TrashData::<T>::get(i); }); }

它先通过权重曲线waste_proof_size_some(n)估算出"执行多少次读取操作能填满剩余权重",然后实际执行n次TrashData读取。每次读取会访问一个 1 KiB 的存储值,从而产生真实的 PoV(Proof of Validity)证明开销。

计算时间消耗(waste_at_most_ref_time)在 lib.rs#L314-L364:

pub(crate) fn waste_at_most_ref_time(meter: &mut WeightMeter) { let Ok(n) = Self::calculate_ref_time_iters(&meter) else { return }; meter.consume(T::WeightInfo::waste_ref_time_iter(n)); let clobber = Self::waste_ref_time_iter(vec![0u8; 64], n); // By casting it into a vec we can hopefully prevent the compiler from optimizing it debug_assert!(clobber.len() == 64); if clobber.len() == 65 { TrashData::<T>::insert(0, [clobber[0] as u8; VALUE_SIZE]); } }

实际的计算"浪费"由waste_ref_time_iter完成(lib.rs#L332-L345):用 Blake2b-512 哈希器对一段数据反复执行i次哈希更新,最后finalize一次。注释中特别说明了两点设计意图:

  • Blake2 哈希速度非常快,所以单次迭代会做多次hasher.update,以便一次性消耗更多ref_time;
  • 通过把哈希结果放入Vec并加一个"不可能成立"的len() == 65判断分支,尽可能阻止编译器把这段纯计算优化掉——否则就浪费不成权重了。

从 benchmarking.rs#L52-L56 可看到,waste_ref_time_iter的基准测试范围是i in 0..100_000,每次迭代的ref_time开销在 1–10 ms 量级(见 lib.rs#L332-L335 的注释)。


三、三个核心外部接口

Glutton 对外只暴露三个可调用函数,全部仅允许 Root 或AdminOrigin调用,权限由 Config trait 中的AdminOrigin控制:

接口参数作用相关事件
initialize_palletnew_count: u32,witness_count: Option<u32>初始化/调整TrashData垃圾数据量PalletInitialized { reinit }
set_computecompute: FixedU64设置on_idle消耗剩余ref_time的比例ComputationLimitSet { compute }
set_storagestorage: FixedU64设置on_idle消耗剩余proof_size的比例StorageLimitSet { storage }

3.1initialize_pallet:先初始化,才能开吃

在 lib.rs#L212-L248 中,initialize_pallet负责管理TrashData——一张用于"浪费证明大小"的存储 Map。关键点:

pub fn initialize_pallet( origin: OriginFor<T>, new_count: u32, witness_count: Option<u32>, ) -> DispatchResult { T::AdminOrigin::ensure_origin_or_root(origin)?; let current_count = TrashDataCount::<T>::get(); ensure!( current_count == witness_count.unwrap_or_default(), Error::<T>::AlreadyInitialized ); // ... 按 new_count 与 current_count 的大小关系插入或删除条目 Self::deposit_event(Event::PalletInitialized { reinit: witness_count.is_some() }); TrashDataCount::<T>::set(new_count); Ok(()) }
  • new_count:目标条目数量。源码注释给出的推荐默认值是5_000(一个合理的垃圾数据量)。
  • witness_count:当前TrashData的实际条目数(见证值)。首次初始化时传None;如果 Pallet 已经初始化过,必须传Some(current_count)才能绕过AlreadyInitialized错误,实现"重新初始化"(扩容或收缩)。
  • new_count > current_count时插入新条目,否则删除多余条目。

TrashData的每个值大小为VALUE_SIZE = 1024字节(1 KiB),Map 的MaxValues被限制为MAX_TRASH_DATA_ENTRIES = 65_000(见 lib.rs#L49-L54)。为什么是 65k?源码注释解释得很清楚(lib.rs#L120-L135):65k 刚好比16^4 = 65536小一点,避免触发证明大小的基准测试跳变(overestimate);实践中 65k × 1 KiB ≈ 65 MiB 的证明浪费上限已经足够。

3.2set_compute与set_storage:设定吞噬比例

两个函数逻辑完全对称(lib.rs#L250-L280):

pub fn set_compute(origin: OriginFor<T>, compute: FixedU64) -> DispatchResult { T::AdminOrigin::ensure_origin_or_root(origin)?; ensure!(compute <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit); Compute::<T>::set(compute); Self::deposit_event(Event::ComputationLimitSet { compute }); Ok(()) } pub fn set_storage(origin: OriginFor<T>, storage: FixedU64) -> DispatchResult { T::AdminOrigin::ensure_origin_or_root(origin)?; ensure!(storage <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit); Storage::<T>::set(storage); Self::deposit_event(Event::StorageLimitSet { storage }); Ok(()) }

比例值的语义非常直白(lib.rs#L106-L118):

  • 1.0对应 100%,即吞掉全部剩余ref_time(或proof_size);
  • 最大允许值为RESOURCE_HARD_LIMIT = 10(1000%),超过会返回InsaneLimit错误;
  • 超过 1.0 可能导致链停滞(stall the chain)——因为你让on_idle去消耗超过区块剩余权重的资源。

两个存储项Compute与Storage都是StorageValue<_, FixedU64, ValueQuery>,默认值为 0(即默认不消耗任何资源),这也是测试在 tests.rs#L92 中断言Compute::<T>::get()初始为零的原因。


四、Genesis 配置:预置初始状态

如果不希望在部署后手动调用initialize_pallet,可以在 Genesis 中直接配置。Glutton 的 GenesisConfig 包含三个字段:

字段类型说明
computeFixedU64初始计算消耗比例
storageFixedU64初始存储消耗比例
trash_data_countu32初始垃圾数据条目数(用于浪费 proof size)

genesis_build中有三条硬性断言:

  1. trash_data_count <= MAX_TRASH_DATA_ENTRIES,超出直接 panic;
  2. compute <= RESOURCE_HARD_LIMIT,否则报 "Compute limit is insane";
  3. storage <= RESOURCE_HARD_LIMIT,否则报 "Storage limit is insane"。

示例配置(以 JSON chain spec 形式):

{ "glutton": { "compute": "500000000", "storage": "500000000", "trash_data_count": 5000 } }

注意:FixedU64在 JSON 中通常以整数形式表示定点数(1.0 对应其定点表示值),实际数值需按运行时使用的FixedU64编码换算;trash_data_count建议使用文档推荐的5_000作为初始值。


五、Runtime 集成方式

在 Substrate 节点运行时中,Glutton 的集成方式与普通 FRAME Pallet 一致。以bin/node/runtime为例:

1. 依赖声明(bin/node/runtime/Cargo.toml):

pallet-glutton = { version = "4.0.0-dev", default-features = false, path = "../../../frame/glutton" }

并在std、runtime-benchmarks、try-runtime三个 feature 中分别引入对应开关(见 Cargo.toml#L178、#L274、#L346)。

2. Config 实现(bin/node/runtime/src/lib.rs#L443-L447):

impl pallet_glutton::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AdminOrigin = EnsureRoot<AccountId>; type WeightInfo = pallet_glutton::weights::SubstrateWeight<Runtime>; }
  • AdminOrigin使用EnsureRoot,即只有 Root(治理)能调用三个外部接口;
  • WeightInfo使用仓库自动生成的 weights.rs 中的SubstrateWeight。

3. 注册到 Runtime(bin/node/runtime/src/lib.rs#L2050):

Glutton: pallet_glutton,

并在 benchmarking 白名单中加入[pallet_glutton, Glutton](bin/node/runtime/src/lib.rs#L2200),使其可以参与基准测试。


六、基准测试与权重校准

Glutton 的"浪费"行为高度依赖准确的权重曲线,因此它的 benchmark 设计非常讲究。benchmarking.rs 顶部注释特别提醒:

Has to be compiled and run twice to calibrate on new hardware.

也就是说,在新硬件上必须把 benchmark 编译并运行两次来完成校准。它定义了以下基准:

Benchmark变量范围用途
initialize_pallet_grown in 0..1_000初始化/扩容TrashData的权重
initialize_pallet_shrinkn in 0..1_000收缩TrashData的权重
waste_ref_time_iteri in 0..100_000单次哈希迭代消耗的ref_time曲线
waste_proof_size_somei in 0..5_000单次TrashData读取消耗的 proof size 曲线
on_idle_high_proof_waste/on_idle_low_proof_waste—手动验证on_idle在高低两种 proof 负载下的行为
empty_on_idle—on_idle自身的基础开销
set_compute/set_storage—两个设置函数的基础权重

生成的权重文件 frame/glutton/src/weights.rs 是 2023-06-16 在 2.60 GHz Intel Xeon 上、以--steps=50 --repeat=20参数自动生成的,其中记录了完整的 benchmark CLI 命令(weights.rs#L26-L43),可供复现:

./target/production/substrate benchmark pallet \ --chain=dev --steps=50 --repeat=20 \ --pallet=pallet_glutton \ --no-storage-info --no-median-slopes --no-min-squares \ --extrinsic='*' --execution=wasm --wasm-execution=compiled \ --heap-pages=4096 \ --output=./frame/glutton/src/weights.rs \ --header=./HEADER-APACHE2

integrity_test:防死循环的最后防线

Glutton 在 lib.rs#L179-L188 还实现了一个integrity_test:

fn integrity_test() { assert!( !T::WeightInfo::waste_ref_time_iter(1).ref_time().is_zero(), "Weight zero; would get stuck in an infinite loop" ); assert!( !T::WeightInfo::waste_proof_size_some(1).proof_size().is_zero(), "Weight zero; would get stuck in an infinite loop" ); }

它的作用很关键:如果某个平台上这两条权重曲线是零,on_idle里的迭代计算就会陷入死循环(永远填不满 meter)。这个 hook 会在运行时完整性检查阶段(如try-runtime或集成测试)提前拦截此类错误配置。


七、测试用例:行为验证与精度保证

frame/glutton/src/tests.rs 提供了完整的单元测试,可以从四个角度验证 Pallet 行为:

7.1 初始化与扩容/收缩

  • initialize_pallet_works:验证只有 Root 能调用、首次初始化后重复调用(不带见证值)会返回AlreadyInitialized、事件正确发出,且TrashData中确实写入了确定性生成的值(tests.rs#L28-L60)。
  • expand_and_shrink_trash_data_works:验证5000 → 8000 → 6000 → 0的扩容/收缩流程,TrashDataCount与iter_keys().count()始终一致(tests.rs#L62-L87)。

7.2 比例设置与硬上限

  • setting_compute_respects_limit/setting_storage_respects_limit:验证9.99、10.0合法,10.01返回InsaneLimit(tests.rs#L111-L161)。
  • setting_compute_works/setting_storage_works:验证非 Root 签名(signed(1))和无签名(none())调用都会返回BadOrigin(tests.rs#L89-L109)。

7.3on_idle的"接近上限"精度

Glutton 的测试最核心的断言是:实际消耗必须尽量接近目标,且绝不能超支(over-spend)。以on_idle_weight_high_proof_is_close_enough_works为例(tests.rs#L172-L194):

let should = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, WEIGHT_PROOF_SIZE_PER_MB * 5); let got = Glutton::on_idle(1, should); assert!(got.all_lte(should), "Consumed too much weight"); let ratio = Perbill::from_rational(got.proof_size(), should.proof_size()); assert!(ratio >= Perbill::from_percent(99), "Too few proof size consumed, ..."); let ratio = Perbill::from_rational(got.ref_time(), should.ref_time()); assert!(ratio >= Perbill::from_percent(99), "Too few ref time consumed, ...");

在compute = storage = 1.0(100%)的情况下,on_idle消耗的权重必须同时满足:

  • all_lte(should):绝不允许超过剩余权重;
  • 消耗比例 ≥ 99%:也不能浪费太多,保证压测效果接近真实上限。

on_idle_weight_over_unity_is_close_enough_works(tests.rs#L220-L249)进一步验证了比例超过 1.0 的场景:当设置compute = 1.75, storage = 1.5时,on_idle会尝试消耗超过区块限制的权重(consumed.all_gt(max_block)),此时消耗量应介于区块上限与"请求量"之间——这正是测试链是否会被"撑爆"的极端场景。

waste_at_most_ref_time_weight_close_enough与waste_at_most_proof_size_weight_close_enough(tests.rs#L251-L283)则直接对两个内部"浪费"函数做精度校验:在ref_time与proof_size分别接近无限的极端 meter 上,要求consumed_ratio() >= 99%,否则报错Weight calibration failed. Please re-run the benchmarks on the same hardware.——再次印证了"权重曲线必须与硬件匹配"这一前提。

7.4 数据生成器的确定性

gen_value_works(tests.rs#L285-L294)验证gen_value:生成的 1 KiB 值长度正确、不同 seed 值互不相同、非全零、且对相同 seed 具有确定性。这是TrashData数据可复现、可基准化的基础。


八、使用场景与实战建议

8.1 典型压测流程

结合 README 与源码,一个完整的 Glutton 压测流程如下:

  1. 部署:在测试平行链/中继链的 runtime 中注册pallet-glutton(参考第五节),并在 Genesis 中预置trash_data_count(如 5000)。
  2. 初始化:若未配置 Genesis,先以 Root 调用initialize_pallet(5000, None)初始化垃圾数据。
  3. 设置比例:以 Root 调用set_compute/set_storage,例如各设0.5(消耗 50% 剩余权重);逐步调大观察链的负载与出块表现。
  4. 压测:观察区块利用率、出块时间、PoV 大小等指标,逐步逼近 100%(1.0)甚至超过(> 1.0),找到链的真实瓶颈。

8.2 注意事项(红线)

  • 严禁用于有价值链:README 与源码文档首行都给出了WARNING,这条红线没有例外;
  • 比例不要轻易设超过 1.0:源码与注释多次警告Setting this to over 1.0 could stall the chain,只有在你确实想测试"链何时会停滞"时才使用;
  • 换硬件必须重新校准权重:benchmarking.rs明确要求在新硬件上编译运行两次 benchmark,否则on_idle的实际消耗可能与预期偏差超过 1%;
  • TrashData上限 65k:虽然源码注释说明该上限"未强制实施"(超出也能工作),但超过后基准权重可能被低估,导致 PoV 相关权重不准确。

九、源码结构速查

文件内容
frame/glutton/README.md官方警告与功能简介
frame/glutton/src/lib.rsPallet 全部实现:存储项、外部接口、on_idle与内部浪费逻辑
frame/glutton/src/benchmarking.rs9 个 benchmark 与校准说明
frame/glutton/src/weights.rs自动生成的权重曲线(SubstrateWeight)
frame/glutton/src/tests.rs行为、权限、精度与确定性测试
frame/glutton/src/mock.rs测试用 mock runtime(AdminOrigin = EnsureRoot)
frame/glutton/Cargo.toml依赖与 feature 声明(std/runtime-benchmarks/try-runtime)
bin/node/runtime/src/lib.rs参考运行时中的集成示例(Config、注册、benchmark 白名单)

结语

Glutton Pallet 是 Substrate 测试工具箱中一个"反常规"却极其实用的组件:它用最朴素的方式(反复哈希、反复读存储)把区块链的权重预算精确"烧掉",让开发者能在真实环境中验证链的理论极限。理解它的on_idle消耗模型、FixedU64比例语义与基准测试校准逻辑,不仅能帮助你正确地用它做压测,也能加深对 Substrate 权重系统(ref_time/proof_size二维模型)本身的理解。记住那句开篇警告:它只属于测试环境,永远不要让它出现在承载真实价值的链上。

  • 区块链
  • 开发框架
  • 后端

【免费下载链接】substrate

Substrate: The platform for blockchain innovators

项目地址:https://gitcode.com/gh_mirrors/su/substrate
点击查看免费下载

相关推荐

上一篇:【亲测免费】 xrdp: 远程桌面协议服务器
下一篇:zlib: 一个广泛使用的压缩库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 1:59:24

Git原生可审计代码审查:LLM增强但不替代人工的开源范式

1. 这不是又一个“AI代码审查工具”&#xff0c;而是一套可审计、可追溯、可嵌入工作流的开源协作机制你有没有遇到过这样的场景&#xff1a;团队里新来的同学提交了一段看似没问题的Python函数&#xff0c;用pandas.DataFrame.apply()处理了上万行数据&#xff0c;本地跑得飞快…

作者头像 李华
网站建设 2026/9/26 1:59:09

轻量开源版IDEA实战:Lithe编辑器开发Spring Boot项目体验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:58:49

时间数据,如何用小时销售分析提升你的业绩

在现代数据分析中,时间是一个关键维度,能够发现潜在的趋势和模式。从时间戳数据中提取出年、月、日、小时等特征,可以为更深层次的分析提供依据,并揭示数据背后的规律。例如,在零售业中,通过时间特征的分析,可以发现某些时间段的销售高峰,从而帮助企业做出更好的决策。…

作者头像 李华
网站建设 2026/9/26 1:58:35

销售数据异常?教你如何成为数据清洗高手

在数据分析中,异常值的处理是至关重要的一环。异常值是那些偏离正常范围的数据点,可能是由于测量误差或是突发事件引起的。如果不对它们进行处理,可能会对整个数据分析产生严重影响,进而影响基于数据的决策过程。 本教程将通过一个电商平台的销售数据案例,展示如何识别并…

作者头像 李华
网站建设 2026/9/26 1:57:28

作品集制作全流程:PS+InDesign跨页排版到PDF印刷输出

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华