Skip to content

fix(tn-reth): floor snapshot-scaffold heights out of the canonical execution check - #1325

Open
MavenRain wants to merge 5 commits into
mainfrom
fix/1323-scaffold-canonicity-floor
Open

fix(tn-reth): floor snapshot-scaffold heights out of the canonical execution check#1325
MavenRain wants to merge 5 commits into
mainfrom
fix/1323-scaffold-canonicity-floor

Conversation

@MavenRain

@MavenRain MavenRain commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What (Cantina #28)

Fixes #1323. A snapshot restore scaffolds every pre-window execution header
with a B256::ZERO placeholder hash so the reth static files stay contiguous
below the restore window. The vote-time canonical-execution check read those
placeholders directly from the DB, so a snapshot-restored validator would
attest a (scaffold_height, B256::ZERO) pair as canonical execution and let a
peer clear the execution-result gate against a block that was never executed.

Fix

RethEnv now carries the restore window start B as restored_state_floor.
canonical_execution_hash refuses any lookup below the floor, so a scaffold
height resolves to None instead of the zero placeholder and can never be
attested as canonical. On a fresh or normally-synced node restored_state_floor
is None, so the guard is a strict no-op.

Scope note

An earlier revision of this change also rejected the zero hash inside the
RecentBlocks ring as defence-in-depth. That reject was dropped for two
reasons:

  1. It defends an unreachable path. The scaffold dummies are written only to
    the reth static files, never seeded into the RecentBlocks ring. The ring is
    primed from last_executed_output_blocks, which carry real sealed hashes, so
    a scaffold height is never a ring member. The canonical-DB floor guard is the
    only reachable accept path for a scaffold height, and it is fully closed here.
  2. It broke a legitimate sentinel. {0, B256::ZERO} is the default
    "genesis / no execution yet" execution pointer that healthy nodes query
    through wait_for_execution before their first execution. A blanket zero-hash
    reject in the ring makes that query miss and hangs the gate; two tn-node
    integration tests reproduced the hang.

The canonical-DB floor guard is the load-bearing fix and closes #1323 on its own.

Testing

  • tn-reth unit test asserting canonical_execution_hash returns None for a
    height below the restore floor and the real hash at/above it.
  • Manual mutation confirm: neutering the floor guard makes the unit test fail
    (mutant killed).
  • The two tn-node integration tests that caught the earlier ring-guard
    regression (test_entry_reads_static_fee_at_boundary,
    test_sync_then_catchup_recovers_two_worker_accumulator) pass single-threaded.
  • fmt --check, check --all-targets, clippy --all-targets --no-deps -D warnings
    on tn-reth.

…odes

A snapshot-restored validator scaffolds the header region below its
restored-state floor B: zero-hash dummies below the shipped window and
real-hashed but stateless headers within it. The execution-result check
that gates voting (wait_for_execution) read those heights and accepted
them on both paths, so a Byzantine latest_execution_block pointing at a
scaffold height cleared the check on restored nodes. With a restored
voting quorum the poison header certified, and at commit time every
full-history validator failed the same check on the sub-dag leader and
self-halted. Closes #1323.

Close both accept paths at the source:

- RethEnv::canonical_execution_hash returns None below the restored-state
  floor, mirroring read_only_state_db, so the canonical-DB fallback never
  attests a scaffold block.
- RecentBlocks::contains_execution_hash rejects the zero hash outright, so
  a dummy seeded into the ring can never match; sound independent of the
  restore scan-floor work in #1321.

Tests: a new ring unit test rejecting a zero-hash dummy, and the restored
floor scaffold test extended to assert canonical_execution_hash refuses
below B and answers truthfully at B. Both guards mutation-confirmed.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…fix

The scaffold dummies a snapshot restore fabricates are written only to the reth
static files, never seeded into the RecentBlocks ring: the ring is primed from
last_executed_output_blocks, which carry real sealed hashes.  The ring is
therefore not a reachable accept path for a scaffold height, so the zero-hash
reject added in beba215 was defence-in-depth against a path that cannot occur.

The blanket reject also broke the legitimate {0, B256::ZERO} genesis / no-exec
sentinel that a healthy node queries through wait_for_execution before its first
execution: the query missed, wait_for_execution returned Err, and the leader's
sub-dag was declared bogus, flipping the node to CvvInactive and self-halting.
That hung two tn-node integration tests (test_entry_reads_static_fee_at_boundary,
test_sync_then_catchup_recovers_two_worker_accumulator) for 30s each.

Keep the load-bearing half: the restored_state_floor guard in
canonical_execution_hash (the DB fallback), which is the only reachable accept
path for a scaffold height and closes #1323 on its own.

Refs #1323

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain MavenRain self-assigned this Sep 2, 2026
…onicity-floor

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant