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_getBalance、eth_getCode、eth_getProof、eth_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 包名映射到patch、minor或major,并必须包含非空的发布说明;该条目满足"至少一个包 + 非空说明"的校验要求。 - 正文:一句话描述了行为变更——状态查询中省略的区块参数现在默认指向最新区块。
虽然正文只有一行,但它背后对应着 Anvil 节点中一条完整的区块参数解析链路,下文结合源码逐一展开。
为什么需要默认值:JSON-RPC 规范下的可选区块参数
在以太坊 JSON-RPC 规范中,eth_getBalance、eth_getCode、eth_getTransactionCount、eth_getStorageAt、eth_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的分支匹配中,Latest与Pending都被解析为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(状态查询将在待打包区块视角下执行); - 其余所有情况(包括
None、latest、earliest、safe、finalized、具体区块号、区块哈希)一律交给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_getAccount:get_account(第 2442–2459 行),结构与balance对称;eth_getCode:get_code(第 2720–2731 行),同样先经block_request归一化;eth_getProof:get_proof(第 2737–2757 行),此处 fork 判定使用predates_fork_inclusive(包含 fork 锚点本身);eth_getTransactionCount:get_transaction_count(第 4887 行起),接受Option<BlockId>区块参数。
由于所有状态查询共享block_request→ensure_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 提供方(见balance与get_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_getCode、eth_getTransactionCount、eth_getProof、eth_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_getBalance、eth_getCode、eth_getProof、eth_getTransactionCount、eth_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),仅供参考