Foundry Anvil 状态查询 RPC 区块参数默认化解析:省略 block 参数时默认查询最新区块
2026/9/15 13:14:36 网站建设 项目流程

Foundry Anvil 状态查询 RPC 区块参数默认化解析:省略 block 参数时默认查询最新区块

【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry

本篇围绕 Foundry 仓库.changelog/anvil-default-state-block.md记录的 Anvil 行为变更展开:当eth_getBalanceeth_getCodeeth_getProofeth_getTransactionCount等状态查询 RPC 省略区块参数(block number/tag)时,Anvil 现在默认将其解析为「最新区块(latest)」。读完本文,你将理解该默认化的 JSON-RPC 背景、底层ensure_block_number的区块解析逻辑、全部区块标签(earliest / latest / safe / finalized / pending / 具体区块号)的语义差异,以及越界与 fork 模式下的边界行为。

变更速览:changelog 条目原文与定位

仓库根目录的.changelog/anvil-default-state-block.md是一个标准的 Foundry changelog 碎片文件,全文如下:

--- anvil: patch --- Defaulted omitted state query block parameters to the latest block.

它由两部分构成:

  • frontmatter:声明本次变更作用于anvil包,升级级别为patch(缺陷修复级别,不涉及 API 破坏)。按 .changelog/README.md 中描述的条目规范,frontmatter 将 workspace 包名映射到patchminormajor,并必须包含非空的发布说明;该条目满足"至少一个包 + 非空说明"的校验要求。
  • 正文:一句话描述了行为变更——状态查询中省略的区块参数现在默认指向最新区块

虽然正文只有一行,但它背后对应着 Anvil 节点中一条完整的区块参数解析链路,下文结合源码逐一展开。

为什么需要默认值:JSON-RPC 规范下的可选区块参数

在以太坊 JSON-RPC 规范中,eth_getBalanceeth_getCodeeth_getTransactionCounteth_getStorageAteth_getProof等"状态查询"类方法,其区块参数(block number 或 tag)是可选的:客户端可以在调用时不传入该参数,也可以显式传入"latest"

这一可选性带来了一个语义问题:当调用方省略该参数时,节点应查询哪个高度的状态?不同实现历史上存在差异。本次变更明确了 Anvil 的答案——省略即视为latest

核心实现:ensure_block_number的默认化解析

区块参数默认化的逻辑核心位于 Anvil 的内存后端Backend中:crates/anvil/src/eth/backend/mem/mod.rs 的ensure_block_number方法(第 2210–2238 行):

pub async fn ensure_block_number<T: Into<BlockId>>( &self, block_id: Option<T>, ) -> Result<u64, BlockchainError> { let current = self.best_number(); let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { BlockId::Hash(hash) => { self.block_by_hash(hash.block_hash) .await? .ok_or(BlockchainError::BlockNotFound)? .header .number } BlockId::Number(num) => match num { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => U64::ZERO.to::<u64>(), BlockNumber::Number(num) => num, BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), }, }; if requested > current { Err(BlockchainError::BlockOutOfRange(current, requested)) } else { Ok(requested) } }

关键的一行是:

