Foundry Anvil 新增 `debug_executionWitness` 端点:基于父状态构建的执行见证(Execution Witness)详解
2026/9/15 11:46:10 网站建设 项目流程

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,即次要版本变更),正文则是进入发版说明的条目。该片段声明的核心能力有三点:

  1. 新增debug_executionWitnessRPC 端点;
  2. 返回的是best-effort(尽力而为)的见证,构建自被请求区块的完整父状态(full parent state);
  3. 分叉模式下不支持

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 入口到底层实现,调用链为:

  1. 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)。
  2. API 层:crates/anvil/src/eth/api.rs 中的debug_execution_witness(block: BlockNumber)处理器,通过node_info!("debug_executionWitness")记录调用日志后转交 backend。
  3. 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:8545

4.4 返回结构解读

响应为ExecutionWitness对象,包含四个字段:

  • state:父状态 Trie 全部节点(含所有存储 Trie)的 RLP 编码数组;其中必然包含父区块的状态根节点;
  • codes:所有非空合约代码的字节码数组;
  • keys:所有账户地址与存储槽 preimage(key)数组,已排序去重;
  • headersBLOCKHASH窗口内本地已知的祖先区块头 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询