feat(kona/derive): pure sync deriver with full span-batch overlap check (phase 3/6)#20708
Draft
sebastianst wants to merge 1 commit into
Draft
feat(kona/derive): pure sync deriver with full span-batch overlap check (phase 3/6)#20708sebastianst wants to merge 1 commit into
sebastianst wants to merge 1 commit into
Conversation
b318151 to
f7e3a15
Compare
baedfdf to
c81f347
Compare
5 tasks
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>
c81f347 to
df5d004
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.
Summary
Phase 3 of 6 — stacked on top of #20703 (Phase 2) → #20702 (Phase 1).
Builds a pure (sync, IO-free,
no_std)Deriveron top of phase 2'scrate::core::*primitives, and closes the post-Holocene span-batchoverlap 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.rsand 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 returnsErrDeriver::add_l1_input(L1Input) -> Result<(), CriticalError>Deriver::add_span_batch_overlap(SpanBatchOverlap) -> Result<(), CriticalError>Deriver::reset(safe_head, sys_config) -> DeriveTraceextract_l1_input(header, txs, receipts, rollup_cfg) -> L1Input— filter-only, sysconfig-blindDerivationvariants 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:
derivenever errors. Malformed frames / channels / batches / deposit logs / config logs surface as typedTraceEntry::*Dropped/*Failedentries.add_*only error on contract violations (NonContiguousL1Input,L1InputParentMismatch,UnsolicitedOverlap,OverlapParentMismatch,OverlapRangeMismatch).tracing::*calls insidepure/. Callers translateDeriveTraceentries.extract_l1_inputdoes no fallible parsing;L1Inputcarries rawVec<Log>andVec<(Address, Bytes)>.extract_l1_inputdoes not accept aSystemConfig; the dynamicbatcher_addrfilter lives in the deriver (sysconfig-blind helper).SpanBatchOverlapBlock.txs: Vec<Bytes>— raw RLP throughout, no encode/decode round-trip in the overlap check.pure/isno_std; verified by--target=riscv32imac-unknown-none-elf.matchonDerivationandTraceEntryinsidepure/is exhaustive; strict typed reason enums everywhere, noStringreason fields.reset(safe_head, sys_config)clears pending channels, decoded-but-un-emitted batches, active overlap state, and the L1 input buffer; suppliedsys_configbecomes the new rolling sysconfig.Verification gates
All run from
rust/:cargo build -p kona-derive --no-default-features— PASScargo build -p kona-derive --no-default-features --target=riscv32imac-unknown-none-elf— PASS (no_std proof)cargo build -p kona-derive(default) — PASScargo test -p kona-derive --lib pure::— 20/20 PASS (both with and without default features)cargo test -p kona-derive --test replay— 3/3 PASScargo clippy -p kona-derive --all-targets -- -D warnings— CLEANcargo clippy -p kona-derive --no-default-features --all-targets -- -D warnings— CLEANFull lib-test result: 250 passed, 1 failed. The single failure is the pre-existing flake
test_span_batch_extraction_error_flushes_stage(atracingglobal-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.rsuses 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 deletedSignal::Activationsite (actor.rs:130in kona-node) was load-bearing — the hard gate the plan names before phase 4 can proceed.replay_sysconfig_and_deposit_paths— feeds a realConfigUpdatelog and a realTransactionDepositedlog alongside malformed versions of each, and asserts the deriver emits exactly oneSystemConfigUpdated, oneSystemConfigUpdateDropped, and oneDepositLogDropped.replay_long_empty_sequence_stays_quiet— sanity check: 40 empty L1 blocks produce no spurious drops.crates/protocol/derive/tests/fixtures/README.mddocuments the synthetic-vs-real situation, what would be needed to record real fixtures, and how the futurerecord-fixturescargo feature will be wired so CI replays from frozen bytes only.Unit test coverage
Per-component tests asserting specific
TraceEntryshapes (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 + emitsResetevent, 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 uppure::*; hoist test macros out oftest_utils/so--no-default-featureslib 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 fnforupdate_origin), unblocksclippy -D warnings.crates/protocol/derive/src/test_macros.rs— moved fromtest_utils/macros.rsso theframe!macro is available withoutfeature = "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