match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) {

从源码结构可以清晰看到本次变更的落点:当调用方传入None(即省略区块参数)时,unwrap_or会将其替换为BlockId::Number(BlockNumber::Latest),随后在BlockNumber的分支匹配中,LatestPending都被解析为current,也就是self.best_number()(当前最优区块高度)。这正是 changelog 所述"省略的状态查询区块参数默认指向最新区块"的直接实现。

各区块标签的解析语义

同一段match揭示了 Anvil 对各类区块标签的统一处理规则:

区块参数解析结果说明
省略(None最新区块高度本次变更的核心默认化行为
"latest"/"pending"current(最优区块高度)二者在状态查询中均取当前链头
"earliest"0创世区块高度
"safe"current - slots_in_an_epoch安全区块,按一个 epoch 的 slot 数向前偏移(saturating_sub防止下溢)
"finalized"current - slots_in_an_epoch * 2最终确认区块,偏移两个 epoch
具体区块号原样使用"0x10"
区块哈希(BlockId::Hash按哈希查块取高度查不到时报BlockNotFound

从代码可以推断,safe/finalized在 Anvil 本地(非 PoS 全节点)语境下是基于链头高度的相对估算,其偏移量由slots_in_an_epoch(epoch 内 slot 数)决定,并非真实的共识确认状态。

状态查询处理链:block_request与各 RPC handler

ensure_block_number并非被直接调用,而是经由 API 层的统一辅助方法block_request完成参数归一化。crates/anvil/src/eth/api.rs 第 1611–1626 行:

async fn block_request( &self, block_number: Option<BlockId>, ) -> Result<BlockRequest<FoundryTxEnvelope>> { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { let pending_txs = self.pool.ready_transactions().collect(); BlockRequest::Pending(pending_txs) } _ => { let number = self.backend.ensure_block_number(block_number).await?; BlockRequest::Number(number) } }; Ok(block_request) }

该方法的处理逻辑为:

  • 若显式传入Pending,则收集交易池中就绪交易,构造BlockRequest::Pending(状态查询将在待打包区块视角下执行);
  • 其余所有情况(包括Nonelatestearliestsafefinalized、具体区块号、区块哈希)一律交给ensure_block_number归一化为具体区块号,即省略参数时在这里走默认latest分支。

随后,各状态查询 RPC handler 都复用block_request与 fork 判定逻辑,形成统一的状态查询处理链。典型的调用方包括:

  • eth_getBalance:api.rs 的balance方法(第 2424–2437 行),先block_request,再检查 fork 模式下目标区块是否早于 fork 锚点(fork.predates_fork(number)),是则直接委托给 fork 提供方查询远端历史状态,否则由本地后端get_balance处理;
  • eth_getAccountget_account(第 2442–2459 行),结构与balance对称;
  • eth_getCodeget_code(第 2720–2731 行),同样先经block_request归一化;
  • eth_getProofget_proof(第 2737–2757 行),此处 fork 判定使用predates_fork_inclusive(包含 fork 锚点本身);
  • eth_getTransactionCountget_transaction_count(第 4887 行起),接受Option<BlockId>区块参数。

由于所有状态查询共享block_requestensure_block_number这条路径,本次默认化变更一次性覆盖了上述全部方法,而无需在各 handler 中分别处理空参数。

边界行为:越界报错与 fork 模式

区块越界

ensure_block_number在归一化后会做一次守卫检查:

if requested > current { Err(BlockchainError::BlockOutOfRange(current, requested)) } else { Ok(requested) }

即:解析出的区块高度若高于当前链头,Anvil 返回BlockOutOfRange错误,而不是静默截断或返回空状态。这对于省略参数(默认 latest,必然不超过链头)的场景影响不大,但保护了显式传入未来区块号的调用。

fork 模式下的历史状态委托

当 Anvil 以--fork-url运行、且查询区块早于 fork 锚点时,状态查询会被委托给远端 fork 提供方(见balanceget_account中的fork.predates_fork分支、get_proof中的predates_fork_inclusive分支),从而返回真实的链上历史状态;eth_getCode同样如此(api.rs 第 2724–2729 行)。省略区块参数默认走 latest,即返回 fork 之后本地最新状态。

实操验证:典型调用与预期结果

eth_getBalance为例,以下三种调用在本次变更后行为一致(省略参数等价于显式latest):

# 省略区块参数:默认 latest curl http://localhost:8545 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000"],"id":1}' # 显式 latest curl http://localhost:8545 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","latest"],"id":1}' # 指定历史区块号(低于当前链头时返回历史余额) curl http://localhost:8545 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","0x10"],"id":1}'

eth_getCodeeth_getTransactionCounteth_getProofeth_getStorageAt等状态查询方法遵循同一规则:省略区块参数 → 最新区块;显式pending→ 待打包区块视角;显式历史区块号/earliest→ 对应历史状态(fork 模式下委托远端)

总结

.changelog/anvil-default-state-block.md记录的虽是一行 patch 说明,其背后是 Anvil 状态查询链路上一次语义收敛:通过 ensure_block_number 中unwrap_or(BlockId::Number(BlockNumber::Latest))的默认化处理,将省略区块参数的调用统一归一到最新区块,避免了不同状态查询方法之间对空参数的差异化解释。该行为由 API 层 block_request 统一承载,覆盖eth_getBalanceeth_getCodeeth_getProofeth_getTransactionCounteth_getAccount等全部状态查询 RPC;配合earliest/safe/finalized/pending/ 具体区块号 / 区块哈希的完整解析矩阵,以及BlockOutOfRange越界守卫和 fork 模式下的历史状态委托,Anvil 为开发者在本地复现任意历史状态提供了语义明确、行为一致的状态查询入口。

【免费下载链接】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),仅供参考

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

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

立即咨询