開発者へ / JSON-RPC

履歴と保持

ノードはアーカイブではなく RPC のインデックスです。保持するのはおよそ 2 分ぶんのブロックで、それを越えたクエリはエラーではなく null を返します。このページがインデクサーを書いた後ではなく前に存在する理由がそれです。

ノードが保持するもの

データ保持
EVM block history~2 minuteskeep_evm_blocks() = 120,000 / EVM_BLOCK_MS - 480 blocks at the default. Older blocks return null and their logs become unqueryable
Mini-block history20,000KEEP_MINI_BLOCKS, roughly 200 seconds at the 10 ms scheduling target
Receipts and bodies200,000MAX_RECEIPTS, evicted first-in-first-out
Full historynot on the nodeit lives in Postgres behind the explorer, fed by the sequencer's spool. This RPC is an index, not an archive

ブロックのウィンドウは固定ではなく導出されます。keep_evm_blocks() = 120_000 / EVM_BLOCK_MS であり、既定のカデンスでは 480 ブロックです。ログはそのブロックとともに追い出されます。レシートとトランザクション本体は 独自の先入れ先出しの上限を持ち、そのブロックより先に消えることがあります。

読み取りは公開された copy-on-write のスナップショットから提供され、実行ロックを決して 取りません。だから遅い eth_call はブロック生成を止められず、止まったブロックは 読み取りを止められません。ウィンドウを受け入れ可能にしているのはこの設計です。ノードは 現在を速く提供することに最適化し、履歴はそのために作られたものに委ねます。

これがどう失敗するか

ウィンドウの外ではエラーではなく null が返ります

eth_getTransactionReceipt、eth_getTransactionByHash、そして すべてのブロック検索は、追い出されたものに対して null を返します。 「一度も存在しなかった」と「存在したが削除された」を区別するものは何もありません。

正しく見えて正しくない三つのやり方。

  • 長いバックオフでレシートをポーリングする。試行の間隔を十分に長くとれば、 二回のポーリングの間にレシートが追い出されることがあります。送信からおよそ 10 ミリ秒の スケジューリング目標のうちに存在するので、辛抱強くではなく、早く何度も問い合わせて ください。
  • ブロック 0 からバックフィルする。earliest は最も古い保持されているブロックであり、作業している間にも前へ進みます。それを追いかける バックフィルのループは、固定された開始点に決して届きません。
  • 台帳を毎晩照合する。夜間のジョブが走るころには、それが欲しかった ブロックはすべて消えています。

代わりに何をするか

完全な履歴は存在します。シーケンサーからエクスプローラーの背後にある Postgres へ流し込まれて おり、そちらはアーカイブであるように作られています。したがって。

  • 現在について、つまり直近の数秒については、ノードを使ってください。 サブスクリプションと狭い範囲の eth_getLogs がノードの速い部分です。
  • 過去については、エクスプローラーを読んでください。debug 名前空間がないためノードがまったく公開しない呼び出しトレースも、そちらにはあります。
  • 自分の履歴を保つには、後から問い合わせるのではなく、購読してイベントが 届くそばから永続化してください。ミニブロックのストリームが最も忠実な情報源です。遅い ハンドラーはそこから切り離されるので、キューへ押し出して処理は別の場所で行ってください。
javascript
// Right: capture as it happens, persist immediately.
socket.send(JSON.stringify({
  jsonrpc: "2.0", id: 1, method: "eth_subscribe",
  params: ["logs", { address: MY_CONTRACT }],
}));

// Wrong on this chain: a range that has already scrolled out of the window
// returns an empty array rather than an error, so this looks like "no events".
await client.getLogs({ address: MY_CONTRACT, fromBlock: 0n, toBlock: "latest" });

そしてイベントストアを (blockNumber, logIndex) でキーにするなら、 トランザクションハッシュも含めてください。ここでの logIndex はトランザクションごとに採番されるので、その組は 一意ではありません。

再起動をまたぐレシート

状態は再起動を生き延びます。レシートのインデックスは生き延びません

レシートとログのインデックスはメモリ上に保持されます。受理された送金のレシートが シーケンサーの再起動後に消え、残高の変化のほうは正しく生き延びた、という観測があります。 つまりレシートは何かの永続的な記録ではありません。何が起きたかの記録としては エクスプローラーを、ノードのレシートは速くて消えやすい受領通知として扱ってください。