Tournament read-interface additions for the reference node - #284
Merged
Conversation
GCdePaula
force-pushed
the
feature/block-finished-view
branch
from
August 27, 2026 22:48
b07bb79 to
3408771
Compare
GCdePaula
marked this pull request as ready for review
August 27, 2026 23:31
guidanoli
previously approved these changes
Aug 28, 2026
guidanoli
left a comment
Collaborator
There was a problem hiding this comment.
Solid changes! In particular, I really like that tournamentStanding no longer reverts for no-winner tournaments, and instead returns an extra Boolean value signaling failure.
Base automatically changed from
feature/prt-machine-yield-check-sling
to
feature/prt-machine-yield-check
August 28, 2026 15:28
Append finishedAt to the standing ABI, populate it from the existing finish-time authority, and update strict Rust and Lua consumers without a compatibility fallback.
The INNER_WINNER arm of tournamentStanding() now populates finalState with the winner's claimed final state, which the view already loads to map the parent commitment. Saves observers a per-tournament log query and makes the winner arms uniform. Breaking for strict observers: the field was previously canonical-zero in this arm. The Rust observer discards the value (events own commitment records; a zero claim is on-chain-representable, so no shape check), and the Lua adapter cross-checks it against the folded join record.
A view named "can I stage" should answer, not revert: ROOT_FAILED now returns (isFinished = true, isTournamentFailed = true, zeroed winner fields) through a new isTournamentFailed flag instead of reverting with TournamentFailedNoWinner. The internal classifier is total; the revert posture moves into stageTournamentResult unchanged (same error, same precedence). The node's stage planner asserts loudly on isTournamentFailed, matching the existing impossible-state idiom: a failed tournament on a defended epoch means the protocol assumption broke.
MatchAdvanced gains segmentStartPosition (the same value the bisecting projection reports), so a finished match's bisection trajectory survives match deletion in the event history. The value is already in match storage at emit time; the direction remains derivable from consecutive events except when the waiting side's children are identical, which is exactly the ambiguity the explicit field removes. The Lua client folds the position as a per-match breadcrumb (seeded at zero on creation) and cross-checks it against the live projections, alongside the existing otherParent/leftNode breadcrumbs. The Rust node needs only regenerated bindings: its domain event keeps the minimal deadline-replacement shape.
TournamentDescriptor gains startInstant and allowance, appended so the existing fields keep their positions. Both are clone-immutable creation arguments with no read path until now; an inner clone's allowance is inherited from the parent match clocks at seal time, and joins close at startInstant + allowance under inclusive expiry (now documented on the view). The Lua domain descriptor carries and bounds-checks both fields; the legacy compact reader decodes the widened tuple. The Rust observer leaves them undecoded (Hero acts eagerly on current state) and only its wire fixtures change.
commitmentStanding(root) projects one commitment's raw join record and clock snapshot: joined flag, claimed final state, claimer, and either the paused clock's frozen reserve or the running clock's fixed inclusive deadline. Total over unjoined roots (canonical zeros), and deliberately a snapshot rather than a status: storage keeps no elimination tombstone or commitment-to-match link, so liveness stays topological, and eliminated commitments keep their last written clock (the alpha.3 caveat, now documented on the struct). Unlike alpha.3's removed getCommitment, the raw Clock.State encoding stays off the wire and the claimer is exposed. No per-block-varying field: running-clock consumers derive remaining time from the fixed deadline client-side, which suits pinned reads. The Lua adapter gains the decode arm; the Rust node needs bindings only: the sling has no consumer (join idempotency is event replay and its response policy is eager).
TournamentStandingView gains winnerExpiresAt, populated only in the INNER_WINNER arm: the first inclusive instant at which the winner becomes eliminable and the standing degrades to INNER_ELIMINABLE_WINNER_EXPIRED. The arm already evaluates exactly this boundary through _winnerExpired, so the field adds no new arithmetic authority, and it is fixed once the tournament finishes (the winner clock freezes with the tournament). The consensus-consumed innerResult protocol surface is untouched. The boundary test now pins the reported instant as the exact flip block: INNER_WINNER one block before it, eliminable at it. The Rust observer and Lua adapter canonicality-check the field (nonzero exactly for INNER_WINNER, strictly after finishedAt) and discard it, and the legacy compact reader decodes the widened tuple.
Tournament answers supportsInterface for type(ITournament).interfaceId (reachable through the clones), and MultiLevelTournamentFactory for both type(IMultiLevelTournamentFactory).interfaceId and the base type(ITournamentFactory).interfaceId, since type(I).interfaceId excludes inherited functions. Any interface change flips the id, so the answer doubles as an exact-generation gate for consumers, mirroring DaveConsensus. Applied last in this interface pass so the advertised ids are the final shipped shapes. No client changes: nothing in this repo consumes the gate.
…raint Three commitments from the alpha.4 API review reply: - epoch-lifecycle.md states that settlement never touches the bond path and why (consensus liveness must not depend on the tournament payment path; no recipient code on settlement transactions), and names the obligation it creates: every node implementation owns driving bond recovery per retired tournament, one bond per epoch at stake. - epoch-lifecycle.md states the staging period's second role: the reaction interval in which application-layer foreclosure stops a decided-but-wrong result, freezing the epoch with its inputs reported as never finalized - an intended terminal state, not a stranded-value bug. - joinTournament NatSpec warns at the decision point that the caller becomes the fixed recovery claimer and must be able to receive ETH within the gas-bounded recovery payment (comment-only: hash gate confirms only metadata fingerprints moved).
EXPECTED_DEPENDENCIES_SHA256 still described the pre-alpha-9 dependency tree: the pin was last set by the runner extraction (f4695f4) and the alpha-9 bump (98f355f) never updated it, so the reproducible leaf gas gate rejected every correctly restored checkout. The new digest was reproduced from a pristine soldeer.lock restore (wipe and reinstall yield the same value).
The 4_420_000 allocation predated the machine-yield check and the alpha-9 rollups-contracts bump, which together made the maximum-input leaf-proof path cheaper; the exact-relationship witnesses have been failing since. Under the pinned release environment (forge 1.5.1-v1.5.1, clean tree, matching dependency digests) the maximum rounded recommendation is 3_885_000 (two-winning orientation; the one-winning orientation rounds 1_000 lower and is recorded as alternate slack). The selection adopts the recommendation exactly with zero retained headroom. Propagation: leaf terminal allocation 4_550_000 -> 4_015_000, leaf refund cap 0.221 -> 0.19425 ether, and the leaf-height work reserves and join bonds shrink accordingly (non-leaf values unchanged). Every other action family's recommendation stayed within its configured allocation. Constants-only: ABI and storage unchanged, bytecode and deployment identity change.
A failed root is a documented terminal state, not a local contradiction: the ticked Hero path already logs FailedNoWinner and idles, and plan_stage_tournament_result also runs with no Hero (Absent), where the assert turned a restart into a crash loop. Treat isTournamentFailed like not-finished: log at error level and plan nothing. stageTournamentResult's TournamentFailedNoWinner revert remains the write-side guard.
The segment-start breadcrumb skipped the fold's defensive-cloning convention (eliminable_at already clones through bint): ingestion retained the caller's mutable bint and copy_match aliased fold storage into accessor results. Clone at both seams, with regressions for both mutation vectors.
The observer's winnerExpiresAt shape check only required nonzero for INNER_WINNER, weaker than the stated canonicality rule and the Lua adapter's check. The winner clock's allowance is positive, so a canonical expiry strictly follows finishedAt; enforce it and pin the equality and earlier-expiry rejections.
- The calibration record's network-admission section compared the complete-call gas to transaction limits; the right quantity is the Prague transaction estimate (5,078,866 units: 30.27% of the EIP-7825 cap, 8.46% of the observed block limit). Calldata intrinsics sit outside the refundable seam, so the allocation is unaffected. Hash citations refreshed to the post-autosquash history. - prt-refund-accounting.md: leaf terminal maximum 4,550,000 -> 4,015,000 after the recalibration. - epoch-lifecycle.md: the locked-value exposure is every retired tournament's balance (root and inner), not one bond per epoch. - winnerExpiresAt docstrings state the canonical-zero-after-expiry behavior and the finishedAt + clockAllowance recomputation. - ERC-165 docstrings now state exactly what the id fingerprints: declared function signatures only; a changed id proves a new generation, an unchanged id proves nothing, and binding pinning guards return shapes, struct layouts, and events. - word_small documents its deliberate sub-2^63 envelope; the semantic event decoder keeps full uint64 range where history demands it.
GCdePaula
force-pushed
the
feature/block-finished-view
branch
from
August 28, 2026 19:41
3408771 to
8a8be70
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adaptations to the tournament read API requested by the rollups-node team's alpha.4 review, plus the gas recalibration that review work surfaced. The production Solidity footprint is deliberately small. Diffstat over non-test contract sources: 6 files changed, +159/−25.
Read surface
tournamentStanding()gainsfinishedAt(accepted earlier), populatesfinalStateforINNER_WINNER, and gainswinnerExpiresAt(the first inclusive instant the inner winner becomes eliminable; fixed once the tournament finishes).commitmentStanding(root)per-commitment view: join record, claimer, and a clock snapshot with no per-block-varying field. A projection, not the alpha.3getCommitment: rawClock.Statestays off the wire and eliminated commitments keep stale snapshots (liveness stays topological).MatchAdvancedgainssegmentStartPosition, so a finished match's bisection trajectory survives match deletion in the event history (topic0 changes; only possible pre-release).TournamentDescriptorgainsstartInstantandallowance(the clone's immutable time envelope; joins close atstartInstant + allowance, inclusive).canStageTournamentResult()is total:ROOT_FAILEDanswers through a newisTournamentFailedflag instead of reverting;stageTournamentResultkeeps the revert.Tournament(through the clones) andMultiLevelTournamentFactory.Gas recalibration
Gas.WIN_LEAF_MATCH4,420,000 -> 3,885,000: the old value predated the machine-yield check and the alpha-9 bump, which made the maximum-input leaf-proof path cheaper; the exact-relationship witnesses had been failing. Measured under the pinned release environment; dated record indocs/reviews/2026-08-27-prt-leaf-gas-recalibration/. Leaf bonds shrink accordingly (about 0.027 ETH at the height examples). The reproducible leaf gate's dependency-digest pin, stale since alpha 9, is fixed alongside.