Ethereum RPCのlatestがずれる理由:fixed block・archive・Multicall
Ethereum RPCのpending・latest・safe・finalized、block number/hash、archive state、JSON-RPC batch、Multicallを比較。同一blockで価格・残高・slot・logsを再現します。

Ethereum RPCで同じaddressを読んだはずなのに、価格、残高、allowance、proxy slot、eventが噛み合わない。最初に確認するのはprovider名ではなく、すべてのreadが同じblockを参照したかです。
latestは固定snapshotではありません。1回目のeth_callと2回目のeth_getLogsの間にheadが進めば、どちらも正しい応答でも、組み合わせた結果はchain上の一つの時点に存在しません。
対策は、目的に合うsafeまたはfinalizedを一度取得し、そのblock numberとhashを保存してから全methodへ渡すことです。JSON-RPC batchやMulticallはrequestをまとめますが、block指定の代わりにはなりません。
この記事ではlocal Anvilでmoving latestの失敗を再現し、Ethereum mainnet block 25,869,830へ価格・残高・allowance・raw storage・logsを固定します。公開RPCの観測は2026-08-31、Japan region、public unauthenticated planです。wallet、API key、署名、transaction送信、資産操作は使いません。
pending・latest・safe・finalizedは位置が違う
Ethereum JSON-RPCのblock parameterは、数量で指定するblock numberのほか、pending、latest、safe、finalizedなどのtagを受け取ります。
| identifier | 何を選ぶか | 主な用途 | 注意点 |
|---|---|---|---|
pending |
次のblock候補に対応するpending state | mempool反映後のシミュレーション | 内容が変わる。hashがnullの実装もある |
latest |
nodeが現在canonical headとみなすblock | 最新表示、低遅延監視 | callごとに進み、近いreorgの影響を受ける |
safe |
consensus layerがsafeとするhead | latestより強い基準の表示 | method・providerの対応差を確認する |
finalized |
finalized checkpointに対応するhead | 再現用blockの選択、会計・記事data | latestより古い。極端なconsensus failureまでの絶対保証ではない |
| block number | その高さのblock | 複数methodを同じ高さへ揃える | non-finalizedではreorg後に別hashへ対応し得る |
| block hash | そのblock identity | 厳密な再現、canonicality確認 | methodがEIP-1898へ対応するか確認する |
2026-08-31 03:46 JST前後に3つのpublic endpointを確認したsnapshotでは、pending 25,869,973、latest 25,869,972、safe 25,869,926、finalized 25,869,894でした。2 endpointはpending blockのhashをnull、1 endpointは暫定hashを返しています。
これはprovider優劣の表ではありません。同じ時刻帯でもtagが指す位置と返却fieldが異なり得るため、取得日時、endpoint、plan、region、number、hashを一緒に保存する例です。
finalizedは強いが「何があっても不可逆」ではない
Ethereum proof-of-stakeでは、validatorのsupermajority voteによりcheckpointがfinalizedされます。通常の短いreorgと比べて、finalized blockを覆すには大規模なconsensus failureやslash対象となる振る舞いが必要です。
したがって再現用snapshotにはfinalizedが扱いやすい一方、「数学的に絶対不可逆」「providerがfinalizedと返せば必ず正しい」とは書けません。別endpointでnumberとhashを照合し、観測条件を残します。
blockを一度解決して全readへ渡す
viemでは、最初にfinalized blockを一度だけ取得します。その後はblockTagを繰り返し使わず、解決済みのblock.numberを全readへ渡します。
const block = await client.getBlock({ blockTag: 'finalized' })
if (block.number === null || block.hash === null) {
throw new Error('finalized block identity is unavailable')
}
const at = block.number
const price = await client.readContract({
address: feed,
abi: feedAbi,
functionName: 'latestRoundData',
blockNumber: at,
})
const balance = await client.readContract({
address: token,
abi: erc20Abi,
functionName: 'balanceOf',
args: [holder],
blockNumber: at,
})
const implementation = await client.getStorageAt({
address: proxy,
slot: implementationSlot,
blockNumber: at,
})
const logs = await client.getLogs({
address: token,
fromBlock: at,
toBlock: at,
})
block numberを使う利点は、viemの各actionへ同じ形で渡しやすいことです。同時にhashも保存し、別providerから取得したblock headerが同じhashか照合します。
proxyのraw slotを読む手順はERC-1967 proxyをblockへ固定して調べる記事、transaction・receipt・eventのerror層を先に分ける場合はtransaction失敗を切り分ける順番も参照してください。
EIP-1898でblock hashとcanonicalityを指定する
EIP-1898は、block numberの代わりにblockHashを含むobjectを渡せる形を定義しています。requireCanonical: trueを付けると、そのhashがcanonical chain上にない場合をerrorとして扱えます。
const raw = await client.request({
method: 'eth_call',
params: [
{ to: feed, data: latestRoundDataCalldata },
{ blockHash: block.hash, requireCanonical: true },
],
})
EIP-1898の対象にはeth_getBalance、eth_getStorageAt、eth_getTransactionCount、eth_getCode、eth_call、eth_getProofがあります。blockNumberとblockHashを同じobjectへ同時に入れず、requireCanonicalはhashと一緒に使います。
今回のfixed block hashは0x53d1375a96ad48b9a0f3e30152c0f01dc142fca8dce2f22ee95270a27b365294です。public endpointとAnvilの両方で、blockHash + requireCanonical: trueのeth_callがnumber固定と同じ値を返しました。
ただし、gatewayがEIP-1898を実装していない場合があります。その場合はnumberと取得済みheader hashを別々に照合し、unsupported errorも隠さず記録します。hash指定はarchive stateを生成しないため、古いstateがprune済みならやはり失敗します。
historical eth_callにはstate historyが必要
古いblock headerやtransactionを取得できることと、その時点のcontract stateでeth_callできることは別です。historical callには、対象blockのstateを再構成できるexecution clientまたはarchive serviceが必要です。
Gethのcurrent archive documentationでは、path-based state historyを--history.stateで設定します。現在のcommand-line referenceはdefaultを90,000 blocksとし、0は全historyを要求する値です。設定、sync方式、client version、provider planによって利用可能な深さは変わります。
同じendpointでcurrentは成功、old blockは失敗した
Chainlink ETH/USD feedのlatestRoundData()を、latestとblock 16,000,000で同じpublic endpointへ送りました。
| request | observed result at 2026-08-31 |
|---|---|
eth_call(..., "latest") |
HTTP 200、decoded resultを取得 |
eth_call(..., "0xf42400") |
HTTP 403 / RPC code -32602 |
| error | Archive requests require a personal token |
同じhistorical callは、authoring時点の別のpublic unauthenticated endpointで成功し、ETH/USD answer 120872000000、decimals 8、updatedAt 2022-11-18T21:55:35Zを返しました。
ここから言えるのは、block 16,000,000のstate自体は取得可能だが、最初のpublic planではarchive requestとして拒否されたことです。失敗endpointがarchive非対応のnodeである、別endpointが永久に全historyを保証する、とまでは断定できません。plan gate、retention、routing、rate limitは後から変わり得ます。
sequential・JSON-RPC batch・Multicallの違い
3方式は「何をまとめるか」が違います。
| method | HTTP / RPC形状 | 同一blockになる条件 | response / failure | 対象外 |
|---|---|---|---|---|
| sequential | callごとに1 request | 全callへ同じnumber/hashを渡す | call単位で順番に処理 | request途中でlatestが動く |
| JSON-RPC batch | 複数RPC itemを1 HTTP bodyへ入れる | 各itemへ同じnumber/hashを渡す | orderは保証されずIDで照合 | atomic executionの保証 |
| Multicall3 | 1回のeth_call内で複数contract call |
outer eth_callへblockを指定 |
callごとのallowFailureを設計 | logs、raw storage、archive state供給 |
JSON-RPC 2.0 specificationでは、batch内のrequestをserverが並列または任意順で処理でき、response orderもrequest orderと一致する必要はありません。配列位置ではなくidで結合します。
viemのmulticall actionはMulticall3へ複数のcontract readを渡し、1回のRPC callへまとめます。同じouter eth_callなので、contract側のreadは一つのEVM block contextを共有します。blockNumberを省略すれば、その1回が実行された時点のlatestです。後日同じ値を再現したいならexplicit blockを残します。
batchだけではatomic snapshotにならない
次のbatchは1 HTTP requestですが、各itemがlatestを指定しています。serverは独立して処理できるため、head境界をまたぐ可能性を仕様上排除できません。
[
{"jsonrpc":"2.0","id":4105,"method":"eth_call","params":[{"to":"0x…","data":"0x…"},"latest"]},
{"jsonrpc":"2.0","id":4101,"method":"eth_call","params":[{"to":"0x…","data":"0x…"},"latest"]}
]
latestを両方とも0x18abe06へ変えれば、処理順やresponse順にかかわらず同じblock stateを要求できます。batchがsnapshotを作るのではなく、各itemのblock pinがsnapshotを作ります。
Multicallに入らないstateもある
Multicall3はEVM contract callをまとめます。balanceOf、allowance、oracle getter、複数contractのview functionには向きます。
一方、eth_getStorageAtはnodeのRPC method、eth_getLogsはreceipt log indexからの検索です。Multicall contractから同じ形で取得できません。これらは別requestのまま同じblock numberまたはhashへ固定し、結果を後から結合します。
また、Multicallを古いblockへ指定しても、providerがそのhistorical stateを持たなければouter eth_callが失敗します。Multicallはarchive nodeの代替ではありません。
Anvilでmoving latestのずれを再現する
local再現環境はFoundry / Anvil 1.8.0、Solidity 0.8.36、viem 2.56.0、Prague EVM、chain ID 31337へ固定しました。Foundryは3 passed; 0 failed; 0 skippedです。
最初にblock 3で4種類のstateを更新し、price()をlatestで読みます。その後、別transactionでblock 4へ進めてからbalanceOf()をlatestで読みました。
| read | block | value |
|---|---|---|
first price() at latest |
3 | 250,000,000,000 |
| state update transaction | 4 | price / balance / allowance / raw slotを更新 |
second balanceOf() at latest |
4 | 1,400,000 |
2つのresponseは個別には正しいですが、price from block 3 + balance from block 4というpairは一つのsnapshotではありません。
次に全callをblock 3へ固定しました。
| state | expected | sequential | JSON-RPC batch | Multicall |
|---|---|---|---|---|
| price | 250,000,000,000 | 250,000,000,000 | 250,000,000,000 | 250,000,000,000 |
| balance | 1,250,000 | 1,250,000 | 1,250,000 | 1,250,000 |
| allowance | 7 | 7 | 7 | 7 |
| raw value | 41 | 41 | 41 | 41 |
batchのrequest ID orderは3003, 3001, 3004, 3002です。再現scriptはresponse配列を意図的に逆順へ並べたnormalization probeも通し、IDで結合して同じ4値になることを検査します。
同じblock 3のslot 0は0x…0029、decoded valueは41です。SnapshotUpdated eventのblock hashもblock 3と一致し、EIP-1898 hash-pinned callも41を返しました。local再現JSONにblock、raw slot、event、request orderを保存しています。
mainnetで5種類のstateを同じblockへ固定した
公開計測はEthereum mainnet block 25,869,830、hex 0x18abe06、timestamp 2026-08-30T18:17:59Zを使いました。authoring時点のfinalized headより64 blocks古く、2つのstate-capable public endpointでblock hashと全contract readを照合しています。
| state type | target / method | fixed-block result |
|---|---|---|
| oracle price | Chainlink ETH/USD latestRoundData() |
250019228748 / decimals 8 = 2,500.19228748 USD |
| token balance | USDC balanceOf(OP L1StandardBridge) |
25313978911659 / decimals 6 = 25,313,978.911659 USDC |
| token allowance | USDC allowance(bridge, portal) |
0 |
| raw proxy storage | bridge EIP-1967 implementation slot | 0xB37a11AadF167B2F0b8dD85372De4bC66CD4A891 |
| event logs | USDC Transfer in the fixed block |
14 logs |
Chainlink roundのupdatedAtは2026-08-30T17:26:11Zです。block timestampより前なのは正常です。oracleが各blockで必ず更新されるのではなく、そのblock時点でcontractが保持していた最新roundを読んでいるためです。
contract getter 6件は、sequential 6 HTTP requests、JSON-RPC batch 1 HTTP request、Multicall3 1 eth_callの3方式ですべて一致しました。別endpointでも同じ6値です。
raw implementation slotと14件のlogsは別RPCですが、同じblock numberを使用しています。14 logsの正規化JSONはSHA-256 b2aa6df269ad18cc0d587f2ac82c1758bada2beaeb550a9a82557c8ec77e839aです。公開計測JSONには全raw log、raw eth_call、batch ID order、EIP-1898 result、archive success / failureを残しました。
USDC addressはCircle公式、Chainlink feedは公式quickstart、OP Mainnet bridge / portalはSuperchain Registryの固定commitから確認しています。addressとABIを推測してprovider比較へ混ぜないためです。
計測scriptを再実行する
defaultはnetworkへ接続せず、保存済みJSONのblock、3方式の値、raw slot、logs、archive境界、endpoint安全条件を検査します。
node scripts/fetch-ethereum-rpc-snapshot.mjs
public endpointへread-onlyで再照合する場合だけ--fetchを付けます。固定block stateがcommitted evidenceと一致するか検査し、動くtagとarchive observationは現在値として別に出力します。
node scripts/fetch-ethereum-rpc-snapshot.mjs --fetch
scriptはHTTPS、credentialなし、query parameterなし、allowlist済みhostnameだけを受け付けます。環境変数でendpointを差し替える場合も同じ条件です。authorization header、wallet、private key、署名、transactionは扱いません。
| field | committed measurement |
|---|---|
| checkedAt | 2026-08-30T18:46:35.912Z |
| network / chain ID | Ethereum mainnet / 1 |
| region | JP |
| plan | public unauthenticated; no account or API key |
| Node / viem | Node 24 / viem 2.56.0 |
| latency ranking | 実施していない |
| fixed block | 25,869,830 / 0x53d1…5294 |
| historical probe | block 16,000,000 |
tag support、archive depth、rate limit、cache、routing、planは変わります。後日の--fetchでarchive errorが消える、別errorになる、endpoint自体が終了する可能性があります。committed JSONは執筆時のraw observation、live fetchは現在の再確認として分けてください。
provider差を調べる順番
値が違うときは、次の順に狭めます。
- chain ID、contract address、ABI、function selector、argumentsを固定する
- 各responseのblock numberとhashを記録する
latestを一度safeまたはfinalizedへ解決し、explicit blockで再実行する- number取得後に別endpointで同じhashか確認する
- batch responseを配列位置ではなくIDで結合する
- Multicallのouter block、contractごとのsuccess、allowFailureを記録する
- historical failureのHTTP status、RPC code、message、plan、region、timestampを残す
- RPC node、indexer、cache、Explorer APIのどの層を読んだか分ける
latestでだけ違い、fixed blockで一致するならmoving headまたは近いreorgが第一候補です。fixed blockでも一方だけ古いcallに失敗するならstate historyまたはplan boundaryを調べます。同じblock hashでraw RPCは一致するのにExplorer集計だけ遅れるなら、indexer lagやcacheを別層として確認します。
blob dataのようにexecution stateとは別のretention境界を持つ証拠は、Fusaka・PeerDASとblob feeの固定block検証で扱っています。execution RPCがtransaction hashを返しても、別APIのhistorical dataが永久に残るとは限りません。
同じ考え方でOP StackのPortal stateやArbitrumのOutbox stateを確認する場合は、L2出金をprove・challenge・finalize・claimへ分ける手順を参照してください。ERC-20のraw balanceOfをdecimalsとwallet表示へ対応させる場合は、tokenが表示されない時の確認順へ進みます。latestのcontract値と過去receipt timestampを、同じ時点の保証として混ぜないことが重要です。
Ethereum RPC snapshot確認チェックリスト
- chain ID、address、ABI、argumentsを固定した
- pending / latest / safe / finalizedのどれを選んだか記録した
- tagを一度numberとhashへ解決した
- contract reads、raw storage、logsへ同じblockを渡した
- number固定とhash / canonicality確認を分けた
- EIP-1898 unsupported errorも保存した
- sequential / batch / Multicallを同じblock条件で比較した
- batch responseをIDで対応させた
- Multicallへ入らないlogsとraw storageを別RPCで固定した
- historical stateに必要なretention / archive / planを確認した
- endpoint、region、plan、timestamp、HTTP status、RPC errorを残した
- public endpointの一回の観測を永久仕様や性能順位にしていない
結論は、RPC snapshotは先にblock numberとhashを一度解決し、price、balance、allowance、raw storage、logsの全readを同じblockへ対応させることです。sequential、batch、Multicallはrequest形状を選ぶ道具で、snapshot identityやarchive availabilityは別に設計します。
この記事にスポンサー、affiliate、wallet接続、署名要求、transaction送信、資産操作のCTAはありません。将来RPC、node hosting、monitoring、data infrastructureの広告を置く場合も広告であることを明示し、測定method、raw result、provider observation、technical conclusionから分離します。
endpoint差の原因が429、timeout、JSON-RPC code、node policy、transaction validation、EVMのどこにあるかを先に分ける場合は、Ethereum RPCエラーの層別診断でraw responseとviemのcause chainを照合してください。
確認した一次情報
- Ethereum JSON-RPC API確認日: 2026/08/31
- Ethereum proof-of-stake finality確認日: 2026/08/31
- EIP-1898 blockHash in JSON-RPC確認日: 2026/08/31
- JSON-RPC 2.0 Specification確認日: 2026/08/31
- Geth archive mode確認日: 2026/08/31
- Geth command-line options確認日: 2026/08/31
- viem getBlock確認日: 2026/08/31
- viem multicall確認日: 2026/08/31
- Multicall3 source確認日: 2026/08/31
- Circle USDC contract addresses確認日: 2026/08/31
- Chainlink historical prices quickstart確認日: 2026/08/31
- Superchain Registry OP Mainnet configuration確認日: 2026/08/31
- Foundry v1.8.0確認日: 2026/08/31



