For the complete documentation index, see llms.txt. This page is also available as Markdown.

75031 bc medium default blob frame sizing can create unencodable frames causing retry storms and 30 batcher resource amplification

Submitted on Apr 26th 2026 at 19:56:52 UTC by @OxPrince for Audit Comp | Base Azul

  • Report ID: #75031

  • Report Type: Blockchain/DLT

  • Report severity: Medium

  • Target: https://github.com/base/base/tree/v0.8.0-rc.24

  • Impacts:

    • Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours

Description

Brief/Intro

The batcher can emit a blob frame that is valid according to the frame-size limit but impossible to pack into a single EIP-4844 blob after the submission layer adds its own derivation/frame overhead. When this one-byte-oversized frame reaches SubmissionQueue::submit_pending, blob encoding fails before any transaction is sent, the same frame is requeued, and the loop immediately retries it without backoff or quarantine. In production this can make the batcher repeatedly spend CPU on an unencodable frame and stop progressing normal L1 submissions, causing resource amplification and backlog growth.

Vulnerability Details

The bug is an off-by-one mismatch between:

  • the maximum frame payload emitted by ChannelOut::output_frame;

  • the maximum payload accepted by the blob encoder;

  • the extra wrapper bytes added when a frame is packed into a blob transaction.

The default batcher configuration uses blob DA and a 130,044 byte target frame size:

The CLI exposes the same defaults:

The blob encoder also has a maximum payload size of 130,044 bytes:

When a channel is closed, ChannelOut::output_frame subtracts the frame metadata overhead from max_frame_size. The first frame also reserves one byte for the compression version. Later frames do not reserve this byte:

Therefore, with default settings:

  • first frame data length: 130,044 - 23 - 1 = 130,020;

  • non-first frame data length: 130,044 - 23 = 130,021.

The first frame still fits into one blob transaction:

The non-first full frame does not fit:

That is one byte above BlobEncoder::BLOB_MAX_DATA_SIZE.

The submission loop does not reject an oversized first submission before attempting blob encoding:

The problematic behavior is the Err(e) => ... continue path. For an unencodable frame, requeueing does not change the frame size. The next loop iteration acquires the permit again, dequeues the same frame again, attempts to encode the same impossible blob payload again, fails again, and repeats.

No L1 transaction is submitted in this path, so no receipt, confirmation, failure, or txpool state can break the loop. There is also no retry counter, sleep, backoff, or failed-frame quarantine.

Reachability

The issue is reachable with default blob settings. It does not require an invalid configuration.

The default encoder can produce the problematic non-first full frame when a channel fragments into multiple frames. A large poorly-compressible L2 transaction payload is enough to create this situation. This is realistic for calldata-heavy or high-entropy transaction input.

The following reachability PoC proves this:

This confirms that normal default framing can emit a non-first frame with 130,021 bytes of data.

Impact Details

Medium: Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours

The resource increase comes from repeated blob-encoding attempts against a frame that can never fit. The batcher repeatedly:

  1. dequeues the same frame;

  2. builds a blob payload;

  3. calls BlobEncoder::encode_packed;

  4. receives DataTooLarge;

  5. requeues the frame;

  6. immediately repeats the same work.

This is not brute force in the sense of many external requests or repeated network calls. The amplification is internal: one reachable bad frame causes the batcher to spin in its own submission loop.

This is far above the 30% Medium threshold. In the real BatchEncoder/SubmissionQueue path, the same frame remains available after each requeue, and there is no built-in maximum retry count on this encode-failure path.

Operationally, this can cause:

  • elevated CPU consumption in the batcher process;

  • failure to make progress on L1 batch submission while the impossible frame remains at the front of the queue;

  • backlog growth behind the stuck frame;

  • partial node/operator degradation during calldata-heavy or high-entropy traffic.

References

  • crates/batcher/encoder/src/config.rs: default target_frame_size = 130_044, max_frame_size = 130_044, da_type = Blob.

  • bin/batcher/src/cli.rs: CLI defaults for --target-frame-size=130044 and --data-availability-type=blobs.

  • crates/batcher/comp/src/channel_out.rs: non-first frames reserve no compression-version byte, allowing 130,021 bytes of frame data under default frame sizing.

  • crates/batcher/blobs/src/encoder.rs: blob payload limit is 130,044; blob frame packing adds 1 + 23 bytes of overhead.

  • crates/batcher/core/src/submissions.rs: oversized first submission is accepted, encode_packed fails, the same frame is requeued, and the loop immediately continues.

  • crates/batcher/encoder/src/encoder.rs: poc_default_framing_emits_blob_oversized_non_first_frame.

  • crates/batcher/core/src/submissions.rs: poc_oversized_blob_frame_requeues_without_backoff and poc_oversized_blob_frame_resource_amplification.

Proof of Concept

Main poc: poc_oversized_blob_frame_resource_amplification

Open proof of concept

Was this helpful?