You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(validator): sync world state before forking in checkpoint proposal validation (#24694)
## Problem
On a live mainnet node, checkpoint-proposal validation could throw an
uncaught error out of the
gossipsub message handler whenever it ran while the world-state
synchronizer trailed the archiver
(block source) by a block:
```
ERROR world-state:database Call CREATE_FORK failed: Error: Unable to initialize from future
block: 117860 unfinalizedBlockHeight: 117859. Tree name: NullifierTree
ERROR p2p:libp2p_service Error handling gossipsub message: Error: Unable to initialize from
future block: 117860 unfinalizedBlockHeight: 117859. Tree name: NullifierTree
```
The archiver already had block N (so the block lookups inside checkpoint
validation succeeded), but
world state's `unfinalizedBlockHeight` was still N-1. When
`validateCheckpointProposal` forked world
state at the parent block before it had been applied, the raw tree error
escaped as an uncaught
ERROR-level gossipsub message. The node lost that checkpoint
validation/attestation for the round and
spammed error logs; it self-healed the next tick, but it should not
happen.
## Root cause
The two world-state fork sites were asymmetric. The block re-execution
path (`reexecuteTransactions`)
syncs world state *before* forking. The checkpoint validation path
(`validateCheckpointProposal`)
forked via `checkpointsBuilder.getFork` *without* a preceding sync. The
checkpoint path does sync the
block source (archiver) earlier, but that does not advance the world
state, so the gap remained.
## Fix
- Move the sync-before-fork invariant into
`FullNodeCheckpointsBuilder.getFork`: it now calls
`worldState.syncImmediate(blockNumber, blockHash)` before
`worldState.fork(blockNumber)`, so the
single fork helper owns the invariant rather than each caller.
`syncImmediate` blocks until world
state reaches the block, or throws a typed error if it genuinely cannot,
instead of leaking the raw
tree error.
- In `validateCheckpointProposal`, look up the parent block's hash from
the block source and pass it
through so the sync re-syncs on a world-state reorg. Wrap the fork in a
try/catch that maps any
failure to a clean, non-slashable `world_state_not_synced` result, so a
sync/fork failure never
surfaces as an uncaught ERROR-level gossipsub message.
- As a second, independent guard, check that the forked world state's
archive root matches the
proposal's `lastArchiveRoot` before rebuilding the checkpoint. A
mismatch means world state forked
from a different chain than the proposal was built on, so we fail fast
with a clean, non-slashable
`initial_archive_mismatch` result — mirroring the block-proposal
re-execution check — instead of a
confusing downstream header/archive mismatch.
Both new reasons record an `unvalidated` outcome and are non-slashable,
matching the other
"we couldn't validate right now" / local-state-divergence reasons.
Other checkpoint-builder fork sites were reviewed and need no change:
the proposer's
`checkpoint_proposal_job` forks at its own already-synced,
coherence-checked world-state tip, and the
block-proposal re-execution path already syncs before forking and
performs the equivalent
archive-root check.
## Tests
- `checkpoint_builder.test.ts`: `getFork` syncs world state to the block
before forking, and
propagates a sync failure without forking.
- `proposal_handler.test.ts`: forks at the parent block number passing
its block hash; a fork failure
returns `world_state_not_synced`; and a fork whose archive root diverges
from the proposal returns
`initial_archive_mismatch`.
## Notes
The same bug exists on the v4 line — the v4 site is the same function,
forking via
`this.worldState.fork(parentBlockNumber)` directly without a preceding
`syncImmediate`. This fix
should be ported there as well.
Fixes A-1421
(cherry picked from commit b7a7d1b)
0 commit comments