查历史状态为什么建议用区块哈希而不只是区块高度:EIP-1898 的参数写法 图 1
查历史状态为什么建议用区块哈希而不只是区块高度:EIP-1898 的参数写法 · 图 1

用 RPC 查某个历史时点的余额、代码或存储值,多数人的写法是给一个区块高度——"0x1234abc" 这样的十六进制数字,或者 latestfinalized 这类标签。平时没问题,但重组发生时”高度”是个有歧义的坐标:同一个高度在分叉期间可以同时对应规范链区块和被丢弃的孤块,你查到的余额可能是另一条时间线上的。EIP-1898 给这类查询加了一个更精确的坐标:区块哈希。它是 Final 状态的接口类提案,写进了主流 JSON-RPC 规范。

区块参数从一种写法变成两种写法

规范里原本允许的区块参数有这些:十六进制区块高度、earliestlatestpendingsafefinalized。EIP-1898 在这之外允许传一个对象:要么带 blockNumber 字段,要么带 blockHash 字段(32 字节哈希),两种字段表达的是同一件事,只是后者把查询钉死在一个具体区块上。之所以要引入对象形式,是因为 JSON-RPC 里很难区分”一串十六进制”到底是高度还是哈希,干脆用键名把意图写明白。提案还特别说明可以按块哈希查规范链之外的区块——这正是它的核心价值:只要节点存有那个区块,即使它已经不在规范链上,查询也能明确指向它。

受影响的方法有六个:eth_getBalanceeth_getStorageAteth_getTransactionCounteth_getCodeeth_calleth_getProof。它们共同的特征是最多带一个区块参数,改造成本低。

requireCanonical:要不要”只认规范链”

对象参数里还可以多带一个布尔字段 requireCanonical,默认是 false。不开它时,节点只要找得到这个块就回答,哪怕它其实是孤块;开了它,节点还必须确认该区块在规范链上,否则报错。两种报错的语义是分开的:区块压根找不到时抛”资源未找到”(建议的错误码 -32001),区块存在但不在规范链上时抛”输入无效”(建议 -32000),调用方据此能区分”我记错了哈希”和”这个块被重组掉了”这两种完全不同的情况。对账程序尤其应该开这个开关,或者自己拿到结果后再验证哈希归属,免得把一个孤块上的状态当成正式账本记进系统。同一主题下区块标签视角的补充读物见 Latest、Safe、Finalized:同一个查询读三条链尖有什么不同

什么时候真正用得上

三个场景值得记住。第一,重组窗口内的对账:你的系统按高度 A 记了一笔余额,随后发生重组,高度 A 在新区块链上换了内容。用区块哈希重查一遍原区块,就能分辨”记录错误”还是”链本身换轨”,两个浏览器显示不一致的成因分析见 同一笔交易两个浏览器确认数不同?链尖快照、索引延迟与节点同步。第二,取证与审计:要证明”某区块当时就是那个状态”,按哈希查询得到的回答天然无歧义,配合 eth_getProof 还能进一步做值证明。第三,跨节点核对:你从公共服务读的 latest 与自建节点的 latest 可能差着一两个块,用同一个区块哈希向两边提问,比对结果才有意义。

边界:哈希钉得准,不代表每个节点都答得出

按哈希查历史区块对节点存了什么有要求:全节点的历史数据保留策略不同,有的只留最近一段状态,更老的区块状态需要归档节点或远端历史存储才能回答;普通查询公共 RPC 也不保证开放所有区块参数写法。查询前先分清是参数写法不被支持(方法报错、对象被拒),还是节点确实没有那个区块的数据(报资源未找到),两类错误的处置路径完全不同。

把写法变成肌肉记忆

给脚本作者的检查单:凡是要落库的历史查询,优先把区块坐标同时记成”高度+哈希”一对;能传对象参数的接口就不要用裸高度;对安全性敏感的比对开 requireCanonical 或事后核验;拿到 -32001-32000 分开处理。这些习惯在你没遇到重组时看不出价值,遇到一次就值回全部成本。

本文仅讲解公开接口用法,不构成投资建议。