- 区块链
- 后端
【免费下载链接】chia-blockchain
Chia blockchain python implementation (full node, farmer, harvester, timelord, and wallet)
本文以仓库文档 .cursor/context/testing/clvm.md 为骨架,结合
chia/_tests/clvm/测试套件、SpendSim仿真器与钱包 puzzle 源码,系统梳理 Chia 区块链中 CLVM 智能合约的测试分层与验证策略。读完本文,你将掌握:何时用纯Program单元测试、何时用SpendSim模拟器、如何验证 puzzle 压缩与 Chialisp 反序列化兼容性,以及修改钱包 puzzle 驱动、压缩字典或模拟器时应当守护哪些行为契约并运行哪些测试。
模块定位:chia/_tests/clvm/到底是什么
chia/_tests/clvm/是 Chia 仓库中面向Python 侧 CLVM 程序工具、钱包 puzzle 驱动、条件构造器与轻量级智能合约集成的可执行契约测试目录。它的层级位置介于纯钱包工具测试与全节点共识测试之间:大量测试用例会构造真实的Program、CoinSpend、SpendBundle对象,然后直接执行,或通过一个由生产 mempool 与 coin-store 代码支撑的小型模拟器提交。
从测试目录(chia/_tests/clvm/)可以看到它的职责划分:
| 测试文件 | 守护的边界 |
|---|---|
test_program.py | Program的at/replace/curry/uncurry/run系列 API |
test_curry_and_treehash.py | wallet.util.curry_and_treehash树哈希辅助函数 |
test_puzzle_compression.py | 已部署 puzzle 字节的压缩/解压往返兼容 |
test_chialisp_deserialization.py | CLVM 字节格式反序列化边界 |
test_puzzle_drivers.py、test_puzzles.py | 标准支付 puzzle、taproot 隐藏 puzzle、M-of-N 委托花费 |
test_custody_architecture.py、test_member_puzzles.py、test_restrictions.py | custody puzzle 框架、成员谜题与限制谜题 |
test_message_conditions.py | SEND_MESSAGE/RECEIVE_MESSAGE配对不变式 |
test_singletons.py | singleton 顶层与 P2-singleton 各代版本 |
test_spend_sim.py | SpendSim/SimClient可被测试假设的 RPC 语义 |
benchmark_costs.py、test_clvm_step.py | 成本基准与 CLVM 步进行为 |
这套测试保护了四条关键边界:
Program便捷 API(at、replace、curry、uncurry、run 变体)与钱包 puzzle 构造使用的树哈希辅助;- CLVM 序列化/反序列化与 puzzle 压缩兼容性(含已部署的字典版本);
- 标准支付 puzzle、taproot/隐藏 puzzle 签名、M-of-N 委托花费、singleton 顶层行为、custody puzzle 架构、限制与消息条件;
SpendSim面向 puzzle 测试的 RPC 式表面:mempool 准入、区块挖矿、hint 查询、回滚、puzzle/solution 查询,无需启动完整服务。
值得注意的是,原文档带有版本锚定声明(Verified: 2026-07-12 against 24db9ad3901d),即"若源码与本文档矛盾,以源码为准并更新文档"——这是仓库内部面向后续审计/实现 Agent 的蒸馏式架构上下文,刻意省略了穷尽式的测试清单。
两种执行模式:不要把纯 CLVM 测试和模拟器测试混为一谈
纯 CLVM / 单元测试模式
直接调用Program方法、已编译 puzzle 模块或辅助函数。适用于结构性不变式:精确的 curry 形状、树哈希、解码后的 puzzle driver 字段、压缩往返、Chialisp 反序列化、CLVM 步进。
例如test_program.py中test_at断言Program.to(17) == Program.to([10, 20, 30, [15, 17], 40, 50]).at("rrrfrf"),且非法字符q抛ValueError、越界路径ff抛EvalError——这些都是确定性的结构断言,不依赖任何链上状态。
模拟器支撑测试模式
使用sim_and_client()(实现见 chia/_tests/util/spend_sim.py):
from chia._tests.util.spend_sim import sim_and_client async with sim_and_client() as (sim, client): # sim: SpendSim —— 直接操作区块/回滚/时间 # client: SimClient —— 类似 full-node RPC 的查询/提交接口 pass关键点:SpendSim不是 mock mempool。它把真实的 full-nodeCoinStore、HintStore、MempoolManager、Mempool与simple_solution_generator()(来自 chia/full_node/bundle_tools.py)接线到一个内存数据库中,区块则由裁剪后的SimFullBlock/SimBlockRecord表示。因此SimClient.push_tx()会走生产代码路径pre_validate_spendbundle()与add_spend_bundle(),像GENERATOR_RUNTIME_ERROR、时间锁错误、消息配对错误、签名/条件校验失败这些结果,都是有意义的真实 mempool 结果,而不是模拟器伪造的。
SpendSim.farm_block()的完整流程(spend_sim.py 第 243–314 行):
- 汇总 mempool 中所有
MempoolItem.fee计算费用; - 通过
calculate_pool_reward/calculate_base_farmer_reward与create_pool_coin/create_farmer_coin生成区块奖励币; - 若 mempool 非空,调用
MempoolManager.create_bundle_from_mempool()打包交易; - 用
compute_spend_hints_and_additions计算 hint 并写入hint_store,收集 spent 币与新增币; - 调用
coin_store.new_block()应用添加/移除并记录交易 generator; - 追加
SimBlockRecord/SimFullBlock,递增高度; - 通过
new_peak()清空并重验证 mempool。
rewind()则回滚 coin store(rollback_to_block)并重建 mempool 对象、重置区块列表与时间戳。这正是 singleton 与 custody 测试能在同一预先花费高度上复用、走不同分支的原因。在 test_spend_sim.py 中,test_rewind在 5 个块之后rewind(save_height),断言区块列表长度与高度精确恢复。
旧式 harness:chia/_tests/clvm/coin_store.py
这是一个更小的遗留风格花费测试装置,被较老的 puzzle 测试(如test_puzzles.py)使用。其CoinStore.validate_spend_bundle()通过get_name_puzzle_conditions(..., mempool_mode=True, height=uint32(3886635))(soft-fork2 之后的语义)获取 NPC 结果,再用check_time_locks()检查时间锁,然后把添加/移除应用到一个内存记录映射中。它不维护 hint、不产生区块、不支持回滚。文档给出的选择标准是:凡是依赖真实 mempool 准入、hint、回滚或区块包含行为的功能,优先用SpendSim。
SpendSim 的 RPC 语义契约
test_spend_sim.py定义了智能合约测试可以从SimClient假设的行为:
- 挖矿产生真实的奖励币与高度——
test_farming断言 5 次farm_block()后高度为 4,且首块reward_claims_incorporated[0].amount == 18375000000000000000; push_tx()返回MempoolInclusionStatus+Err元组;- hint 查询遵守 include-spent 与高度过滤——
test_all_endpoints中用 hint 币验证了include_spent_coins=False/True、start_height、end_height各种组合下返回数量精确变化; - puzzle-hash / puzzle-hashes / parent-id / name / block-record / block / additions-removals / mempool item / puzzle-solution 等查询的行为与 full-node RPC 足够接近,可支撑 puzzle driver 测试;
get_puzzle_and_solution()使用生产 Rust 实现chia_rs.get_puzzle_and_solution_for_coin2从记录的区块 generator 中重建CoinSpend——test_all_endpoints断言重建结果与原始bundle.coin_spends[0]完全相等。
由于SpendSim存储的是简化区块记录,它适合 puzzle 与 mempool 行为验证,不适合做共识/头/weight-proof 断言。
SimClient还实现了get_all_mempool_tx_ids、get_all_mempool_items、get_mempool_item_by_tx_id、get_coin_records_by_hint等接口,设计初衷(见 spend_sim.py 顶部注释)是:先用它测试,之后可以无缝换成行为一致的chia.rpc.full_node_rpc_client真实客户端。
Program封装:钱包侧 CLVM 行为契约
Program(chia/types/blockchain_format/program.py)是围绕 s-expression 数据的薄封装。原文档强调测试守护其精确行为,以下为源码与测试互相印证的关键点:
at 与 replace:同一套f/r语法
at(position)只接受f(取 first)与r(取 rest)组成的路径串,出现其他字符直接抛ValueError(源码第 92–109 行)。replace(**kwargs)使用相同语法替换路径,并且拒绝冲突或不可能的路径而非部分重建畸形树:test_replace_conflicts中p1.replace(rr=105, rrf=200)抛ValueError,test_replace_conflicting_paths中p1.replace(ff=105)同样失败,test_replace_bad_path验证q、rq等非法路径。
run 系列:默认 flags 的微妙之处
Program实例的run()/run_with_cost()默认flags=DEFAULT_FLAGS,而DEFAULT_FLAGS = MEMPOOL_MODE(源码第 23 行)——即 wallet 风格、包含 mempool/soft-fork 行为;模块级辅助函数run/run_with_cost(同文件第 298–311 行)则可以不使用 mempool flags。断言 cost 或输出字节的测试必须说清自己走哪条路径。test_run验证(/ 2 5)对[10, -5]在 100000 上限下 cost 恒为 1107、结果为0xFE。
curry 与 uncurry:严格识别规范形状
curry()发出规范的 CLVM curry 形状,uncurry()只识别该形状且拒绝带尾随垃圾或畸形 quoted 参数的表达。test_curry_uncurry的幂等性检查断言:
(a (q (+ 2 5)) (c (q . 200) (c (q . 30) 1)))即(a (q F) (c (q A1) (c (q A2) 1)))的标准形态。而test_uncurry_top_level_garbage、test_uncurry_args_garbage、test_uncurry_not_pair、test_uncurry_not_curried分别验证:顶层列表尾部有垃圾、args 列表尾部有垃圾、第二个元素不是 quoted pair、函数根本没有 curry 过——这四种情况下uncurry()都返回原程序与Program.to(0),而不是部分解析。钱包 puzzle driver 正是依赖这种严格性来检查分层 puzzle。
树哈希辅助:wallet.util.curry_and_treehash
test_curry_and_treehash.py验证 chia/wallet/util/curry_and_treehash.py 中的curry_and_treehash、calculate_hash_of_quoted_mod_hash、shatree_atom、shatree_atom_list、shatree_int必须与Program.to(...).get_tree_hash()在 atom、int、atom 列表与 curried 参数上完全一致。test_curry_and_treehash以p2_delegated_puzzle_or_hidden_puzzle.MOD为模板,对 500 组参数[v, v*v, v*v*v]逐一断言puzzle.get_tree_hash()与手工curry_and_treehash()结果相等;test_shatree_int的参数化值覆盖了0、-1、0x7F、0x80、100000000、-10000000,专门盯防负整数与大整数编码不一致。
Puzzle 压缩:兼容性测试,而非性能测试
test_puzzle_compression.py配合 chia/wallet/util/puzzle_compression.py 守护已部署 puzzle 的压缩兼容性:
ZDICT是已部署 puzzle 字节与遗留字典的有序列表(源码第 31–43 行):standard_puzzle.MOD + LEGACY_CAT_MOD、OFFER_MOD_OLD、singleton/NFT 各模块、CAT_MOD、SETTLEMENT_PAYMENT,最后是一个b""字典——注释明确说明这是"故意破坏与旧版本的兼容",提醒后续字典更新必须谨慎;LATEST_VERSION = len(ZDICT)(当前为 6);compress_object_with_puzzles在数据前写入 2 字节大端版本号,之后是带 zdict 的 zlib 压缩;decompress_object_with_puzzles读取版本号后选择zdict_for_version(version)解压,版本超出len(ZDICT)抛CompressionVersionError;- 解压输出上限:
decompress_with_zdict使用max_length=6 * 1024 * 1024(6 MiB)。test_decompress_limit对 10 MiB 缓冲压缩后解压,断言结果 ≤ 6 MiB——这是资源上限检查,不是压缩率测试; lowest_best_version(puzzle_list)从版本 1 起,对每个 mod 在 ZDICT 中查找出现的最高版本并取max(...)+1。test_lowest_best_version断言:CAT_MOD → 4、OFFER_MOD_OLD → 2、OFFER_MOD → 5;- 往返一致性与压缩率:
test_standard_puzzle、test_cat_puzzle、test_offer_puzzle、test_nesting_puzzles(CAT 嵌套标准谜题)、test_unknown_wrapper(未知 wrapper 也仅作字面压缩)都断言"压缩后更小 + 解压后与原文相等",并通过CompressionReporter记录compression_ratio; test_version_override证明同一SpendBundle用版本 1 压缩的结果大于用LATEST_VERSION压缩的结果,且两者都能正确解回。
Chialisp 反序列化:字节格式边界的正反两面
test_chialisp_deserialization.py用chia_puzzles_py.programs.CHIALISP_DESERIALISATION编译出反序列化模块,以INFINITE_COST上限直接运行:
- 正面用例:简单列表
("hello" "friend")(十六进制ff8568656c6c6fff86667269656e6480)、一个完整的"密码币"puzzle、包含三个超长整数(正、负、hex 前缀)的列表——都断言反序列化结果与Program.from_bytes(b)一致; - 负面用例:
serialized_atom_overflow(size)构造 6 字节长度前缀(0x80/0xC0/0xE0/0xF0/0xF8/0xFC系列)加上 1000 字节填充,对0xFFFFFFFF、0x3FFFFFFFF、0xFFFFFFFFFF、0x1FFFFFFFFFF四种溢出尺寸断言运行抛异常。
这组测试守护的是 CLVM 字节格式解析的规范化与失败行为。
Custody 架构契约:可组合 puzzle 框架
custody 测试驱动 chia/wallet/puzzles/custody/custody_architecture.py 中的PuzzleWithRestrictions、MofN、MemberHint、RestrictionHint、UnknownMember、UnknownRestriction、DelegatedPuzzleAndSolution等类型,作为一个小型可组合 puzzle 框架验证:
PuzzleWithRestrictions.memo()是链上/导出同步契约;from_memo()必须重建未知成员与限制,包括递归的MofN,这样后续钱包代码才能按 puzzle hash 填充已知 puzzle 实现;puzzle_reveal()按INDEX_WRAPPER→ 可选限制层 → 顶层DELEGATED_PUZZLE_FEEDER的顺序分层;puzzle_hash()使用预计算哈希来匹配 reveal,而不必物化每一层;solve()必须按 CLVM 模块预期的精确顺序对齐:成员验证者 solution、委托 puzzle 验证者 solution、成员 solution、可选的委托 puzzle/solution。test_custody_architecture.py通过参数化(各种限制组合 × custody/MofN puzzle 组合)来防止 proof 格式回归;MofN拒绝不可能的阈值与重复成员节点;其 solve 格式随阈值形状变化,测试迭代组合以捕获格式回归。
具体成员 puzzle 测试(test_member_puzzles.py)覆盖三类:test_bls_with_taproot_member(BLS + taproot)、test_singleton_member(singleton 背书成员)、test_fixed_puzzle_member(固定 puzzle 成员)。每类都同时断言成功路径与逃逸路径:非法隐藏 puzzle、错误的固定委托 puzzle、缺失的 singleton 批准消息、构造器/solve 误用,分别以SUCCESS, None或FAILED, Err.GENERATOR_RUNTIME_ERROR作为结果。
限制(restriction)测试(test_restrictions.py)覆盖委托 puzzle 包装栈、高度锁、固定CREATE_COIN目的地、SEND_MESSAGE禁令。关键语义:一个合法的限制通常要求在提交前用限制包装委托 puzzle;只提交原始委托 puzzle + 匹配 solution 会失败。错误码区分也很有代表性:高度锁未满足返回(PENDING, Err.ASSERT_HEIGHT_RELATIVE_FAILED)(进入待处理而非直接拒绝),而其余错误多为(FAILED, Err.GENERATOR_RUNTIME_ERROR)。
消息条件:配对不变式与 API 严格性
test_message_conditions.py覆盖配对的SEND_MESSAGE/RECEIVE_MESSAGE不变式:对每种非零的发送者/接收者承诺模式(参数化范围0b001001..0b111111,跳过以000结尾的无效组合),单独的 send 或单独的 receive 都会得到(FAILED, Err.MESSAGE_NOT_SENT_OR_RECEIVED),而聚合的配对消息成功。
MessageParticipant(chia/wallet/conditions.py)被刻意设计得很严格:没有 anyone-can-send/receive 参与者;coin-id 承诺要么独立存在、要么必须与 parent/puzzle/amount 全部字段匹配;手动传入的mode_integer必须与提供的参数一致。原文档指出这些"既是条件测试,也是 API 脚枪(footgun)测试"——即测试本身就是对易错 API 用法的约束。
Singleton 契约:多代顶层的守恒不变式
test_singletons.py 同时测试 legacy 与当前顶层,覆盖 chia/wallet/puzzles/singleton_top_layer.py、singleton_top_layer_v1_1.py、p2_singleton.clsp、p2_singleton_or_delayed_puzhash.clsp,断言 launcher 流程、eve spend、稳态 spend、P2-singleton claims、P2-singleton-or-delayed claims、delayed escape、melting 与负奇数金额不变式。test_singleton_top_layer以version=[0, 1]参数化两种顶层。
核心不变式:
- launcher 金额必须是奇数;
- singleton 花费必须恰好创建一个奇数子币(melting 场景除外);
- lineage proof 从父币花费推导,且对非 launcher 父币要包含 inner puzzle hash;
- P2-singleton claims 同时耦合 coin 与 puzzle 公告;
- delayed escape 需要在模拟器状态中流逝足够秒数/块数(
SpendSim.pass_time/pass_blocks正是为此设计); - legacy 与当前 singleton 层对同一畸形 even-coin 路径可能返回不同的
Err值,测试明确编码了这一区别。
测试中的sign_delegated_puz辅助展示了真实签名路径:通过p2_delegated_puzzle_or_hidden_puzzle.calculate_synthetic_secret_key计算合成私钥,再以del_puz.get_tree_hash() + coin.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA为消息做 BLS 签名(AugSchemeMPL.sign)。
编辑与评审指南:写测试时守住这些约定
原文档给出了面向未来修改者的明确纪律,这与仓库源码一一对应:
- 何时用模拟器:断言依赖 mempool 准入、条件验证、hint、回滚或交易 generator 包含行为时,用
sim_and_client();确定性的 CLVM 结构、序列化、哈希与辅助 API 行为,直接用Program执行。 - 负路径断言要具体:测试经常区分
FAILED与PENDING,并断言具体Err码——如GENERATOR_RUNTIME_ERROR、ASSERT_HEIGHT_RELATIVE_FAILED、ASSERT_SECONDS_RELATIVE_FAILED、ASSERT_MY_AMOUNT_FAILED、MESSAGE_NOT_SENT_OR_RECEIVED(前两者已在 restriction/member 测试中实证)。 - 不要用原始状态突变替代模拟器流程:很多测试依赖
new_peak()、coin-store 回滚、hint 持久化或 mempool 清理;rewind()之所以被 singleton/custody 测试复用,正是因为它在真实 coin store 与 mempool 上执行这些生产路径。 - 改 wallet puzzle driver 时:同时断言纯表示契约(memo、puzzle hash、parse/fill 行为)和一条走
SpendSim的链上花费路径。前者捕获钱包同步/识别回归,后者捕获 CLVM 或 mempool 接受回归。 - 压缩变更:保持已部署字典兼容性与解压输出上限。新增字典版本必须更新
LATEST_VERSION行为与lowest_best_version()的期望,同时不能让旧压缩 blob 无法解码(ZDICT末尾的b""哨兵正是对"打破旧兼容"的显式警示)。
验证指南:改动落在哪里,就跑哪组测试
- 改动涉及
SpendSim:运行test_spend_sim.py,并至少选一个使用回滚或 mempool 错误的模拟器支撑 puzzle 套件(如test_singletons.py、test_custody_architecture.py中的相关用例); - 改动涉及
Program:运行test_program.py与test_curry_and_treehash.py; - 改动涉及钱包 puzzle driver:运行对应的 CLVM 测试文件(
test_puzzle_drivers.py、test_member_puzzles.py、test_restrictions.py、test_message_conditions.py、test_singletons.py等),若该 puzzle 出现在钱包识别或交易创建路径上,再补对应钱包模块测试。
源码导航
- CLVM 测试套件:chia/_tests/clvm/(本文全部测试文件均在此目录)
- SpendSim 模拟器实现:chia/_tests/util/spend_sim.py
- 旧式花费测试装置:chia/_tests/clvm/coin_store.py
- Python 侧 CLVM 封装:chia/types/blockchain_format/program.py 与 chia/wallet/puzzles/
- 树哈希辅助:chia/wallet/util/curry_and_treehash.py
- 压缩字典与版本逻辑:chia/wallet/util/puzzle_compression.py
- 相关行为上下文:.cursor/context/types.md、.cursor/context/wallet.md
一句话总结:这套 CLVM 测试体系的价值在于"真实"二字——Program层守护确定性结构,SpendSim层复用生产级 mempool/coin-store 逻辑执行完整花费流程,二者共同构成了从钱包 puzzle 构造到链上接受的连续验证闭环。
- 区块链
- 后端
【免费下载链接】chia-blockchain
Chia blockchain python implementation (full node, farmer, harvester, timelord, and wallet)
相关推荐
5分钟快速上手:SmartRefreshLayout自定义刷新组件终极指南
5分钟快速上手:SmartRefreshLayout自定义刷新组件终极指南 还在为Android应用的下拉刷新样式千篇一律而烦恼吗?想要为你的App打造独一无二
移动开发UI组件chia-blockchain智能合约测试自动化:提高开发效率的技巧
chia blockchain智能合约测试自动化:提高开发效率的技巧 引言 在chia blockchain项目开发过程中,智能合约的测试自动化是确保代码质量和
区块链后端PE-sieve深度解析:从API调用到内存扫描的完整技术实现
PE sieve深度解析:从API调用到内存扫描的完整技术实现 PE sieve是一款强大的内存扫描工具,能够识别并转储各种潜在的恶意植入物,如被替换或注入的P
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考