Foundry Anvil 新增debug_executionWitness端点:基于父状态构建的执行见证(Execution Witness)详解
【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry
Anvil(Foundry 内置的本地以太坊开发节点)在anvil: minor变更中新增了对debug_executionWitnessRPC 端点的支持:该端点返回请求区块的完整父状态所构建的 best-effort 执行见证,格式与 reth 的debug_executionWitness一致,且在分叉(fork)模式下不可用。阅读本文后,你将理解执行见证(execution witness)的概念与用途、Anvil 实现该端点的方式与全部限制条件,并掌握如何通过cast或 HTTP RPC 调用该端点、解读返回结构,以及为无状态(stateless)重放或区块验证工具链提供输入数据。
1. 变更背景:一条 changelog fragment 背后的内容
本主题源自仓库 .changelog/anvil-debug-execution-witness.md,其完整内容如下:
--- anvil: minor --- Added support for the `debug_executionWitness` RPC endpoint, returning a best-effort witness built from the full parent state of the requested block. Not supported while forking.在 Foundry 的 changelog 体系中(详见 .changelog/README.md),.changelog/目录下的每个文件都是一个"变更片段"(fragment):frontmatter 将本次变更映射到某个工作区包(此处为anvil)及版本级别(minor,即次要版本变更),正文则是进入发版说明的条目。该片段声明的核心能力有三点:
- 新增
debug_executionWitnessRPC 端点; - 返回的是best-effort(尽力而为)的见证,构建自被请求区块的完整父状态(full parent state);
- 分叉模式下不支持。
2. 什么是执行见证(Execution Witness)
执行见证是面向无状态以太坊(stateless Ethereum)与区块验证场景的概念。在无状态客户端模型下,验证节点不再维护完整的世界状态,而是随每个区块附带一份"见证",用于证明该区块执行过程中读取/写入了哪些状态,使验证方无需本地全量状态即可重放执行并校验结果。
见证通常包含以下成分:
- 状态 Trie 节点:账户状态与存储槽在 Merkle Patricia Trie 中的节点 RLP 编码;
- 合约代码:执行中涉及的所有合约字节码;
- Preimage(原像):地址与存储槽对应的 key 原像,用于将 hash 映射回明文 key;
- 祖先区块头:
BLOCKHASH指令可访问的历史区块头。
reth 提供了同名的debug_executionWitness端点,Anvil 的新实现明确以"与 reth 格式一致"为目标,便于使用同一套下游工具消费两种节点的输出。
3. Anvil 的实现原理:全量父状态见证
3.1 为什么是"全量父状态"而非"最小见证"
Anvil 在日常运行中不记录某个区块执行究竟触达了哪些状态(这一点在源码注释中被明确点出)。因此它无法像 reth 那样给出"仅包含本次执行访问状态"的最小见证,而是退而求其次:把整个父状态编码进见证。具体来说,见证中包含(见 crates/anvil/src/eth/backend/mem/mod.rs 中debug_execution_witness的实现):
- 父状态 Trie 的全部节点(包含所有账户及其所有存储 Trie)的 RLP 编码;
- 全部合约代码;
- 所有账户地址与存储槽的 preimage。
由于全量父状态是任何最小见证的严格超集,因此针对该区块的无状态重放仍然可以成功——验证方需要的所有状态都在其中。代价是:见证体积随总状态量增长,而不是随区块实际访问的状态量增长。在测试链(状态有限)上这完全可接受,但在大状态链上会明显膨胀。
3.2 核心调用链
从 RPC 入口到底层实现,调用链为:
- RPC 层:crates/anvil/core/src/eth/mod.rs 中定义了
DebugExecutionWitness(BlockNumber),并以#[serde(rename = "debug_executionWitness", with = "sequence")]绑定到 JSON-RPC 方法名,参数按序列(sequence)方式解析(支持"0x1"、"latest"等区块标识),并配有test_serde_debug_execution_witness序列化测试(crates/anvil/core/src/eth/mod.rs)。 - API 层:crates/anvil/src/eth/api.rs 中的
debug_execution_witness(block: BlockNumber)处理器,通过node_info!("debug_executionWitness")记录调用日志后转交 backend。 - Backend 层:crates/anvil/src/eth/backend/mem/mod.rs 中的
Backend::debug_execution_witness,执行实际的见证构建;其中调用state_trie_witness(定义于 crates/anvil/src/eth/backend/mem/state.rs)计算状态根与 Trie 节点集合。
3.3 实现细节
在 backend 实现(crates/anvil/src/eth/backend/mem/mod.rs)中,构建流程为:
- 将区块号解析为数字,超出当前最优区块高度时返回
BlockOutOfRange错误; - 通过
number.checked_sub(1)求父区块号——创世块(number 0)没有父状态,直接返回 "genesis block has no parent state to build a witness from" 错误; - 在 256 个区块的
BLOCKHASH窗口内收集本地已知的祖先区块头(BLOCKHASH_HISTORY),因此返回的headers可能不足 256 个; - 在父区块对应的数据库快照上,通过
state.maybe_full_db()判断是否处于 fork 模式:- 分叉时本地只有远端访问过的账户,且本地计算的状态根与远端链不一致,因此直接返回 "debug_executionWitness is not supported while forking" 错误(见 crates/anvil/src/eth/backend/mem/mod.rs);
- 非分叉时,用
state_trie_witness生成状态根与全部 Trie 节点; - 遍历所有账户:收集账户地址与每个存储槽(32 字节大端)作为 preimage key;对每个账户去重收集合约代码(跳过
KECCAK_EMPTY空代码); - 对 keys 排序去重后,组装返回
ExecutionWitness { state, codes, keys, headers }。
3.4 已知限制(务必注意)
综合 changelog 与源码注释,该端点在以下场景不可用或输出不完整:
| 限制 | 说明 |
|---|---|
| 分叉模式(forking) | 不支持,直接报错。因为本地仅知远端访问过的账户,且本地计算的状态根与远端链状态根不一致 |
| 创世块 | 无父状态,无法构建见证,返回错误 |
--prune-history | 若父区块状态已从状态历史中被裁剪,无法构建见证 |
headers完整性 | 仅包含本地已知的BLOCKHASH窗口(256 个)内的祖先区块头,可能少于 256 个 |
4. 如何在 Anvil 上调用debug_executionWitness
4.1 启动节点
在本地启动 Anvil(非分叉模式,使用默认链即可获得完整状态历史):
anvil需要钱包/合约状态时,可先用anvil启动后通过cast send或脚本部署合约、发送交易,使状态增长,再请求见证。
4.2 使用cast rpc调用
Foundry 自带的cast rpc可直接发起原始 JSON-RPC 调用,例如请求latest区块的见证:
cast rpc debug_executionWitness latest指定具体区块高度:
cast rpc debug_executionWitness 0x1对创世块的请求会得到报错(genesis block has no parent state to build a witness from)。
4.3 使用 curl 调用
curl -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"debug_executionWitness","params":["latest"],"id":1}' \ http://localhost:85454.4 返回结构解读
响应为ExecutionWitness对象,包含四个字段:
state:父状态 Trie 全部节点(含所有存储 Trie)的 RLP 编码数组;其中必然包含父区块的状态根节点;codes:所有非空合约代码的字节码数组;keys:所有账户地址与存储槽 preimage(key)数组,已排序去重;headers:BLOCKHASH窗口内本地已知的祖先区块头 RLP 编码数组。
5. 测试用例:行为如何被验证
集成测试 crates/anvil/tests/it/api.rs 中的can_get_execution_witness完整覆盖了上述行为:
- 启动测试节点,部署
SimpleStorage合约并调用setValue,使状态包含合约代码与存储; - 断言创世块请求返回错误(无父状态);
- 通过 HTTP 客户端请求
debug_executionWitness,参数为部署/写入后的区块号; - 断言返回的
state节点集合中包含父区块状态根(对节点做 keccak256 后与parent.header.state_root比对),证明见证覆盖完整父状态; - 断言
keys中包含发送方、接收方地址的 preimage,以及 32 字节的存储槽 key,证明地址与槽位原像都被收集。
该测试直接验证了"全量父状态 + preimage 收集"的实现语义,可作为理解本端点的最小可复现样例。
6. 典型应用场景
- 无状态重放(stateless re-execution):借助见证在未持有全量状态的节点上重放目标区块,校验执行结果与状态根;
- 区块验证工具链:以 reth 兼容格式消费 Anvil 节点输出的见证,复用既有验证器/见证解析代码;
- 测试与开发:在本地测试环境中快速导出某一区块父状态的完整快照语义(Trie 节点 + 代码 + preimage),用于调试状态证明相关逻辑。
7. 小结
debug_executionWitness是 Anvil 在anvil: minor变更中新增的调试类 RPC 端点,以"全量父状态"策略构建 best-effort 执行见证,与 reth 输出格式兼容,保证无状态重放可用。它适用于非分叉、未裁剪状态历史的本地测试链;在分叉模式、创世块以及--prune-history场景下不可用或输出不完整。结合本文给出的调用方式(cast rpc/curl)、返回结构说明与源码实现细节,你可以快速将该端点接入自己的状态见证或无状态验证实验。
【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考