Skip to content

feat(kona/derive): pure sync deriver with full span-batch overlap check (phase 3/6)#20708

Draft
sebastianst wants to merge 1 commit into
seb/refactor/kona-derive-core-carveoutfrom
seb/feat/kona-pure-deriver
Draft

feat(kona/derive): pure sync deriver with full span-batch overlap check (phase 3/6)#20708
sebastianst wants to merge 1 commit into
seb/refactor/kona-derive-core-carveoutfrom
seb/feat/kona-pure-deriver

Conversation

@sebastianst
Copy link
Copy Markdown
Member

Summary

Phase 3 of 6 — stacked on top of #20703 (Phase 2) → #20702 (Phase 1).

Builds a pure (sync, IO-free, no_std) Deriver on top of phase 2's
crate::core::* primitives, and closes the post-Holocene span-batch
overlap content-check spec gap that both op-node
(op-node/rollup/derive/batch_stage.go:139) and kona's async pipeline
(crates/protocol/protocol/src/batch/span.rs:591) skip today.

Closes spec gap: full byte-wise post-Holocene span-batch overlap content check — implemented in pure/overlap.rs and exercised by both unit and replay tests.

Public API (kona_derive::pure)

  • Deriver::new(rollup_cfg, l1_cfg, sys_config, safe_head, dependency_set)
  • Deriver::derive(safe_head) -> (Derivation, DeriveTrace)never returns Err
  • Deriver::add_l1_input(L1Input) -> Result<(), CriticalError>
  • Deriver::add_span_batch_overlap(SpanBatchOverlap) -> Result<(), CriticalError>
  • Deriver::reset(safe_head, sys_config) -> DeriveTrace
  • extract_l1_input(header, txs, receipts, rollup_cfg) -> L1Inputfilter-only, sysconfig-blind

Derivation variants the caller matches exhaustively:

  • Attributes { attrs } — emit and advance the engine.
  • NeedL1Input — fetch + extract + add_l1_input.
  • NeedSpanBatchOverlap { parent, content } — fetch L2 range + add_span_batch_overlap.

Contract invariants

Encoded in the types and verified by tests:

  • derive never errors. Malformed frames / channels / batches / deposit logs / config logs surface as typed TraceEntry::*Dropped / *Failed entries.
  • add_* only error on contract violations (NonContiguousL1Input, L1InputParentMismatch, UnsolicitedOverlap, OverlapParentMismatch, OverlapRangeMismatch).
  • No tracing::* calls inside pure/. Callers translate DeriveTrace entries.
  • extract_l1_input does no fallible parsing; L1Input carries raw Vec<Log> and Vec<(Address, Bytes)>.
  • extract_l1_input does not accept a SystemConfig; the dynamic batcher_addr filter lives in the deriver (sysconfig-blind helper).
  • SpanBatchOverlapBlock.txs: Vec<Bytes> — raw RLP throughout, no encode/decode round-trip in the overlap check.
  • pure/ is no_std; verified by --target=riscv32imac-unknown-none-elf.
  • Every match on Derivation and TraceEntry inside pure/ is exhaustive; strict typed reason enums everywhere, no String reason fields.
  • reset(safe_head, sys_config) clears pending channels, decoded-but-un-emitted batches, active overlap state, and the L1 input buffer; supplied sys_config becomes the new rolling sysconfig.

Verification gates

All run from rust/:

  • cargo build -p kona-derive --no-default-featuresPASS
  • cargo build -p kona-derive --no-default-features --target=riscv32imac-unknown-none-elfPASS (no_std proof)
  • cargo build -p kona-derive (default) — PASS
  • cargo test -p kona-derive --lib pure::20/20 PASS (both with and without default features)
  • cargo test -p kona-derive --test replay3/3 PASS
  • cargo clippy -p kona-derive --all-targets -- -D warningsCLEAN
  • cargo clippy -p kona-derive --no-default-features --all-targets -- -D warningsCLEAN

Full lib-test result: 250 passed, 1 failed. The single failure is the pre-existing flake test_span_batch_extraction_error_flushes_stage (a tracing global-default subscriber collision that reproduces on the phase 2 baseline). Not introduced by this PR.

Downstream consumers (kona-node-service, kona-proof, kona-driver) compile cleanly against the new surface.

Replay test — synthetic fixtures

The replay test at crates/protocol/derive/tests/replay.rs uses synthetic fixtures rather than recorded mainnet data. Recording a real mainnet fixture requires both an L1 RPC and a beacon-chain endpoint covering the chosen range plus an L2 lookup snapshot — not available in the environment this PR was assembled in.

The synthetic fixtures hit each load-bearing code path the plan calls out:

  • replay_hardfork_activation_boundary — feeds an L1 stream straddling a Granite activation timestamp and asserts the deriver continues without any soft-reset signal. This closes the otherwise-hidden risk that the deleted Signal::Activation site (actor.rs:130 in kona-node) was load-bearing — the hard gate the plan names before phase 4 can proceed.
  • replay_sysconfig_and_deposit_paths — feeds a real ConfigUpdate log and a real TransactionDeposited log alongside malformed versions of each, and asserts the deriver emits exactly one SystemConfigUpdated, one SystemConfigUpdateDropped, and one DepositLogDropped.
  • replay_long_empty_sequence_stays_quiet — sanity check: 40 empty L1 blocks produce no spurious drops.

