Skip to content

kona: gate PostExec transaction structure in batch validation - #22102

Closed
nonsense wants to merge 3 commits into
developfrom
ae/sdm/fm6-kona-postexec-gate
Closed

kona: gate PostExec transaction structure in batch validation#22102
nonsense wants to merge 3 commits into
developfrom
ae/sdm/fm6-kona-postexec-gate

Conversation

@nonsense

@nonsense nonsense commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Companion to #22101. Kona only checked PostExec (0x7D) activation, so it could accept batches rejected by the execution layer and op-node, producing different safe chains.

This PR adds shared singular- and span-batch validation for:

  • Lagoon activation;
  • one trailing PostExec transaction at most;
  • strict payload decoding and block anchoring;
  • unique refund indices and non-zero refunds.

Entry bounds and targets still require the full block, while refund limits require execution. These remain execution-layer checks.

The span check is defense in depth because post-Holocene spans use singular validation.

Tests

Adds singular and span coverage for every rule, valid controls, and multi-block anchor arithmetic.

cargo nextest run -p kona-protocol --all-features
cargo nextest run -p kona-derive --all-features
cargo check -p kona-protocol --no-default-features
cargo clippy -p kona-protocol --all-features --all-targets -- -D warnings
just fmt-check
just typos

Adds BatchDropReason variants; there are no in-repo exhaustive matches.

kona's batch validation enforced only PostExec activation: a 0x7D transaction
before Lagoon was dropped, but nothing checked how many there were, where in the
block it sat, or whether its payload was well-formed and anchored to the right
block. The execution layer rejects all of those — op-alloy's
parse_post_exec_payload_from_transactions, which kona's own proof executor calls,
plus PostExecState::new in alloy-op-evm.

op-node was just given these rules. Without the matching change a kona-node
follower and an op-node follower reach different verdicts on the same L1 data for
a batch carrying a malformed 0x7D: op-node drops the batch and lets the next one
supply the height, while kona-node accepts it, gets an EL rejection and takes the
deposits-only fallback. Two different safe chains, from a malicious batcher or
from a sequencer buggy at the Lagoon activation boundary.

Add check_post_exec_txs, called by both the singular and span paths, enforcing
activation, at-most-one, payload well-formedness, the payload's block-number
anchor, last-in-block, and the two entry rules PostExecState::new decides from the
payload alone: no zero-refund entry and no duplicate transaction index. Activation
moves into the shared function rather than staying duplicated in each caller's
classification match, so a future caller cannot bypass it. Rule precedence mirrors
op-alloy arm for arm, so a payload violating several rules yields the same reason
in both.

Four EL rules are deliberately left to the executor and documented as such: an
entry index out of range or targeting a deposit or the 0x7D itself, and
refund <= evm_gas_used. Entry indices are block-global — they count the deposits
the pipeline prepends — while a batch holds only the block's non-deposit
transactions, so those need the full transaction list; the ceiling needs
execution. This matches op-node's residual exactly, so the two cannot disagree.

Note the span path is defense in depth: it is only reached pre-Holocene, while SDM
activates at Lagoon, so on a real chain span batches are validated per block
through SingleBatch::check_batch. Enforcing the rules in both keeps them aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nonsense
nonsense marked this pull request as ready for review July 29, 2026 15:19
@nonsense
nonsense requested a review from a team as a code owner July 29, 2026 15:19
@einar-oplabs
einar-oplabs self-requested a review July 30, 2026 05:23
@einar-oplabs einar-oplabs self-assigned this Jul 30, 2026
// `i` includes skipped overlap, preserving block numbering.
let post_exec_validity = check_post_exec_txs(
&batch.transactions,
parent_block.block_info.number + 1 + i as u64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fable 5 (max): Verified against the loop head: the overlap continue sits after enumerate, so i keeps counting skipped blocks and this arithmetic holds. It is the subtlest invariant in the PR though, and today only the comment defends it — the multi-block anchor test runs without overlap. A case with an overlapping span (parent below the safe head, first element skipped) whose first new block carries a correctly-anchored PostExec tx would pin the invariant against a future refactor that filters skipped blocks before enumerating.

Comment thread rust/kona/crates/protocol/protocol/src/batch/post_exec.rs
use tracing::warn;

/// Validates one block's `PostExec` transactions and payload anchor.
pub(crate) fn check_post_exec_txs(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could accept batches rejected by the execution layer and op-node

Would it be worth it to share the validation code between EL and Kona?

@einar-oplabs
einar-oplabs self-requested a review July 30, 2026 05:36
nonsense added a commit that referenced this pull request Jul 30, 2026
The span PostExec cases were all single-block spans, so the per-block
anchor was only ever checked at index 0. Dropping the index term from
parentBlock.Number + 1 + i passed the whole suite.

Add a two-block span whose second block carries a PostExec transaction
anchored to parentBlock.Number + 2, plus a variant anchored to the first
block's number. This matches the kona coverage in #22102.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nonsense
nonsense requested a review from sebastianst July 30, 2026 13:05
@nonsense
nonsense marked this pull request as draft July 30, 2026 18:12
@nonsense nonsense closed this Jul 30, 2026
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