尧图精选

EIP-6122 详解:基于时间戳的 Forkid 检查——合并后以太坊节点的链兼容性识别机制

🕒 发布时间:2026/9/15 14:50:11 📁 来源:尧图网络
EIP-6122 详解基于时间戳的 Forkid 检查——合并后以太坊节点的链兼容性识别机制【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs导读本指南围绕 Ethereum Improvement Proposal 仓库中的 EIP-6122 展开系统讲解以太坊合并The Merge之后节点 P2P 网络中forkid校验如何从按区块号调度分叉演进为按时间戳调度分叉的完整方案。读完本文你将掌握FORK_HASH/FORK_NEXT的新计算规则、时间戳与区块号混合校验的附加约束、向后兼容影响以及可直接用于测试的主网完整测试向量。背景从 EIP-2124 到 EIP-6122以太坊 P2P 网络中存在大量公链、私链和测试网络发现协议本身无法区分它们。为了快速判断一个远程节点是否有用同一创世、同一组分叉EIP-2124 提出了一种基于链配置的节点身份标识方案——fork identifier分叉标识符。EIP-2124 的核心是让每个节点维护两个值FORK_HASH对创世哈希与所有已通过的按区块号分叉做 IEEE CRC32 校验得到的 4 字节校验和FORK_NEXT下一个即将到来的分叉的区块号若无已知分叉则为0。分叉标识符定义为RLP([FORK_HASH, FORK_NEXT])可通过 ENR参见 EIP-778或eth/6x握手在节点间传播使节点能够快速切断不兼容节点提升整个 P2P 网络的可靠性。然而合并之后情况发生了根本变化权益证明PoS共识层按 slot 号调度分叉而 slot 是基于时间的度量。为了让执行层与共识层在同一时刻完成升级例如上海/Shapella执行层被迫在合并后也按时间戳调度分叉。EIP-6122 正是为此而生它将forkid的计算与校验从仅支持区块号升级为同时支持区块号与时间戳。动机时间驱动的分叉调度在 PoW 时代分叉按区块号调度而在 PoS 时代共识层按 slot 号时间度量调度分叉。为了在执行层与共识层同一时间调度分叉执行层在合并后必须同样按时间戳调度分叉。forkid计算让节点能够快速确定对端节点的配置并断开配置错误或针对其他网络的节点。这一能力在时间驱动分叉时代依然不可或缺只是需要让FORK_HASH的计算同时涵盖两种调度单位。从仓库中的配套 EIP 可以印证这一演进脉络EIP-6953 系统梳理了以太坊历史上的升级激活触发器PoW 时代按区块号Frontier1、Homestead1150000、London12965000等合并后执行层改为按时间戳其中Shanghai 在 EL 侧的激活时间戳为1681338455与 CL 侧 Capella 的 epoch194048恰好同时。该 EIP 明确指出执行层使用时间戳导致了节点的FORK_HASH与FORK_NEXT值计算方式发生变化这些变化由 EIP-6122 描述。EIP-7568 则记录了 Shapella 是首个在执行层与共识层同时激活的升级执行层升级激活机制改为使用时间戳详见 EIP-6953 与 EIP-6122。规范时间戳化的FORK_HASH与FORK_NEXTEIP-6122 规定每个节点维护以下值FORK_HASHIEEE CRC32 校验和[4]byte由创世哈希与已通过的 fork 区块号或时间戳计算得出已通过的 fork 区块号或时间戳按升序依次喂入 CRC32 校验和如果多个 fork 在同一区块或同一时间激活该区块号或时间戳只参与一次校验区块号视为uint64整数校验时采用大端big endian编码区块时间戳同样视为uint64整数校验时采用大端编码如果某条链在创世时就以非 Frontier 规则启动这不算作分叉不参与计算。FORK_NEXT下一个即将到来的 fork 的区块号或时间戳uint64若无已知的下一个 fork 则为0。值得注意的是对于FORK_NEXT无需区分其是时间戳还是区块号——它只是下一个分叉何时到来的一个标量。计算示例EIP-6122 给出了一个时间戳分叉的计算样例。假设在 Homestead 之上存在一个时间戳为1668000000的分叉forkhash CRC32(genesis-hash || uint64(1150000) || uint64(1668000000))其计算结果为0xcb37b2eehomestead 虚构分叉。对照 EIP-2124 中纯区块号时代的计算方式forkhash₀ 0xfc64ec04GenesisCRC32(genesis-hash)forkhash₁ 0x97c2c34cHomesteadCRC32(genesis-hash || uint64(1150000))可以看到新的计算只是把按区块号的分叉与按时间戳的分叉统一按升序串联进 CRC32 输入流本质上是对 EIP-2124 机制的平滑扩展。附加规则EIP-6122 明确了以下两条强制规则按时间戳调度的分叉必须安排在按区块号调度的分叉之后主网及私有网络均适用。这是由以太坊历史决定的所有基于区块号的分叉都先于基于时间戳的分叉发生。远程节点 forkid 的验证实现必须先按区块过滤再按时间戳过滤。原理Rationale上海升级将按时间戳调度因此forkid计算必须更新以同时支持时间戳与区块号。由于所有基于区块号的分叉都早于基于时间戳的分叉节点需要先检查基于区块号的分叉再检查基于时间戳的分叉——这就是附加规则 2 的由来。向后兼容性本变更对forkid计算做了轻微修改。其后果是一旦时间戳调度的分叉发生应用本变更的节点将断开未应用本变更的节点。这不仅是预期行为而且正是forkid存在的初衷让已升级节点在分叉边界立即拒绝未升级节点避免陈旧节点stale nodes在网络上占用宝贵的对等连接与带宽资源。测试用例主网全量验证向量EIP-6122 提供了一套完整测试向量基于主网配置在时间戳1668000000启用 withdrawals取款并以区块18000000作为合并 netsplit 分界。测试一主网各高度下的 forkid 演进以下 Go 测试逐一枚举了从未同步到未来 Shanghai 区块的主网FORK_HASH/FORK_NEXT取值type testcase struct { head uint64 want ID } tests : []struct { config *params.ChainConfig genesis common.Hash cases []testcase }{ // Withdrawal test cases withdrawalConfig, params.MainnetGenesisHash, []testcase{ {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block {1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block {1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block {1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block {2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block {2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block {2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block {2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block {4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block {4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block {7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block {7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block {9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block {9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block {9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block {9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block {12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block {12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block {12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block {12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block {13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block {13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block {15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block {15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 18000000}}, // First Gray Glacier block {18000000, 0, ID{Hash: checksumToBytes(0x4fb8a872), Next: 1668000000}}, // First Merge Start block {20000000, 0, ID{Hash: checksumToBytes(0x4fb8a872), Next: 1668000000}}, // Last Merge Start block {20000000, 1668000000, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}}, // First Shanghai block {20100000, 2669000000, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}}, // Future Shanghai block }, }解读这份测试向量可以看到 EIP-6122 对主网 forkid 演进的完整刻画区块号分叉阶段合并前从0xfc64ec04Frontier一路演变为0x4fb8a872Merge Start即合并 netsplit 区块18000000。这一阶段FORK_NEXT始终是下一个区块号分叉。时间戳分叉阶段合并后在高度20000000、时间戳1668000000处进入 ShanghaiFORK_HASH变为0xc1fdf181此时FORK_NEXT归零无已知后续分叉。FORK_NEXT的单位切换合并前它是区块号如18000000合并后它变成时间戳如1668000000——正如规范所述区分FORK_NEXT是时间戳还是区块并不重要。测试二主网节点的远程 forkid 校验决策第二组测试枚举了主网节点可能处于的不同状态以及面对各种远程 forkid 时应接受nil错误还是拒绝ErrRemoteStale/ErrLocalIncompatibleOrStaletests : []struct { head uint64 id ID err error }{ /// Local is mainnet Withdrawals, remote announces the same. No future fork is announced. {20000000, 1668000001, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}, nil}, // Local is mainnet Withdrawals, remote announces the same also announces a next fork // at block/time 0xffffffff, but that is uncertain. {20000000, 1668000001, ID{Hash: checksumToBytes(0xc1fdf181), Next: math.MaxUint64}, nil}, // Local is mainnet currently in Byzantium only (so its aware of Petersburg Withdrawals), remote announces // also Byzantium, but its not yet aware of Petersburg (e.g. non updated node before the fork). // In this case we dont know if Petersburg passed yet or not. {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, // Local is mainnet currently in Byzantium only (so its aware of Petersburg Withdrawals), remote announces // also Byzantium, and its also aware of Petersburg (e.g. updated node before the fork). We // dont know if Petersburg passed yet (will pass) or not. {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, // Local is mainnet currently in Byzantium only (so its aware of Petersburg Withdrawals), remote announces // also Byzantium, and its also aware of some random fork (e.g. misconfigured Petersburg). As // neither forks passed at neither nodes, they may mismatch, but we still connect for now. {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil}, // Local is mainnet exactly on Withdrawals, remote announces Byzantium knowledge about Petersburg. Remote // is simply out of sync, accept. {20000000, 1668000000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, // Local is mainnet Withdrawals, remote announces Byzantium knowledge about Petersburg. Remote // is simply out of sync, accept. {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, // Local is mainnet Withdrawals, remote announces Spurious knowledge about Byzantium. Remote // is definitely out of sync. It may or may not need the Petersburg update, we dont know yet. {20000000, 1668000001, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil}, // Local is mainnet Byzantium pre-withdrawals, remote announces Petersburg. Local is out of sync, accept. {7279999, 1667999999, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil}, // Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local // out of sync. Local also knows about a future fork, but that is uncertain yet. {4369999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, // Local is mainnet Withdrawals. remote announces Byzantium but is not aware of further forks. // Remote needs software update. {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale}, // Local is mainnet Withdrawals, and isnt aware of more forks. Remote announces Petersburg // 0xffffffff. Local needs software update, reject. {20000000, 1668000001, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, // Local is mainnet Withdrawals, and is aware of Petersburg. Remote announces Petersburg // 0xffffffff. Local needs software update, reject. {20000000, 1668000001, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, // Local is mainnet Withdrawals, remote is Rinkeby Petersburg. {20000000, 1668000001, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, // Local is mainnet Withdrawals, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). {88888888, 1668000001, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrRemoteStale}, // Local is mainnet Withdrawals. Remote is in Byzantium, but announces Gopherium (non existing // fork) at block 7279999, before Petersburg. Local is incompatible. {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrRemoteStale}, }这些用例体现了 EIP-6122 校验逻辑的几个关键判定原则FORK_HASH相同但FORK_NEXT不同属于未来分叉尚不确定的范畴通常接受连接规则 1b远程FORK_HASH是本地方叉历史的子集远程节点只是不同步out of sync接受连接规则 2远程FORK_HASH超出本地认知如远程声明了一个本地不存在的Gopherium分叉根据具体场景返回ErrRemoteStale远程软件陈旧或ErrLocalIncompatibleOrStale本地不兼容或陈旧拒绝连接规则 4不同网络如 Rinkeby 的0xafec6b27对主网节点直接拒绝。注意最后一组用例还展示了区块号分叉与时间戳分叉在FORK_NEXT字段中的混用Next: 1668000001是时间戳而Next: 88888888、Next: 7279999是区块号进一步印证了规范中FORK_NEXT无需区分时间戳与区块的设计。跨 EIP 的联动forkid 的下游应用EIP-6122 定义的新版FORK_HASH计算方式不仅是节点内部校验逻辑还被后续标准直接引用EIP-6953网络升级激活触发器明确将FORK_HASH/FORK_NEXT计算方式的变更归因于 EIP-6122EIP-7910eth_configJSON-RPC 方法在定义配置对象成员forkId时明确要求其取值按 EIP-6122 规定的特定分叉的FORK_HASH值以无符号0x前缀十六进制、四字节左补零、小写形式呈现。该 RPC 方法面向节点运营者、验证者团队与网络监控工具用于在硬分叉前验证客户端配置是否正确——而正确的forkId正是 EIP-6122 计算规则的直接产物。也就是说EIP-6122 不仅保证了合并后 P2P 网络中节点的快速甄别也为运维侧分叉前配置核验提供了标准化的数据基础。安全考虑EIP-6122 声明目前没有已知的安全风险。从机制本身看FORK_HASH是 CRC32 而非密码学哈希但正如 EIP-2124 所述由于节点可能在任何时候说谎密码学哈希在这里没有额外价值CRC32 足以将任意数据压成 4 字节且不遗漏任何输入且被以太坊、gzip、zip、PNG 等广泛采用各语言实现成熟。总结EIP-6122 是合并后以太坊节点身份标识机制的关键更新计算规则扩展FORK_HASH的 CRC32 输入流统一支持区块号 时间戳混合序列均按uint64大端编码、升序喂入同一区块/时间点的多重分叉只校验一次。FORK_NEXT统一化下一个分叉既可以是区块号也可以是时间戳无需区分。调度约束时间戳分叉必须晚于区块号分叉验证时先按区块过滤、再按时间戳过滤。向后兼容时间戳分叉生效后未升级节点会被自动断开——这正是forkid设计的初衷。配套的主网测试向量从 Frontier 到 Shanghai既可作为客户端实现的回归测试基准也可作为理解主网 forkid 演进的权威参考。延伸阅读EIP-2124: Fork identifier for chain compatibility checks —— forkid 机制的原始定义与校验规则EIP-6953: Network Upgrade Activation Triggers —— 各时代升级激活触发方式全览EIP-7568: Hardening Shanghai —— Shapella 首次实现 EL/CL 同时激活的升级记录EIP-7910: eth_config JSON-RPC Method —— 消费 EIP-6122FORK_HASH的 RPC 接口LICENSE —— 本文档版权声明CC0【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →