Skip to content

refactor(node): feed a dispute ruler only the window it was positioned at - #302

Merged
GCdePaula merged 5 commits into
mainfrom
fix/node-single-window-dispute-feeder
Oct 20, 2026
Merged

GCdePaula merged 5 commits into
mainfrom
fix/node-single-window-dispute-feeder

Conversation

@GCdePaula

@GCdePaula GCdePaula commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Follows #301, now merged; this branch is rebased onto main.

Why

#301 turned a torn dispute boundary into a stop. That removed the last
production path into the dispute write-back's content-addressed miss, the
branch that stored a crossed boundary in full. Today every production
dispute feed lands on the window its boundary was loaded from:

  • positioning crosses on clones and publishes the target boundary;
  • nested levels and level-0 quartets below window height lie inside one
    window;
  • the frontier serves the rest.

An instrumented run saw 467 positioned rulers and no production feed
beyond the positioned window. The write-back's only remaining callers were
tests that replay whole epochs on a fresh store.

This PR makes that contract explicit before the scoped review of
engine/{ruler,machine_stf,dispute}.rs, so the reviewer reads one feeder
contract instead of two.

Reading guide (commit by commit)

  1. test: replay levels wider than a window on the scratch feeder.
    engine_level_root dispatches on the level's span. A level wider than
    one window (the echo golden epoch_root_r44_h48, the runner's root
    replayed under both tables, and the corpus mcycle cases) now replays
    on the scratch feeder. scratch_quartet_root is generalized to any
    stride for this. Golden values are unchanged.
  2. test: pin a rejection fed at a crossed dispute boundary. This is
    new coverage. Echo rejects input 2. A middle-level root over window 2
    reverts to the directory the crossing published, matches the scratch
    replay, and ends idle on boundary 2's state. No test fed a rejection
    above boundary 0 before.
  3. refactor: feed a dispute ruler only the window it was positioned
    at.
    This is the contract change. Feeder::Store keeps the
    positioned window and the directory ruler_at loaded and verified,
    and uses it as the revert checkpoint, as the Advance feeder does with
    its batch checkpoint. Feeding any other window panics. The constructor
    is renamed at_stored_boundary and is private. Two other changes:
    • snapshot_resumed_source_matches_template_replay is deleted. It
      drove the write-back directly, and what it compared stays pinned by
      restarted_source_resumes_a_half_built_level and
      dispute_source_matches_prototype_tree.
    • Tests and docs that described a write-back now describe the
      crossing. In node-architecture.md, the reason not to skip a torn
      boundary is now the single-window feeder.
  4. refactor: delete the dispute write-back's boundary store. This
    removes Storage::commit_boundary_machine, stage_machine_store, and
    their four storage tests. After this commit, production never calls
    cm_store. publish_staged, checkout and commit_clone stay, since
    the runner and the crossing use them.
  5. docs: say which stores the dispute machine can serve unaided. The
    frontier comment in dispute.rs still promised full-epoch replay from
    a store the runner never processed; it now says the machine serves only
    quartets that feed at most one input, and that wider levels replay on
    the scratch feeder in tests.

Net production code: -62 lines (snapshots.rs -67, machine_stf.rs +5).
Tests: -117 lines of storage unit tests, -28 in engine_machine.rs.

What to scrutinize

  • The new single-window assert in Feeder::Store. If the root stride
    could ever exceed one window, or a production ruler fed past its
    positioned window, this assert is the loud revisit trigger. Before this
    PR, that case was a silent full store.
  • The revert checkpoint is now the positioned directory itself, with no
    sync and no rehash. restore_rejected still asserts the reloaded root
    equals the revert root.

Gates

All run in the emulator v0.22 devshell, on v0.22 echo and yield images (archive_version 8), on the tree this branch has now (the rebase onto main left it byte-identical):

  • just check: green. The node lib has 310 tests: fix(node): robustness pass from the Go node and feature/prt comparison #301's 314 minus the four deleted storage tests.
  • just test-engine-machine: 22 passed.
  • just test-node-harness: 28 passed.
  • Corpus replay (computation_hash_corpus_dave_matches_release_manifest, release, against the v0.22.0 corpus): passed.
  • just e2e-smoke (TEST_INSTANCE=18619): 0 of 5 failed: echo simple, chaos, kill_catchup_batched and stf_all; and yield stf_revert, whose dispute over the revert transition the honest node won.

@GCdePaula
GCdePaula force-pushed the fix/node-single-window-dispute-feeder branch from 8af7824 to 596d1b0 Compare October 19, 2026 17:11
@GCdePaula
GCdePaula marked this pull request as ready for review October 19, 2026 17:12
@GCdePaula
GCdePaula requested a review from stephenctw October 19, 2026 17:12
stephenctw
stephenctw previously approved these changes Oct 20, 2026
Base automatically changed from fix/sling-node-robustness to main October 20, 2026 09:32
@GCdePaula
GCdePaula dismissed stephenctw’s stale review October 20, 2026 09:32

The base branch was changed.

The whole-epoch differentials (the echo golden epoch_root_r44_h48, the
runner's root replayed under both tables, and the corpus mcycle cases)
asked the dispute facade for a level-0 root on a fresh store. With no
runner rows to serve it, the facade replayed every input window on the
dispute feeder, which stored each crossed boundary in full. Production
never does that: the runner's rows serve those levels.

engine_level_root now replays a level wider than one window on the
scratch feeder, generalizing scratch_quartet_root to any stride, so the
runner's eager fold is still checked against an independent machine
replay. Levels inside one window keep the facade and both collectors;
engine_root goes through the same dispatch.
No test fed a rejected input from a positioned boundary above the epoch
start, so no test reloaded a directory the crossing had published as the
revert checkpoint. Echo rejects input 2: a middle-level root over window
2 crosses inputs 0 and 1, publishes boundary 2, feeds input 2 from it,
and must match the scratch replay and end idle on boundary 2's state.
…d at

The dispute feeder committed every boundary it crossed into the store,
fully storing any the store missed, and used that directory as the revert
checkpoint. Since the torn-boundary fallback became a stop, no production
ruler feeds past the window its boundary was loaded from: positioning
crosses on clones and publishes the target, nested levels and level-0
quartets below window height lie inside one window, and the frontier
serves the rest. An instrumented run saw 467 positioned rulers and no
production feed beyond the positioned window.

The feeder now keeps that window and the directory positioning loaded
and verified, uses it as the revert checkpoint as the advance feeder uses
the batch's, and asserts loudly if asked to feed any other window. Its
constructor becomes private. snapshot_resumed_source_matches_template_replay
fed two windows through the feeder directly; what it compared, a source
resuming from a crossing-published boundary 1 against a template replay,
stays pinned by restarted_source_resumes_a_half_built_level, and the
window-1 tree shapes by dispute_source_matches_prototype_tree. Tests and
docs that described a write-back now describe the crossing.
commit_boundary_machine and stage_machine_store lost their last caller
with the single-window dispute feeder. Production no longer stores a
machine in full: the runner and the crossing publish clones through
commit_clone and publish_staged, which stay. Their storage tests go too:
the write-once row panic stays pinned by insert_boundary_disagreement_panics,
the vanished-row skip by nearest_boundary_answers_the_floor_and_self_heals,
and republication by a_vanished_dispute_boundary_is_crossed_to_again.
The startup sweep still removes .part-* leftovers from older versions.
The frontier comment still promised that a store the runner never
processed is served by full-epoch replay. With the single-window feeder
the machine answers only quartets that feed at most one input; production
never disputes such a store with inputs, since a hero starts only after
the runner's roll, and the engine harnesses replay wider levels on the
scratch feeder.
@GCdePaula
GCdePaula force-pushed the fix/node-single-window-dispute-feeder branch from 596d1b0 to 7becfb0 Compare October 20, 2026 09:36
@GCdePaula
GCdePaula requested a review from stephenctw October 20, 2026 09:36
@GCdePaula
GCdePaula merged commit dbcca7d into main Oct 20, 2026
9 checks passed
@GCdePaula
GCdePaula deleted the fix/node-single-window-dispute-feeder branch October 20, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants