ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Anvil fork 端点身份校验一文讲透

Anvil fork 端点身份校验一文讲透 Anvil fork 端点身份校验一文讲透【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundryFoundry 的 Anvil 是本地以太坊节点fork 模式下通过--fork-url拉取远端链状态。这次 Anvil patch 给anvil_reset和anvil_setRpcUrl两条入口加上了端点身份校验切换远端源之前先确认 URL 背后执行上下文链 ID、hardfork、网络画像、fork 锚点区块与预期一致通过后才原子提交。收益很直接——远端节点被换掉后旧 fork 缓存不再被复用跨网络家族的 reset 和Anvil fork 自己 RPC的自环会在门口被挡住。为什么 URL 字符串不够用先设想一次故障 http://relay.example/fork这个地址背后的节点重启了、换了数据目录或者执行配置被改到了另一个 hardfork——URL 一个字没变返回的链上下文却整个换了。如果 Anvil 只凭 URL 字符串判断端点没变旧端点写进磁盘的 fork 缓存会被静默复用状态污染要等到测试跑出莫名其妙的失败才暴露。为此源码把端点的身份建模成一组描述执行上下文的字段定义在 fork.rs#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct ForkEndpointIdentity { pub(crate) execution_chain_id: u64, pub(crate) source_chain_id: u64, pub(crate) network: OptionNetworkVariant, pub(crate) network_profile: OptionNetworkConfigs, pub(crate) hardfork: OptionFoundryHardfork, pub(crate) instance_id: OptionB256, pub(crate) source_fork_block_number: Optionu64, pub(crate) source_fork_block_hash: OptionB256, }这不是 URL 的哈希而是这个端点实际在跑什么的画像字段锁定的信息execution_chain_id端点实际执行所在的链 IDsource_chain_idfork 数据所来源的链 IDnetwork/network_profile网络变体与画像用于是否同一网络家族的判断hardfork端点上报的硬分叉它是否上报决定了身份可不可信instance_id实例唯一标识用来识别是不是我自己source_fork_block_number/source_fork_block_hashfork 锚点区块的高度与哈希值得注意context_eqfork.rs在比对两份身份时刻意排除了instance_id——同一上下文下的新实例允许接管但上下文本身链、网络、hardfork、锚点区块不许变。校验的对象是执行上下文是否一致而不是是不是同一个进程。身份从哪来可信性怎么分身份不是凭空有的得向远端要。Anvil 的探测策略写在 config.rs用anvil_nodeInfo私有方法询问对端是否 Anvil500ms 超时核心是一个matchmatch response { Ok(node_info) { self.identified true; Ok(Some(node_info)) } Err(_) if !self.identified Ok(None), Err(error) { Err(error).wrap_err(failed to determine network family from fork endpoint) } }这段代码做了两件事首次成功响应之前探测失败被当作可选能力不可用直接吞掉普通 RPC 不实现anvil_nodeInfo不能让启动卡在未知方法上一旦确认对端是 Anvil此后的失败一律作为错误返回。后者是关键——如果远端端点在中途被 reset 或执行配置被替换探测会失败而在这里静默吞掉就等于把端点被换掉这件事藏起来了。结构体上方的文档注释L112-L118把这个先尽力、后严格的策略写得很明白。探测成功上报了hardfork身份就被标记为权威authoritativeis_authoritative就是self.hardfork.is_some()一行fork.rs。后面所有严格的判定都挂在这一个 bit 上。调一次 anvil_setRpcUrl中间发生了什么anvil_setRpcUrl用于动态更换远端源handler 在 api.rs。它最重要的设计是旧端点留下的身份信息不能当作新 URL 的提示直接信任。handler 先把旧的链 ID 提示清零强制对新 URL 重新解析真实身份validation_config.fork_chain_id None; let (provider, endpoint_identity) validation_config .replacement_fork_provider( url, expected_identity, block_number, block_hash, self.instance_id(), ) .await?;为什么必须清零因为不清零的话一个不受支持的网络比如 Anvil 执行不了的 zkSync EraVM可能藏在旧端点的离线提示后面绕过网络家族检查。真正的重活在replacement_fork_providerconfig.rs这个重试循环值得逐行看for _ in 0..3 { let before self.resolved_fork_endpoint_identity(provider, mut node_info_probe).await?; eyre::ensure!( before.instance_id ! Some(serving_instance_id), cannot set Anvils fork provider to its own RPC endpoint ); let block provider .get_block(BlockNumberOrTag::Number(block_number).into()) .await .wrap_err(failed to confirm active fork block on replacement endpoint)?; let after self.resolved_fork_endpoint_identity(provider, mut node_info_probe).await?; if before ! after { continue; } if !before.context_eq(expected) { eyre::bail!(replacement fork endpoint has an incompatible execution context); }取区块前后各读一次身份两次不一致说明端点在验证期间被换过就从头重试最多 3 次仍不一致就放弃报错。同时在这里就地检查新端点不能是自己的 RPC自环、上下文必须与旧身份context_eq、fork 锚点区块哈希必须对得上。验证通过才进提交段拿lifecycle_lock写锁加 mining 锁在一个临界区内一次写掉provider、fork_urls、endpoint_identity并同步node_config.fork_endpoint_is_anvilapi.rs。整个流程再被reset_lock串行化api.rs 顶部的注释L164-L167说明了它的作用身份读取与 reset 转换之间不允许交错读到的身份不会在验证中途被另一条路径改掉。anvil_reset 的先摆好、再切换fork 重置路径由 mem/mod.rs 里的stage_fork_reset承担。它的做法与 setRpcUrl 不同不触碰运行中的后端把新 fork、新 DB、新费用状态全部摆好验证全过才切换。承载这些内容的StagedMemoryReset在源码注释里被描述为fully prepared in-memory replacement awaiting an atomic backend commitmem/mod.rs。校验清单按顺序执行任何一步失败都走Err或Ok(None)运行中的后端原封不动跨网络家族未显式选择网络且新端点画像不受支持时直接返回invalid_params错误信息明说不能跨网络家族 reset要用匹配的配置起新实例L4402-L4414自环目标instance_id等于本机serving_instance_id时拒绝L4415-L4419if staged_client_config.endpoint_identity.instance_id Some(serving_instance_id) { return Err( RpcError::invalid_params(cannot reset Anvil to its own RPC endpoint).into() ); }锚点区块从远端拉取 fork 区块header 哈希与解析出的block_hash不一致就放弃L4420-L4426提交前复验fork_urls_match_context对 URL、身份、区块号与哈希再核对一遍L4434-L4444。第 4 条看似冗余反面场景是真实的reset 是异步长流程取区块的时刻不等于提交的时刻端点可能在这中间被换掉。没有这次复验就可能把旧上下文的 fork 提交上去——而有了它验证时是一套、提交时又是另一套的中间态根本不可能落地。身份变了哪些缓存必须失效严格校验最终服务于 fork 磁盘缓存的保护 。ForkCacheSource记录最近一次提交 fork 的来源URL 身份mem/mod.rs而同一 URL 上身份是否被换过的判定只有三行let authoritative self.endpoint_identity.is_authoritative() || endpoint_identity.is_authoritative(); self.rpc_url rpc_url authoritative self.endpoint_identity ! endpoint_identityauthoritative这道闸门值得注意只有新旧至少一侧是权威身份时才启用严格比对。匿名 RPC 端点应答不了anvil_nodeInfo的普通节点在同一 URL 复用时保留原有缓存行为——不是所有端点都能被严格验证过度严格会打破既有体验。一旦判定身份变化三件事依次发生新 DB 先清空为状态快照、只写入新 fork 区块头确保不继承旧端点存储L4372-L4378旧、新两侧的缓存命名空间被收进失效列表命名空间按source_chain_id URL 定位文件名形如storage-{keccak256(url)}.jsonmem/mod.rs收集逻辑在 L4379-L4395提交时原子地失效这些命名空间并丢弃旧缓存状态L4396-L4397。也就是说URL 一个字没变只要身份hardfork、链 ID、网络画像变了旧缓存就再也不会被复用。staged DB 自身由StagedForkCacheLease保护未提交的 staged fork 不会把缓存落盘失败或被丢弃时租约的rollback/Drop会清掉缓存文件不留半成品L347-L411。测试把行为钉在哪最能说明问题的一处断言在 config.rsfork_endpoint_revalidation_requires_authority_or_fallbacks构造匿名身份hardfork: None断言is_authoritative()为 false 且不要求主端点复验。它锁住的是hardfork 上报决定权威性这条规则——将来谁把权威判定改成别的依据比如instance_id是否存在这个测试会先红。缓存租约的行为则由 mem/mod.rs 的staged_fork_cache_lease_waits_for_last_owner锁定数据库还有别的用户时rollback不清理避免与对方后续的缓存 flush 竞争最后一个 owner drop 时才真正清掉 staged DB 并删除缓存文件同目录的兄弟文件则原样保留。这个测试钉住的正是谁负责清理、何时清理这条很容易写歪的边界。改动前后对照面向改动前改动后端点同一性判断URL 字符串相等ForkEndpointIdentity多字段比对探测失败处理一律按能力不可用吞掉识别出 Anvil 后按错误严格返回setRpcUrl 换源沿用旧端点的链 ID 提示清空提示重解析真实身份并比对上下文reset 切换直接构建后写入staged 构建 → 多重校验 → 一次提交身份变化后的缓存同 URL 可能继续复用新旧命名空间一并失效DB 重置三句话收束Anvil 不再用 URL 回答对端还是不是原来那个对端而是用对端实际上报的执行上下文回答reset 与换 URL 两条路径都是先验证、后提交失败时不存在任何中间态身份真正变化时磁盘缓存随之原子作废下一次 fork 拿到的必然是新端点的真实状态。整个实现集中在 crates/anvil/ 模块内围绕 fork 后端与节点配置展开。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进