Skip to content

kona: match op-node span-batch uvarint decoding - #22126

Merged
sebastianst merged 2 commits into
developfrom
seb/kona-span-batch-uvarint
Jul 30, 2026
Merged

kona: match op-node span-batch uvarint decoding#22126
sebastianst merged 2 commits into
developfrom
seb/kona-span-batch-uvarint

Conversation

@sebastianst

@sebastianst sebastianst commented Jul 30, 2026

Copy link
Copy Markdown
Member

Aligns kona's span-batch uvarint decoding with op-node. A byzantine batcher could otherwise publish a span batch that one client decodes and the other rejects, splitting derivation on identical L1 data.

kona decoded all six span-batch uvarint fields with unsigned_varint::decode::u64, whose accept-set differs from op-node's binary.ReadUvarint in both directions:

  • Non-minimal encodings[0x81, 0x00] (1 with a redundant trailing zero) is a valid protobuf Base128 varint. op-node accepts it; unsigned-varint rejects it as NotMinimal, so op-node builds L2 blocks the fault proof program cannot reproduce.
  • Ten-byte varints — a tenth byte terminating above 0x01 sets bits past 63. op-node reports overflow; unsigned-varint returns Ok with the high bits silently discarded, so the fault proof program builds blocks op-node rejects.

The root cause is a wrong varint family: unsigned-varint implements the minimal-only multiformats encoding, not the protobuf Base128 varint that the span batch format specifies — hence a mismatch in both directions. All six fields (rel_timestamp, l1_origin_num, block_count, block_tx_count, tx_nonce, tx_gas) now route through read_uvarint, a port of binary.ReadUvarint. Encoding is unchanged; every value unsigned_varint::encode::u64 emits lies inside the new accept-set.

Applies from Delta onward, not fork-gated. Deploying needs no fork: in both directions the change moves kona onto op-node's accept-set, and op-node's behaviour defines the canonical chain, so it cannot alter any history op-node has already derived. Consensus-affecting (derivation): shipping to a live chain requires a new kona-client release + prestate.

Verification

A differential harness recorded Go's verdict for 649,092 byte strings — all 1- and 2-byte inputs, exhaustive over an 11-symbol alphabet to depth 5, every tenth byte after nine-byte continuation prefixes, 11-byte inputs, and 400k random strings. read_uvarint agrees on accept/reject, value, and consumed length for every one.

The committed field-level vectors are shared byte for byte between the two suites — the table in TestSpanBatchUvarintConformance mirrors the kona tests exactly. On top of that, each client decodes a whole span batch whose block_count has been re-encoded non-minimally, on its own batch (TestSpanBatchNonMinimalBlockCount / test_decode_raw_span_batch_non_minimal_block_count).

Closes ethereum-optimism/protocol-team#223.

Also verified via a local OMP/GPT-5.6-sol review.

🤖 Co-created with Claude Opus 5 (1M context)

kona decoded all six span-batch `uvarint` fields with
`unsigned_varint::decode::u64`, whose accept-set differs from op-node's
`binary.ReadUvarint` in both directions:

- Non-minimal encodings — `[0x81, 0x00]` (`1` with a redundant trailing
  zero) is a valid protobuf Base128 varint. op-node accepts it,
  `unsigned-varint` rejects it as `NotMinimal`.
- Ten-byte varints — a tenth byte terminating above `0x01` sets bits past
  63. op-node reports overflow, `unsigned-varint` returns `Ok` with the
  high bits silently discarded.

Either way a byzantine batcher can publish a span batch that one client
decodes and the other rejects, splitting derivation on identical L1 data.

All six fields (`rel_timestamp`, `l1_origin_num`, `block_count`,
`block_tx_count`, `tx_nonce`, `tx_gas`) now route through `read_uvarint`,
a port of `binary.ReadUvarint`. Encoding still uses
`unsigned_varint::encode::u64`, whose minimal output the new decoder
accepts in full.

Shared vectors pin the pair together: a field-level table on each side and
a whole span batch whose `block_count` is re-encoded non-minimally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sebastianst
sebastianst requested review from a team as code owners July 30, 2026 14:07
Comment thread rust/kona/crates/protocol/protocol/src/batch/prefix.rs Outdated
The three conformance-test blocks each restated why kona must accept exactly
op-node's encodings. Keep that explanation in the `varint` module doc, which
already covers the accept-set, and leave a one-line pointer at each test site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sebastianst
sebastianst added this pull request to the merge queue Jul 30, 2026
Merged via the queue into develop with commit aa4dd03 Jul 30, 2026
111 of 136 checks passed
@sebastianst
sebastianst deleted the seb/kona-span-batch-uvarint branch July 30, 2026 18:21
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