Skip to content

feat(node): multi-blob derivation support (V2 batch)#937

Open
curryxbo wants to merge 1 commit intofeat/multi_batchfrom
feat/multi-blob-derivation
Open

feat(node): multi-blob derivation support (V2 batch)#937
curryxbo wants to merge 1 commit intofeat/multi_batchfrom
feat/multi-blob-derivation

Conversation

@curryxbo
Copy link
Copy Markdown
Contributor

@curryxbo curryxbo commented Apr 22, 2026

Summary

Complete the last remaining piece on feat/multi_batch: node-side derivation support for V2 multi-blob batches. V2 support on the contracts / prover / challenger sides has already landed on feat/multi_batch; this PR fills in the derivation gap so that multi-blob batches can be decoded end-to-end.

Key design points (aligned with morph-specs spec-002 §3.2 step 7 and §4.2.5):

  • Concat first, decompress once. tx-submitter for V2 compresses the entire batchBytes as a single zstd stream and then splits it across blobs by MaxBlobBytesSize. Only blob[0] carries a valid zstd frame header; blob[1..N-1] are middle bytes. The previous implementation decompressed each blob independently and appended the results, which fails as soon as more than one blob is used. The new path iterates blobs in tx.BlobHashes() order, extracts raw scalar bytes, concatenates into a single buffer, and calls zstd.DecompressBatchBytes exactly once.

  • Strict reordering against tx.BlobHashes(). The Beacon API does not guarantee sidecar order matches the L1 tx's declared blob hashes. Any reordering would corrupt the concatenated zstd stream. We index sidecars by the KZG-derived versioned hash and reassemble the local BlobTxSidecar in exactly the order declared by the tx.

  • Per-blob VerifyBlobProof. KZG proof verification runs per blob before concatenation. A corrupt blob is located directly instead of being misattributed to a decompression failure downstream.

  • V2 header parsing. V2 reuses V1's 257-byte wire layout, but the semantics of offset 57 change from "single versioned hash" to "aggregated hash keccak(hash[0]||...||hash[N-1])".

    • BlobVersionedHash() returns an error on V2+ to prevent callers from silently treating the aggregated hash as a single blob hash.
    • New BlobHashesHash() accessor returns the aggregated hash; it errors on V0/V1.

Changes

node/types/batch_header.go

  • Add BatchHeaderVersion2 = 2 and expectedLengthV2 = 257.
  • validate() accepts the V2 length.
  • Split BlobVersionedHash() (V0/V1 only) and BlobHashesHash() (V2+ only), each guarded by a version check.

node/derivation/batch_info.go

  • Rewrite ParseBatch() to concatenate all blob raw bytes in tx order first, then perform a single DecompressBatchBytes.

node/derivation/derivation.go

  • fetchRollupDataByTxHash() reorders beacon sidecars by tx.BlobHashes() via a KZG-derived hash index.
  • Enable per-blob VerifyBlobProof.

Tests

node/types:
  TestBatchHeader                                    PASS
  TestBatchHeaderV2                                  PASS
  TestBlobHashesHashUnavailableForLegacy             PASS

node/derivation:
  TestParseBatchSingleBlob                           PASS
  TestParseBatchMultiBlob                            PASS  (random incompressible payload forces compressed size > MaxBlobBytesSize, guaranteeing split across 2 blobs)
  TestParseBatchMultiBlobConcatDecompressInvariant   PASS

Test Plan

  • go build ./... passes.
  • Unit tests: single-blob regression + multi-blob cross-boundary parsing + V2 header parsing + V0/V1 guards.
  • (integration) Devnet end-to-end V2 multi-blob run: tx-submitter emits a multi-blob batch → rollup commitBatch → node derivation → state replay.
  • (integration) V1 parent → V2 child transition batch: verify blockCount derivation is correct across the version bump.

Related

  • Spec: morph-l2/morph-specs/specs/2026/Q2/spec-002-multi-blob-batch-submission
  • Base branch commits: V2 contracts (d78251c4 / 56bd83ff / 9ae6e516), prover multi-blob (2711f073 / 9894b040 / edf823ff), challenger (8c419a78).

Align node-side derivation with feat/multi_batch's V2 batch format so
that multi-blob batches produced by tx-submitter can be decoded and
verified end-to-end.

Changes
- node/types/batch_header.go
  - Add BatchHeaderVersion2 (=2) and expectedLengthV2 (=257, reuses V1
    wire layout).
  - BlobVersionedHash() now errors on V2+ so callers do not silently
    treat the aggregated hash as a single blob hash.
  - New BlobHashesHash() accessor returns the aggregated blob hash
    (keccak(hash[0]||...||hash[N-1])) stored at offset 57 for V2+.

- node/derivation/batch_info.go
  - Rewrite ParseBatch: concatenate all blob raw scalar bytes in tx
    order first, then perform a single zstd.DecompressBatchBytes.
    The prior per-blob decompress-then-append path is incompatible with
    tx-submitter's "compress whole batch, then split into blobs"
    strategy (only the first blob carries a valid zstd stream header).

- node/derivation/derivation.go
  - fetchRollupDataByTxHash: index beacon sidecars by KZG-derived
    versioned hash and reassemble the local BlobTxSidecar in the exact
    order declared by tx.BlobHashes(). Beacon API ordering is not
    guaranteed; any reordering corrupts the concatenated zstd stream.
  - Enable per-blob VerifyBlobProof so a bad blob is located before
    concatenation rather than misattributed to a decompression failure.

Tests
- node/types/batch_test.go
  - TestBatchHeaderV2: V2 257-byte layout + aggregated hash at offset 57.
  - TestBlobHashesHashUnavailableForLegacy: V0/V1 reject BlobHashesHash.
- node/derivation/batch_info_test.go (new)
  - TestParseBatchSingleBlob: backward-compat single-blob path.
  - TestParseBatchMultiBlob: forces compressed payload across 2 blobs
    using random incompressible data; verifies concat-then-decompress.
  - TestParseBatchMultiBlobConcatDecompressInvariant: direct regression
    guarding the low-level invariant ParseBatch now relies on.

Spec reference
- morph-specs spec-002-multi-blob-batch-submission §3.2 step 7, §4.2.5.
@curryxbo curryxbo requested a review from a team as a code owner April 22, 2026 07:10
@curryxbo curryxbo requested review from tomatoishealthy and removed request for a team April 22, 2026 07:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac11f55b-fc56-4d99-ac21-2f03eac41678

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multi-blob-derivation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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