cloud-sre
Erigon Mainnet アーカイブノードが 25393069 で止まったとき、chaindata だけを消して戻した記録
Ethereum Mainnet の Erigon アーカイブノードで、v3.5.0 が block 25393069 の gas used mismatch で止まった。snapshots を残し、chaindata だけを作り直して復旧したときの現場メモ。
この障害は、最初は「1 ブロックだけ壊れている」ように見えた。
Ethereum Mainnet の Erigon アーカイブノードを erigontech/erigon:v3.5.0 に上げたあと、Execution stage が block 25393069 付近で毎回止まった。
一時的な peer 不足でも、ランダムな disk I/O の揺れでもない。
同じブロックで gas used mismatch が繰り返し出ていた。
最終的に効いたのは、ブロックをスキップすることでも、/data 全体を消すことでもなかった。
既存の snapshots は残し、/data/chaindata だけを削除して、Erigon に execution database を作り直させた。
その後、ノードは 25393069 を越え、RPC の高さは 25393999 まで commit され、さらに後続ブロックの実行も続いた。
用語
| 用語 | 意味 |
|---|---|
| Archive node / アーカイブノード | 過去の receipt、trace、historical state query を返すために履歴状態を保持するノード。 |
| Erigon | Ethereum execution client のひとつ。headers、bodies、senders、execution、trie、indexes などを stage に分けて同期する。 |
| Datadir | ノードのデータディレクトリ。このケースでは /data。snapshots、chaindata、logs、downloader data が入る。 |
| chaindata | Erigon の主要な execution database。削除すると execution state は作り直しになるが、snapshots 全体を消すわけではない。 |
| Snapshot | 事前に構築された chain data。これがあると、genesis から昔ながらの線形 replay をする必要が小さくなる。 |
| Gas-used mismatch | ノードがブロックを実行して計算した gas used が、block header の値と一致しない状態。execution client はそのブロックを無効として扱う。 |
| Staged sync | Erigon の同期方式。Headers、Bodies、Senders、Execution、TxLookup、Finish が別々の進捗を持つ。 |
症状
起動形態は Ethereum Mainnet の archive node として普通のものだった。
erigontech/erigon:v3.5.0
--chain=mainnet
--datadir=/data
--prune.mode=archive
datadir は古いバージョンから in-place upgrade したもの。 アップグレード後、Erigon は DB を開けるし、ローカル snapshots も見えている。 しかし execution が同じブロックを越えられなかった。
中心のエラーはこれだった。
gas used mismatch block=25393069 header=20304193 execution=20137672
[4/6 Execution] rw exit err="invalid block, block=25393069, invalid block, gas used by execution: 20137672, in header: 20304193"
[4/6 Execution] Execution failed err="invalid block, block=25393069 ..."
Cannot update chain head err="updateForkChoice: [4/6 Execution] invalid block, block=25393069 ..."
eth_syncing の stage も同じ場所で止まっていた。
Headers 25393067
Bodies 25393067
Senders 25393067
Execution 25393067
Finish 25393067
ノード全体が死んでいるわけではない。
25393069 近辺で安定して失敗していた。
なぜブロックをスキップしなかったか
execution client は、失敗したブロックを安全にスキップできない。
もし 25393069 の実行結果が header と一致しないなら、その後の state root、receipts、traces も信用できない。
archive node ではなおさら、後続の historical state や trace が前の execution state に依存している。
現実的な選択肢は二つだけだった。
- bad-block marker、index、local stage の問題だと切り分けて、その層だけを直す。
- 現在の execution database を捨て、信頼できる snapshots と block data から作り直す。
先に試した軽い修復
Erigon の image には integration tool が入っている。
まず存在を確認した。
docker exec erigon command -v integration
docker exec erigon integration --version
次に、主プロセスと integration が同時に /data を書かないように Erigon を止める。
docker stop erigon
stage の状態を確認する。
docker run --rm \
-v /data:/data \
--entrypoint integration \
erigontech/erigon:v3.5.0 \
print_stages --datadir=/data
この時点で Execution は 25393067。
ローカルの block snapshots と DB 内の headers/bodies は、その周辺より先まで存在していた。
bad-block marker を消す。
docker run --rm \
-v /data:/data \
--entrypoint integration \
erigontech/erigon:v3.5.0 \
clear_bad_blocks --datadir=/data
これは BadHeaderNumber table を clear した。
続いて senders を補う。
docker run --rm \
-v /data:/data \
--entrypoint integration \
erigontech/erigon:v3.5.0 \
stage_senders --datadir=/data --chain=mainnet --block=25393070
最後に unwind して execution を再実行した。
docker run --rm \
-v /data:/data \
--entrypoint integration \
erigontech/erigon:v3.5.0 \
stage_exec --datadir=/data --chain=mainnet --unwind=100 --block=25393070
ここでの教訓は、stage_exec は短時間で終わる修復ボタンではない、ということ。
MDBX に大量の書き込みが続き、docker stats の Block I/O は数百 GB まで増えた。
それだけで hung とは言えない。
ただ、復旧判断を急ぐ現場では、短時間で明確な結果を返す手段でもなかった。
docker run --rm で一時的な integration container を動かす場合は、終了時の情報も取り逃しやすい。
- container は exit 後すぐ消えることがある。
docker waitや log follow を先に仕込んでおくと、exit code と最後の log を残しやすい。
chaindata 削除に切り替えた理由
上流 issue には、snapshots を残して chaindata を削除すると復旧した、という報告があった [1]。
これはノード全体の削除ではない。 対象はここだけ。
/data/chaindata
触らないものは次の通り。
/data/snapshots
/data/downloader
/data/logs
snapshots が残っていれば、Erigon はローカルデータから execution state を作り直せる。
もちろんコストはある。
Execution History、trie、indexes、TxLookup は再構築される。
それでも /data 全体を消すよりはずっと小さい。
実際の手順
まず、Erigon や integration が datadir を書いていないことを確認する。
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Command}}'
ps -eo pid,stat,pcpu,pmem,etime,args | grep -E 'erigon|integration stage_exec' | grep -v grep
一時的な execution container を止める。
docker stop -t 60 <temporary-integration-container>
stage_exec が残っていないことを確認する。
ps -eo args | grep -E 'integration stage_exec' | grep -v grep
chaindata だけ削除する。
rm -rf /data/chaindata
test ! -e /data/chaindata && echo chaindata_deleted
Erigon を起動する。
docker start erigon
起動直後、RPC が一時的に 0x0 を返すことがある。
これは execution database を消した直後なので自然な状態。
stage がもう一度進む必要がある。
{
"currentBlock": "0x0",
"stages": [
{ "stage_name": "Execution", "block_number": "0x0" },
{ "stage_name": "TxLookup", "block_number": "0x0" },
{ "stage_name": "Finish", "block_number": "0x0" }
]
}
復旧中に見えたログ
最初の良いサインは、ローカル snapshots を再利用していることだった。
[1/6 OtterSync] Skipping SyncSnapshots, local preverified. Use snapshots reset to resync
次に execution history を補う。
Downloading Execution History progress=30363/36158
この間、eth_blockNumber が動かないことがある。
それだけでは失敗ではない。
その後、block insert と execution に戻った。
[BlockCollector] Inserting blocks from=25392000 to=25392999
[BlockCollector] Inserting blocks from=25393000 to=25393999
[4/6 Execution] parallel starting from=25392365 to=25393999
本当に見たいのは、元の失敗ブロックを越えたかどうか。
[4/6 Execution] parallel executed blk=25392996
[4/6 Execution] parallel executed blk=25393199
25393199 は 25393069 より大きい。
この時点で、再構築後の execution は元の失敗点を越えた。
その後、RPC stage も commit された。
eth_blockNumber = 0x1837b4f
Execution = 0x1837b4f
TxLookup = 0x1837b4f
Finish = 0x1837b4f
0x1837b4f は 25393999。
ここまで、古い gas used mismatch block=25393069 は再発していない。
再利用できる runbook
Erigon archive node が同じ execution block で繰り返し止まる場合、私はこの順序で見る。
- 正確な failure log を残す。block number、header gas、execution gas を記録する。
- 実際に動いている image と起動引数を確認する。Terraform や source tree の期待値だけを見ない。
eth_syncingとintegration print_stagesで、どの stage が止まっているかを見る。- bad-block marker が疑わしいなら
clear_bad_blocksを試す。ただし、それで必ず復旧するとは考えない。 stage_execを動かす前に、datadir を書く process が一つだけであることを確認する。- reset するなら
/data/chaindataだけを削除し、snapshots は残す。 - restart 後は
eth_blockNumberだけでなく、Execution History、Execution、TxLookup、Finishを見る。 - 最後は、元の失敗ブロックを越えたかどうかで判断する。
一番重要な境界は単純だ。
主 Erigon と integration command に同時に /data を書かせない。
この障害から残した判断
固定ブロックの gas-used mismatch は、普通の再起動だけでは説明しにくい。 client の execution rule か、現在の client とローカル execution database の組み合わせが壊れている可能性が高い。
今回、v3.5.0 でも 25393069 の失敗は再現した。
軽い command で marker を消したり一部 stage を再実行したりはできたが、復旧時間としては明確な答えにならなかった。
chaindata を削除すると、Erigon はローカル snapshots から作り直し、失敗ブロックを越えた。
次に同じ形を見るなら、判断順はこうする。
- fixed-block failure かを確認する。
- 同じ signature の upstream report を探す。
- 低リスクの確認と marker cleanup を先にやる。
- reset が必要なら、必要な層だけ reset する。
きれいな修正ではない。 ただし、境界が明確で、検証点もはっきりした workaround だった。