kona: gate PostExec transaction structure in batch validation - #22102
Closed
nonsense wants to merge 3 commits into
Closed
kona: gate PostExec transaction structure in batch validation#22102nonsense wants to merge 3 commits into
nonsense wants to merge 3 commits into
Conversation
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
marked this pull request as ready for review
July 29, 2026 15:19
einar-oplabs
self-requested a review
July 30, 2026 05:23
| // `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, |
Contributor
There was a problem hiding this comment.
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.
| use tracing::warn; | ||
|
|
||
| /// Validates one block's `PostExec` transactions and payload anchor. | ||
| pub(crate) fn check_post_exec_txs( |
Contributor
There was a problem hiding this comment.
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
self-requested a review
July 30, 2026 05:36
einar-oplabs
approved these changes
Jul 30, 2026
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
marked this pull request as draft
July 30, 2026 18:12
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
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:
PostExectransaction at most;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.
Adds
BatchDropReasonvariants; there are no in-repo exhaustive matches.