Skip to content

Rv/enforce invariants#52

Open
asavienko wants to merge 33 commits intorv/dafny-modelsfrom
rv/enforce-invariants
Open

Rv/enforce invariants#52
asavienko wants to merge 33 commits intorv/dafny-modelsfrom
rv/enforce-invariants

Conversation

@asavienko
Copy link
Copy Markdown

@asavienko asavienko commented Apr 8, 2026

Summary

Builds the five-layer invariant verification pipeline for op-supernode (Dafny → structural Go → environmental Go → reference model → fuzz → build-tagged runtime assertions). Single source of truth: op-supernode/invariants/SPEC.md.

See SPEC.md §6 Change log for the per-step diff (Step 1 → Step 6). Highlights:

  • Dafnydafny-models/SupernodeState.dfy with I1I11 predicates, I12_InitialStateSatisfiesInvariants lemma, SupernodeView.dfy bridge layer. 66 verified, 0 errors.
  • Go invariants packageSnapshot mirroring the Dafny datatype, structural CheckI2/I4/I5/I6/I7/I8/I10/I11, env-oracle hybrid CheckI1/I3/I7-env/I8-env/I9, and CheckAll / CheckAllWithEnv composition.
  • Reference model — pure Go ApplyAdvance / ApplyInvalidate / ApplyRewind (T3/T4/T5).
  • Native Go fuzzerFuzzReferenceModel (single-chain) and FuzzReferenceModelMultiChain (cross-chain executing messages, exercising I3 acyclicity and I9 plumbing).
  • Runtime assertionsAssert / AssertWith behind the supernode_invariants build tag; zero-cost no-op in release builds.
  • Trace corpusinvariants/testdata/traces/*.json, replayed by TestTraceCorpus.

Review fixes (this push)

Following the PR review pass:

  1. I7 environmental clause is now wired into CheckAllWithEnv via CheckI7_NoHigherL2Block. The previously orphaned Env.HasHigherL2BlockAtOrBelow is no longer dead.
  2. I9 strengthened to compare full BlockID (not just .Number) and to require every per-chain DeriveL1 be an ancestor of the recorded L1Inclusion. Two distinct L1 blocks at the same height after a reorg no longer pass.
  3. findExecMsgCycle is iterative with an explicit DFS stack so deep exec-msg graphs cannot blow the goroutine stack and panic the supernode under runtime assertions.
  4. Assert zero-value fast-path removed so ActivationTS == 0 is no longer silently skipped.
  5. SetFailureSink is mutex-guarded; trace.collectIDs uses errors.As so wrapped errors in joined trees are still discovered.
  6. Deep copies in CloneSnapshot, StaticStateView.Verified, and StaticStateView.LogsDBFor — every map and inner slice is now caller-owned per the doc string.
  7. ApplyInvalidate deduplicates so T4 is idempotent.
  8. FuzzReferenceModelMultiChain added — runs two L2 chains in lockstep with cross-chain executing messages, exercising I3 acyclicity, I6 monotonicity under multiple chains, and I9 plumbing. ~2.1M execs in 30s, 0 violations.
  9. SPEC.md §5 item 19 documents the I3 LogIdx modeling gap (state-local check ignores LogIdx because BlockWithLogs only stores executing messages).

Out of scope (deferred)

SPEC.md §5 items 13–18 — supernode-side adapter (StateView → live Supernode) plumbing. Blocks on:

  • Adding EnumerateSealed(from, to) to op-supervisor logs DB (item 13).
  • Adding ForEach to chain_container.DenyList (item 14).
  • Public *supernode.Supernode → *interop.Interop accessor or type-assertion bridge (item 16).
  • Snapshot timing constraint: only call SnapshotFrom from progressAndRecord after applyPendingTransition (item 17).

Verification

just verify-dafny                        # 66 verified, 0 errors
just verify-invariants-go                # all unit tests pass
just verify-invariants-go-tagged         # tagged runtime tests pass
just fuzz-invariants                     # FuzzReferenceModel + FuzzReferenceModelMultiChain

The just fuzz-invariants recipe was updated to run both fuzz targets sequentially (Go's -fuzz accepts only one target per invocation).

Test plan

  • just verify-dafny — 66 / 0
  • just verify-invariants-go — pass
  • just verify-invariants-go-tagged — pass
  • FuzzReferenceModel 30s — ~2.6M execs, 0 violations
  • FuzzReferenceModelMultiChain 30s — ~2.1M execs, 0 violations

gtrepta and others added 30 commits March 12, 2026 16:06
Co-authored-by: lisandrasilva <lisandrasilva18@hotmail.com>
timestamp instead of randomly assigning to them.
- Fix payload hash for Executing messages

- Generate Receipts from logs

- Ignore genesis blocks for dependencies/invalidation

- Populate cbIndices

- Implement OptimisticAt for RandomChainContainer
Hand testing.T logger to Interop for debugging
Build a five-layer pipeline that enforces the Supernode invariants from
overview.md under a single stable ID catalog (SPEC.md). Every layer cites
the same I1..I12 / T1..T5 / A1..A5 identifiers so a failure in any layer
is greppable back to the specification.

Layers:

  1. Dafny formal models (dafny-models/SupernodeState.dfy,
     SupernodeView.dfy). AllInvariants over an abstract SupernodeState
     datatype (LogsDB, DenyList, Verified) with predicates I1..I11 and
     I12_InitialStateSatisfiesInvariants lemma. Types.dfy gains
     BlockWithLogs, DenyListEntry, IsParentOf. Supernode.dfy's three
     {:axiom}-ensured abstract methods (applyRewindPlan, sameL1Chain,
     resolveFrontierVerificationView) are replaced with trivial no-op
     stubs; axioms removed, postconditions now proven. Dafny 4.11.0
     verifies 66 / 0 errors.

  2. Go invariants package (invariants/invariants.go, snapshot.go).
     Structured *Error type carries the SPEC ID in every failure;
     CheckAll composes with errors.Join. Covers the structural
     invariants (I2, I4, I5, I6, I7, I8, I10, I11).

  3. Environmental oracle (invariants/env.go). Env interface +
     CheckAllWithEnv for the hybrid invariants I1, I3 (initiating
     message existence + Tarjan cycle detection), I8-ancestry, I9.

  4. Reference model + fuzzer (invariants/reference_model.go,
     fuzz_test.go). Pure Go ApplyAdvance / ApplyInvalidate / ApplyRewind
     matching SPEC T3/T4/T5. FuzzReferenceModel drives the model with a
     stateful byte sequence. Fuzz immediately surfaced an A4 precondition
     gap (ApplyAdvance silently accepted denied blocks); fixed by
     rejecting at the reference-model boundary. 2.9M executions over
     30s with zero violations.

  5. Runtime assertions (invariants/runtime_on.go, runtime_off.go).
     Build-tagged Assert / AssertWith / SetPanicOnFailure /
     SetFailureSink / AssertionFailureCount. Zero-cost no-ops in
     release builds; full checks under -tags=supernode_invariants.

Bridge + trace corpus:

  - invariants/bridge.go: StateView interface and SnapshotFrom lifter.
    Pull-based design decouples the package from the Supernode class
    (no import cycle) and avoids needing a whole-state enumeration API
    on interop.LogsDB or chain_container.DenyList, which is blocked on
    upstream changes tracked in SPEC.md §5 items 13-18.
  - invariants/trace.go: Trace JSON format with steps + expected
    failures. TestTraceCorpus replays testdata/traces/*.json. Three
    canonical traces generated by TestRegenCanonicalTraces.

Tooling:

  - justfile targets: verify-dafny, verify-invariants-go,
    verify-invariants-go-tagged, fuzz-invariants (FUZZTIME env var),
    regen-traces, and verify-invariants which runs all of them as a
    single CI gate.
  - CLAUDE.md documents the five layers and the stable-ID contract.
  - SPEC.md §5 open-items list tracks Step 2c / 3c follow-ups (real
    applyRewindPlan, LogsDB / DenyList enumeration APIs, Supernode-side
    adapter wiring).

Verification:

  $ just verify-invariants
    Dafny: 66 verified, 0 errors
    Go:    ok (default)
    Go:    ok (-tags=supernode_invariants)
    Fuzz:  2.9M executions, 0 failures, 85 interesting inputs retained

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…w documentation

Adds `overview.md` detailing the Supernode's architecture, components, state management, cross-validation logic, and assumptions on L1/L2 chains. Includes a mermaid diagram for visualizing relationships and a breakdown of invariants.
…ultichain fuzzing

- Introduced CheckI7_NoHigherL2Block for validating no L2 block exists with timestamp ≤ verified timestamp beyond the expected head (I7 hybrid clause).
- Improved I9 checks to ensure DeriveL1 results are ancestors of the verified L1 inclusion, fully validating "minimal covering" constraints; reorg-safe by verifying BlockID equality.
- Optimized I3 acyclicity detector with an iterative DFS to prevent stack overflows.
- Added `cloneVerifiedSlice` and `cloneBlockWithLogsSlice` for deep-copying snapshot slices safely.
- Enabled multichain state-machine fuzzing (`FuzzReferenceModelMultiChain`) to test I3, I6, and I9 paths under cross-chain interactions.
- Updated `justfile` fuzz target to run both single-chain and multichain fuzzers.
- Augmented test coverage with regression tests for new I7 and I9 constraints (`TestCheckI7_`, `TestCheckI9_` cases).
- Addressed SPEC.md gaps for I3 LogIdx validation and traced new assumptions in §5 item 19.
- Enhanced runtime assertion logic: explicit failure sink, improved nil handling, synchronization for thread safety.
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