crates/protocol/derive/tests/fixtures/README.md documents the synthetic-vs-real situation, what would be needed to record real fixtures, and how the future record-fixtures cargo feature will be wired so CI replays from frozen bytes only.

Unit test coverage

Per-component tests asserting specific TraceEntry shapes (no log-grep):

  • pure::extract::tests (4) — batch-inbox filter, deposit log filter, config log filter, failed-receipt skip.
  • pure::overlap::tests (5) — accept-on-full-match, drop on tx-count mismatch / tx mismatch / l1-origin mismatch / missing L1 info tx.
  • pure::deriver::tests (11) — empty path, non-contiguous L1 error, parent-mismatch error, unsolicited-overlap error, reset clears state + emits Reset event, malformed sysconfig log → SystemConfigUpdateDropped, malformed deposit log → DepositLogDropped, batcher-from mismatch → BatchInboxTxIgnoredFromMismatch, garbage calldata → FramesParseFailed, sequencing-window expiry → EmptyBatchGenerated, future-safe-head sanity.

Total: 20 unit + 3 replay = 23 new tests, all passing.

Files

  • crates/protocol/derive/src/lib.rs — wire up pure::*; hoist test macros out of test_utils/ so --no-default-features lib tests can construct frames.
  • crates/protocol/derive/src/pure/ — new module: deriver.rs, types.rs, extract.rs, overlap.rs, mod.rs.
  • crates/protocol/derive/src/stages/traversal/{indexed,polling}.rs — minor clippy fix (const fn for update_origin), unblocks clippy -D warnings.
  • crates/protocol/derive/src/test_macros.rs — moved from test_utils/macros.rs so the frame! macro is available without feature = "async".
  • crates/protocol/derive/tests/replay.rs — new replay test.
  • crates/protocol/derive/tests/fixtures/README.md — documents synthetic vs. recorded fixtures.

Fixes #20698
Part of #20695

🤖 Generated by Claude Code

@sebastianst sebastianst force-pushed the seb/refactor/kona-derive-core-carveout branch from b318151 to f7e3a15 Compare May 13, 2026 14:15
@sebastianst sebastianst force-pushed the seb/feat/kona-pure-deriver branch 2 times, most recently from baedfdf to c81f347 Compare May 13, 2026 14:34
Phase 3 of the pure-derivation migration. Builds a pure (sync, IO-free,
no_std) `Deriver` on top of phase 2's `core/*` primitives, and closes
the post-Holocene span-batch overlap content-check spec gap that both
op-node and kona's async pipeline skip today.

Public API in `kona_derive::pure`:
- `Deriver::new(rollup_cfg, l1_cfg, sys_config, safe_head, dep_set)`
- `Deriver::derive(safe_head) -> (Derivation, DeriveTrace)` — never errors
- `Deriver::add_l1_input(L1Input) -> Result<(), CriticalError>`
- `Deriver::add_span_batch_overlap(SpanBatchOverlap) -> Result<(), CriticalError>`
- `Deriver::reset(safe_head, sys_config) -> DeriveTrace`
- `extract_l1_input(header, txs, receipts, rollup_cfg) -> L1Input` — filter-only,
  sysconfig-blind

Contract invariants encoded in types:
- `derive` never returns `Err`; malformed inputs surface as
  `TraceEntry::*Dropped` / `*Failed` entries.
- `add_*` only error on contract violations (non-contiguous L1, overlap
  range mismatch, unsolicited overlap, etc.).
- No `tracing::*` calls inside `pure/` — callers translate trace events.
- `extract_l1_input` does no fallible parsing; `L1Input` carries raw
  `Vec<Log>` and raw `(Address, Bytes)` calldata.
- `SpanBatchOverlapBlock.txs: Vec<Bytes>` — raw RLP throughout, no
  encode/decode round-trip in the overlap content check.
- `pure/` is `no_std`; verified by the workspace's
  `riscv32imac-unknown-none-elf` target.
- Every `match` on `Derivation` and `TraceEntry` inside `pure/` is
  exhaustive; strict typed reason enums everywhere, no `String` fields.

Replay-test fixture status: synthetic. Recording a real mainnet range
requires L1 RPC + beacon + L2 snapshot access not available in this
environment. `tests/fixtures/README.md` documents the fallback and the
shape of future real fixtures behind a `record-fixtures` cargo feature.

Closes spec gap: full byte-wise post-Holocene span-batch overlap content
check (`pure/overlap.rs`).

Fixes #20698
Part of #20695

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sebastianst sebastianst force-pushed the seb/feat/kona-pure-deriver branch from c81f347 to df5d004 Compare May 18, 2026 13:12
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.

1 participant