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:
dequeues the same frame;
builds a blob payload;
calls BlobEncoder::encode_packed;
receives DataTooLarge;
requeues the frame;
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.
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.