Context
We are consolidating the reorg tests that live in each execution client's repository into one declarative cross-client suite run through hive, so the same payload DAGs and Engine API step lists execute against every client:
Running it against go-ethereum, reth, nethermind, besu and erigon surfaced this.
Case
A forkchoiceUpdated whose headBlockHash is already the current head does not persist the safeBlockHash / finalizedBlockHash it carries. The identical call persists both when it also advances the head.
Canonical a1 ... a6, each delivered with newPayload + forkchoiceUpdated(head=aN, safe=0x0, finalized=0x0) so both markers start unset and the head is a6. Then:
forkchoiceUpdated(head = a6, safe = a4, finalized = a2) → VALID
eth_getBlockByNumber("safe") → -39001 block "safe" not available (head block: 6)
eth_getBlockByNumber("finalized") → -39001
What the suite sees in one of the affected fixtures (test_safe_finalized_labels_follow_fcu), where the previous call had set safe = b2 while advancing the head and the call under test re-sends the same head with safe = b3:
step 11/12 forkchoiceUpdated(head=b3, safe=b3, fin=b3): status=VALID lvh=0x6751a5...fc495f -> 'applied'
assertHead: latest == b3
assertHead: 'safe' is b2 (0x2603fd...e5e8b9), expected b3 (0x6751a5...fc495f) <-- FAIL
and in another fixture, where no marker had ever been set (test_fcu_below_finalized_with_inconsistent_state):
step 21/22 forkchoiceUpdated(head=a10, safe=a10, fin=a5): status=VALID lvh=0x648940...67e454 -> 'applied'
assertHead: latest == a10
eth_getBlockByNumber('safe') error -39001: block "safe" not available (head block: 10)
assertHead: 'safe' is null (None), expected a10 (0x648940...67e454) <-- FAIL
Boundary
Six cases, fresh client each, canonical chain built with zero markers, one call under test, markers read back afterwards:
| case |
call under test |
erigon 3.7.0-dev-ab8e9fde |
geth / reth / nethermind / besu |
| head advances |
newPayload(a6), FCU(head=a6, safe=a4, fin=a2) |
pass |
pass |
| same head, both markers |
head already a6, FCU(head=a6, safe=a4, fin=a2) |
safe null |
pass |
| same head, safe only |
FCU(head=a6, safe=a4, fin=0x0) |
safe null |
pass |
| same head, finalized only |
FCU(head=a6, safe=0x0, fin=a2) |
finalized null |
pass |
| rewind |
markers set while advancing, then FCU(head=a4, safe=a4, fin=a2) |
pass |
pass |
So the trigger is exactly "this call does not change the head", and both markers are affected. Six fixtures in the suite fail on this.
Reproduce
Requires go + docker only; the fixtures come from a preview release and the simulator is built from the PR branch:
git clone -b reorg-suite-pr https://github.com/CPerezz/hive
cd hive && go build .
./hive --sim ethereum/eels/consume-reorg \
--sim.buildarg repo=https://github.com/CPerezz/execution-specs \
--sim.buildarg branch=reorg-suite-pr \
--sim.buildarg fixtures=https://github.com/CPerezz/execution-specs/releases/download/reorg-preview%40v1/fixtures_reorg.tar.gz \
--client erigon --sim.limit ".*test_safe_finalized_labels_follow_fcu.*" --sim.loglevel 3
# results: workspace/logs/<ts>-<id>.json ; per-step trace in workspace/logs/details/*.log
# client output: workspace/logs/erigon/client-*.log
To regenerate the fixtures instead of using the preview release (needs uv):
git clone -b reorg-suite-pr https://github.com/CPerezz/execution-specs
simulators/ethereum/eels/consume-reorg/stage.sh ../execution-specs # fills and stages fixtures.tar.gz
./hive --sim ethereum/eels/consume-reorg \
--sim.buildarg repo=https://github.com/CPerezz/execution-specs \
--sim.buildarg branch=reorg-suite-pr \
--sim.buildarg fixtures=/fixtures \
--client erigon --sim.limit ".*test_safe_finalized_labels_follow_fcu.*" --docker.nocache consume-reorg
Context
We are consolidating the reorg tests that live in each execution client's repository into one declarative cross-client suite run through hive, so the same payload DAGs and Engine API step lists execute against every client:
Running it against go-ethereum, reth, nethermind, besu and erigon surfaced this.
Case
A
forkchoiceUpdatedwhoseheadBlockHashis already the current head does not persist thesafeBlockHash/finalizedBlockHashit carries. The identical call persists both when it also advances the head.Canonical
a1 ... a6, each delivered withnewPayload+forkchoiceUpdated(head=aN, safe=0x0, finalized=0x0)so both markers start unset and the head isa6. Then:What the suite sees in one of the affected fixtures (
test_safe_finalized_labels_follow_fcu), where the previous call had setsafe = b2while advancing the head and the call under test re-sends the same head withsafe = b3:and in another fixture, where no marker had ever been set (
test_fcu_below_finalized_with_inconsistent_state):Boundary
Six cases, fresh client each, canonical chain built with zero markers, one call under test, markers read back afterwards:
3.7.0-dev-ab8e9fdenewPayload(a6),FCU(head=a6, safe=a4, fin=a2)a6,FCU(head=a6, safe=a4, fin=a2)safenullFCU(head=a6, safe=a4, fin=0x0)safenullFCU(head=a6, safe=0x0, fin=a2)finalizednullFCU(head=a4, safe=a4, fin=a2)So the trigger is exactly "this call does not change the head", and both markers are affected. Six fixtures in the suite fail on this.
Reproduce
Requires go + docker only; the fixtures come from a preview release and the simulator is built from the PR branch:
To regenerate the fixtures instead of using the preview release (needs uv):