Skip to content

feat(l1): enforce per-transaction wire-size cap in mempool admission#6599

Merged
ilitteri merged 6 commits into
mainfrom
feat/mempool-tx-size-cap
May 12, 2026
Merged

feat(l1): enforce per-transaction wire-size cap in mempool admission#6599
ilitteri merged 6 commits into
mainfrom
feat/mempool-tx-size-cap

Conversation

@ilitteri
Copy link
Copy Markdown
Collaborator

@ilitteri ilitteri commented May 11, 2026

Motivation

Every major Ethereum execution client (geth txMaxSize, reth DEFAULT_MAX_TX_INPUT_BYTES, nethermind MaxTxSize / MaxBlobTxSize, erigon size enforcement) ships a per-transaction wire-size cap at admission. Without one a single oversized transaction can chew bandwidth and pool capacity at near-zero attacker cost.

Description

  • New constants MAX_TX_SIZE = 128 KiB and MAX_BLOB_TX_SIZE = 1 MiB in crates/common/types/constants.rs.
  • Blockchain::validate_transaction enforces MAX_TX_SIZE on the canonical RLP encoding of non-blob transactions.
  • Blockchain::add_blob_transaction_to_pool enforces MAX_BLOB_TX_SIZE on the wire wrapperTransaction::encode_canonical_to_vec().len() + BlobsBundle::encode_to_vec().len() — to match geth (tx.Size() includes the attached sidecar), nethermind (MaxBlobTxSize is checked on the wrapper form), and erigon (ValidateSerializedTxn works on the raw wire bytes which for blob txs is the wrapper-with-sidecar). The two encoded sizes are summed because ethrex stores the core tx and the bundle in separate structs; the ±few bytes of outer list framing are rounding error at the 1 MiB scale.
  • New error MempoolError::TxSizeExceeded { actual, limit }.
  • Drops the redundant MAX_TRANSACTION_DATA_SIZE calldata-only check from validate_transaction — the new encoded-size cap is strictly tighter (a 128 KiB calldata payload always encodes to > 128 KiB).
  • Unit test in mempool_tests.rs covering the non-blob path. Integration test for the wrapper check deferred (the c-kzg-gated add_blob_transaction_to_pool isn't currently exercised by mempool_tests, same pattern as PRs feat(l1): cap mempool pending transactions per sender (default 16) #6603/feat(l1): add --mempool.private flag for non-propagating local txs #6576).

Adds the peer-policy size cap that every major Ethereum execution client
(geth `txMaxSize`, reth `DEFAULT_MAX_TX_INPUT_BYTES`, nethermind
`MaxTxSize` / `MaxBlobTxSize`, erigon size enforcement) ships by
default. Without it a single oversized transaction can chew bandwidth
and pool capacity at near-zero attacker cost.

- New constants `MAX_TX_SIZE = 128 KiB` and `MAX_BLOB_TX_SIZE = 1 MiB`
  in `crates/common/types/constants.rs`.
- `Blockchain::validate_transaction` now rejects transactions whose
  `Transaction`-canonical RLP encoding exceeds the cap. For EIP-4844 the
  cap applies to the core tx only — the blob sidecar lives separately
  in `BlobsBundle` and is already bounded by per-blob byte count plus
  per-block blob count.
- New error `MempoolError::TxSizeExceeded { actual, limit }`.
- Two unit tests in `transaction.rs` confirming the wire-size signal
  matches the cap.
Copilot AI review requested due to automatic review settings May 11, 2026 19:23
@ilitteri ilitteri requested review from a team, ManuelBilbao and avilagaston9 as code owners May 11, 2026 19:23
@github-actions github-actions Bot added the L1 Ethereum client label May 11, 2026
@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

The PR correctly implements mempool transaction size limits matching Geth/Reth/Nethermind defaults (128 KiB for standard txs, 1 MiB for EIP-4844). This is a peer policy limit, not consensus, and the implementation correctly excludes the blob sidecar for 4844 transactions.

Issues and Suggestions:

1. Performance: Unnecessary allocation in hot path
crates/blockchain/blockchain.rs:2441

let encoded_len = tx.encode_canonical_to_vec().len();

Allocating a Vec just to check length is expensive for mempool validation. Consider adding a canonical_encoded_len() method to Transaction that calculates the RLP length without allocation, or encode into a reusable buffer.

2. Missing test coverage for EIP-4844 limit
crates/common/types/transaction.rs
Add a test verifying that EIP-4844 transactions use the 1 MiB (MAX_BLOB_TX_SIZE) limit rather than the 128 KiB limit, and that the sidecar exclusion works correctly.

3. Minor: Test boundary condition
The current tests check MAX_TX_SIZE + 1 and a default tx. Add a test for exactly MAX_TX_SIZE bytes to verify the boundary is inclusive (which it should be).

Security/Consensus Notes:

  • Correctly identifies this as mempool policy ("peer-policy default, not consensus")
  • Returns MempoolError ensuring this won't accidentally reject valid blocks during consensus validation
  • EIP-4844 handling correctly excludes the sidecar (which is checked separately via blob count limits)

Nit:
In crates/blockchain/blockchain.rs:2439-2440, the comment mentions "canonical encoding of Transaction excludes the sidecar" - consider explicitly referencing that BlobsBundle is the separate structure to make this clearer for future maintainers.

Overall: Correct and safe, but optimize the allocation before merging if this is a hot path.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have all the context needed. Here is the complete review:


Code Review: feat(l1): enforce per-transaction wire-size cap in mempool admission

Overview

Adds peer-policy admission guards for oversized transactions: 128 KiB for standard transactions and 1 MiB for EIP-4844 blob transactions. These match the limits in geth, reth, and nethermind. The implementation is small, well-motivated, and the PR description is excellent.


Correctness

Size check placed after async I/O — minor ordering issue

In blockchain.rs:2433–2441, the size check happens after two async storage reads (get_latest_block_number + get_block_header). For a DoS-motivated oversized transaction, the guard should fire as early as possible. Moving the encode_canonical_to_vec() check to immediately after the PrivilegedL2Transaction early-return (line 2427) would let it reject before touching storage at all:

if matches!(tx, &Transaction::PrivilegedL2Transaction(_)) {
    return Ok(None);
}

// Size cap — fail fast before any async I/O
let encoded_len = tx.encode_canonical_to_vec().len();
// ...

Overlapping check with MAX_TRANSACTION_DATA_SIZE

blockchain.rs:2468 already checks tx.data().len() >= MAX_TRANSACTION_DATA_SIZE (128 KiB) for non-contract-creation txs. With the new wire-size cap at exactly the same byte count, the data check is now almost entirely subsumed by the wire check for plain txs (RLP framing adds overhead on top of calldata). The data check is not wrong, but the relationship between the two checks is now undocumented. A short comment clarifying this would help future readers — or the data check could be left as a cheaper, early-exit guard explicitly for that purpose.

Boundary semantics are correct

encoded_len > size_limit (strictly greater) — transactions of exactly MAX_TX_SIZE bytes are admitted. This matches geth.


Test Coverage

The two unit tests in transaction.rs confirm the signal used by the check, but neither exercises validate_transaction itself:

  • No test calls validate_transaction with an oversized transaction and asserts MempoolError::TxSizeExceeded.
  • No test covers the EIP-4844 path (MAX_BLOB_TX_SIZE). The existing test_encoded_size_exceeds_max_tx_size only uses EIP1559Transaction. Consider adding a parallel test that creates a large EIP4844Transaction and verifies it encodes above MAX_BLOB_TX_SIZE.
  • test_encoded_size_below_max_tx_size is trivially true for a default-constructed empty transaction and doesn't test any boundary. It could be strengthened by verifying a transaction just under the cap.

Design / Style

Constant placement inconsistency

MAX_TRANSACTION_DATA_SIZE lives in crates/blockchain/constants.rs. The new MAX_TX_SIZE / MAX_BLOB_TX_SIZE land in crates/common/types/constants.rs. The common location is actually better for reuse by other crates, but it creates an inconsistency. Adding a note in blockchain/constants.rs pointing to the new constants (or migrating MAX_TRANSACTION_DATA_SIZE alongside them) would keep related limits discoverable together.

Inline comment length

The 4-line comment block at blockchain.rs:2437–2441 is on the verbose side for inline code. The same information is already captured in the PR description and in the constants.rs doc comments. Condensing to a single line referencing the named constants would be sufficient:

// Peer-policy wire cap (see MAX_TX_SIZE / MAX_BLOB_TX_SIZE in common/types/constants.rs)

Summary

The core logic is correct and the motivation is well-founded. The two actionable points worth addressing before merge:

  • Move the size check before the async storage reads for proper fail-fast behavior.
  • Add a test that drives validate_transaction to TxSizeExceeded, and cover the blob-tx path.

Everything else is minor style feedback.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

Lines of code report

Total lines added: 36
Total lines removed: 1
Total lines changed: 37

Detailed view
+-------------------------------------------+-------+------+
| File                                      | Lines | Diff |
+-------------------------------------------+-------+------+
| ethrex/crates/blockchain/blockchain.rs    | 2508  | +18  |
+-------------------------------------------+-------+------+
| ethrex/crates/blockchain/constants.rs     | 15    | -1   |
+-------------------------------------------+-------+------+
| ethrex/crates/common/types/constants.rs   | 22    | +2   |
+-------------------------------------------+-------+------+
| ethrex/crates/common/types/transaction.rs | 3403  | +16  |
+-------------------------------------------+-------+------+

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 11, 2026

Greptile Summary

This PR enforces a per-transaction wire-size cap at mempool admission — 128 KiB for standard transactions and 1 MiB for EIP-4844 blob transactions — matching the peer-policy defaults shipped by geth, reth, and nethermind. The check uses the canonical RLP encoding length and runs before the existing init-code and data-size guards.

  • MAX_TX_SIZE (128 KiB) and MAX_BLOB_TX_SIZE (1 MiB) are added to constants.rs; Blockchain::validate_transaction rejects on encoded size with the new MempoolError::TxSizeExceeded variant.
  • The new wire-size check is placed ahead of the existing TxMaxDataSizeError guard, which shares the same 128 KiB threshold; because encoding always adds overhead bytes, TxMaxDataSizeError is now unreachable for non-contract-creation transactions.
  • Unit tests confirm the signal fires for EIP-1559 transactions but no comparable test exists for the blob-tx branch (MAX_BLOB_TX_SIZE).

Confidence Score: 4/5

Safe to merge — no transaction is incorrectly admitted or rejected; the only behavioral change is that TxMaxDataSizeError is silently superseded by TxSizeExceeded for large-calldata non-creation txs

The core admission logic is correct and well-commented. The TxMaxDataSizeError arm in blockchain.rs becomes dead code because the new wire-size guard uses the same threshold and runs first with encoding overhead always pushing the total above the limit. Test coverage covers only the EIP-1559 path; the blob-tx branch with MAX_BLOB_TX_SIZE has no corresponding test, leaving the larger 1 MiB cap unverified by any unit test.

The ordering of guards in crates/blockchain/blockchain.rs around lines 2447–2470 deserves a second look for the interaction between TxSizeExceeded and the now-shadowed TxMaxDataSizeError

Important Files Changed

Filename Overview
crates/blockchain/blockchain.rs Adds wire-size cap check before existing init-code/data-size checks; TxMaxDataSizeError is now unreachable for non-contract-creation txs since the new guard fires first at the same threshold
crates/blockchain/error.rs Adds TxSizeExceeded { actual, limit } variant with a human-readable error message; integrates cleanly with the existing From for RpcErr catch-all
crates/common/types/constants.rs Adds MAX_TX_SIZE (128 KiB) and MAX_BLOB_TX_SIZE (1 MiB) constants with accurate doc comments matching geth/reth/nethermind defaults
crates/common/types/transaction.rs Adds two unit tests verifying the wire-size signal for EIP-1559 txs; blob tx (EIP-4844) coverage for MAX_BLOB_TX_SIZE is absent

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[validate_transaction called] --> B{PrivilegedL2Transaction?}
    B -- yes --> C[return Ok - bypass all checks]
    B -- no --> D[encode_canonical_to_vec]
    D --> E{EIP-4844 tx?}
    E -- yes --> F[limit = MAX_BLOB_TX_SIZE 1 MiB]
    E -- no --> G[limit = MAX_TX_SIZE 128 KiB]
    F --> H{encoded_len > limit?}
    G --> H
    H -- yes --> I[Err TxSizeExceeded]
    H -- no --> J{Contract creation AND Shanghai?}
    J -- yes --> K{data > max_initcode_size?}
    K -- yes --> L[Err TxMaxInitCodeSizeError]
    K -- no --> M{Non-creation AND data >= 128 KiB?}
    J -- no --> M
    M -- yes --> N[Err TxMaxDataSizeError - now unreachable for non-creation txs]
    M -- no --> O[Continue remaining checks...]
Loading

Comments Outside Diff (1)

  1. crates/blockchain/blockchain.rs, line 2468-2470 (link)

    P2 TxMaxDataSizeError becomes unreachable for non-contract-creation txs

    The new wire-size check (line 2447) runs before this data-length check and uses the same 128 KiB threshold. Because encode_canonical_to_vec() always emits at least the type byte plus RLP framing in addition to the raw data bytes, any tx whose data.len() >= 131_072 will already have an encoded size > MAX_TX_SIZE (131_072), so the earlier guard fires first. The TxMaxDataSizeError arm is now dead code for regular call transactions; callers that pattern-match on that variant for non-creation txs will instead see TxSizeExceeded.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/blockchain/blockchain.rs
    Line: 2468-2470
    
    Comment:
    **`TxMaxDataSizeError` becomes unreachable for non-contract-creation txs**
    
    The new wire-size check (line 2447) runs before this data-length check and uses the same 128 KiB threshold. Because `encode_canonical_to_vec()` always emits at least the type byte plus RLP framing in addition to the raw `data` bytes, any tx whose `data.len() >= 131_072` will already have an encoded size `> MAX_TX_SIZE` (131_072), so the earlier guard fires first. The `TxMaxDataSizeError` arm is now dead code for regular call transactions; callers that pattern-match on that variant for non-creation txs will instead see `TxSizeExceeded`.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
crates/blockchain/blockchain.rs:2468-2470
**`TxMaxDataSizeError` becomes unreachable for non-contract-creation txs**

The new wire-size check (line 2447) runs before this data-length check and uses the same 128 KiB threshold. Because `encode_canonical_to_vec()` always emits at least the type byte plus RLP framing in addition to the raw `data` bytes, any tx whose `data.len() >= 131_072` will already have an encoded size `> MAX_TX_SIZE` (131_072), so the earlier guard fires first. The `TxMaxDataSizeError` arm is now dead code for regular call transactions; callers that pattern-match on that variant for non-creation txs will instead see `TxSizeExceeded`.

### Issue 2 of 2
crates/common/types/transaction.rs:3786-3793
**No test for the `MAX_BLOB_TX_SIZE` cap**

Both new tests exercise `MAX_TX_SIZE` via EIP-1559 transactions, but there is no corresponding test that constructs an `EIP4844Transaction` with an encoded size just over `MAX_BLOB_TX_SIZE` (1 MiB) and verifies the signal fires. The blob tx branch in `validate_transaction` is the riskier path (1 MiB limit vs 128 KiB), so a parallel "exceeds / below" pair for blob txs would meaningfully close the coverage gap.

Reviews (1): Last reviewed commit: "feat(l1): enforce per-transaction wire-s..." | Re-trigger Greptile

Comment thread crates/common/types/transaction.rs Outdated
Comment on lines +3786 to +3793
#[test]
fn test_encoded_size_below_max_tx_size() {
// A small tx encodes well below the cap.
use crate::types::MAX_TX_SIZE;

let tx = Transaction::EIP1559Transaction(EIP1559Transaction::default());
assert!(tx.encode_canonical_to_vec().len() <= MAX_TX_SIZE);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No test for the MAX_BLOB_TX_SIZE cap

Both new tests exercise MAX_TX_SIZE via EIP-1559 transactions, but there is no corresponding test that constructs an EIP4844Transaction with an encoded size just over MAX_BLOB_TX_SIZE (1 MiB) and verifies the signal fires. The blob tx branch in validate_transaction is the riskier path (1 MiB limit vs 128 KiB), so a parallel "exceeds / below" pair for blob txs would meaningfully close the coverage gap.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/common/types/transaction.rs
Line: 3786-3793

Comment:
**No test for the `MAX_BLOB_TX_SIZE` cap**

Both new tests exercise `MAX_TX_SIZE` via EIP-1559 transactions, but there is no corresponding test that constructs an `EIP4844Transaction` with an encoded size just over `MAX_BLOB_TX_SIZE` (1 MiB) and verifies the signal fires. The blob tx branch in `validate_transaction` is the riskier path (1 MiB limit vs 128 KiB), so a parallel "exceeds / below" pair for blob txs would meaningfully close the coverage gap.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a per-transaction wire-size admission cap to the L1 mempool, aligning ethrex’s peer-policy behavior with other execution clients to reduce bandwidth/mempool DoS risk from oversized transactions.

Changes:

  • Introduces MAX_TX_SIZE (128 KiB) and MAX_BLOB_TX_SIZE (1 MiB) as mempool admission size caps.
  • Adds a canonical-encoding size check in Blockchain::validate_transaction, returning a new MempoolError::TxSizeExceeded { actual, limit } when exceeded.
  • Adds unit tests ensuring Transaction::encode_canonical_to_vec() size behavior matches expectations around the cap.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
crates/common/types/transaction.rs Adds unit tests validating canonical encoding size vs MAX_TX_SIZE.
crates/common/types/constants.rs Defines mempool admission size cap constants for non-blob and blob core transactions.
crates/blockchain/error.rs Adds MempoolError::TxSizeExceeded { actual, limit } for size-cap rejections.
crates/blockchain/blockchain.rs Enforces the per-transaction canonical wire-size cap during mempool validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/blockchain/blockchain.rs Outdated
// nethermind `MaxTxSize` / `MaxBlobTxSize`. For EIP-4844 the canonical
// encoding of `Transaction` excludes the sidecar (which lives in the
// adjacent `BlobsBundle`), so this caps the core tx only.
let encoded_len = tx.encode_canonical_to_vec().len();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

  1. Medium: the new size gate runs too late to provide strong resource protection. add_blob_transaction_to_pool() still does full blobs_bundle.validate() before the new TxSizeExceeded check, and the plain path still recovers sender() before it as well, so oversized submissions can still force crypto work before rejection. I’d pull the size check into a small preflight helper and run it before bundle validation / sender recovery. blockchain.rs:2327 blockchain.rs:2360 blockchain.rs:2441 blobs_bundle.rs:141

  2. Low: tx.encode_canonical_to_vec().len() allocates/clones the full canonical buffer just to measure its length. For typed txs that clones the cached Vec; for legacy txs it re-encodes again after hash(). On the mempool hot path that is avoidable O(n) memory churn for large transactions. A canonical_length() helper using byte-counting would be cheaper. blockchain.rs:2441 transaction.rs:1695 encode.rs:93

  3. Low: coverage is too indirect for the new behavior. The added tests only assert encoder length on EIP-1559; they do not exercise the actual mempool rejection path or the EIP-4844 MAX_BLOB_TX_SIZE branch. I’d add validate_transaction() / add_blob_transaction_to_pool() tests that expect MempoolError::TxSizeExceeded. transaction.rs:3768 blockchain.rs:2442

I did not see consensus, EVM opcode, or gas-accounting regressions in the diff beyond those mempool-admission concerns. I could not run targeted cargo test commands here because rustup failed to write under /home/runner/.rustup/tmp on this read-only environment.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

Copy link
Copy Markdown
Collaborator

@MegaRedHand MegaRedHand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I think we can remove the unit tests

Comment thread crates/blockchain/blockchain.rs Outdated
// nethermind `MaxTxSize` / `MaxBlobTxSize`. For EIP-4844 the canonical
// encoding of `Transaction` excludes the sidecar (which lives in the
// adjacent `BlobsBundle`), so this caps the core tx only.
let encoded_len = tx.encode_canonical_to_vec().len();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to find another way to compute this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread crates/common/types/transaction.rs Outdated
ilitteri added 5 commits May 11, 2026 19:25
…egration tests

Two changes from review:

1. Drop the now-redundant calldata-only size check
   `MAX_TRANSACTION_DATA_SIZE = 128 KiB` (in `crates/blockchain/constants.rs`)
   was byte-identical to the new `MAX_TX_SIZE`, and the new wire-size
   check at the top of `validate_transaction` runs first and rejects
   for all the same inputs (the encoded envelope is strictly larger
   than `data().len()`). Delete the constant, the
   `MempoolError::TxMaxDataSizeError` variant, and the dead check
   block. No behavior change.

2. Replace the two tautological encoding tests in `transaction.rs`
   (still landing — they only asserted that RLP doesn't compress) with
   real integration tests in `test/tests/blockchain/mempool_tests.rs`
   that exercise `validate_transaction` directly:
   - `validate_transaction_rejects_oversize_non_blob` against
     `MAX_TX_SIZE`
   - `validate_transaction_rejects_oversize_blob_core` against
     `MAX_BLOB_TX_SIZE` for an EIP-4844 tx
   Both assert the `TxSizeExceeded { actual, limit }` shape so a
   regression in the limit dispatch (non-blob vs blob) is also caught.
Cross-client audit flagged that geth (`core/types/transaction.go::Size`),
nethermind (`MaxBlobTxSize`), and erigon (`ValidateSerializedTxn`) all
compare their 1 MiB blob-tx cap against the wire form that **includes
the sidecar** (blobs + commitments + proofs). The previous ethrex check
in `validate_transaction` compared `Transaction::encode_canonical_to_vec`
which only covers the core tx — the sidecar lives in the adjacent
`BlobsBundle`. With 6 blobs (~786 KB blob data + ~100 KB
commitments/proofs ≈ 900 KB) the worst-case wire wrapper can reach
~1.9 MiB while still passing the 1 MiB core-only check. Peers reject
that on the wire so ethrex would be admitting txs nobody else will relay.

Changes:
- `validate_transaction` now only enforces `MAX_TX_SIZE` for non-blob txs.
- `add_blob_transaction_to_pool_inner` runs a new wire-wrapper check
  before bundle validation: `core_tx_encoded + bundle_encoded <=
  MAX_BLOB_TX_SIZE`. Summing the two encoded sizes matches geth's
  `tx.Size()` semantic to within the ±few bytes of outer list framing,
  which is rounding error at this scale.
- Drop `validate_transaction_rejects_oversize_blob_core` — the
  function it covered no longer applies to blob txs. Integration test
  for the new wrapper check deferred (same pattern as PRs #6603/#6576:
  the `c-kzg`-gated `add_blob_transaction_to_pool` isn't currently
  exercised by `mempool_tests`).
…ust to measure

Both reviewers (Copilot + @MegaRedHand) flagged that
`tx.encode_canonical_to_vec().len()` allocates a `Vec` purely to read
its length on the mempool-admission hot path, and for typed txs also
clones the cached canonical bytes.

`encode_canonical_len()` counts the 1-byte EIP-2718 type prefix plus
the inner tx's RLP `length()` (which `RLPEncode::length` provides
allocation-free via the existing `ByteCounter`). The two admission
size-check call sites (non-blob in `validate_transaction`, blob
wire-wrapper in `add_blob_transaction_to_pool_inner`) now use this
helper; the blob path also switches `BlobsBundle::encode_to_vec().len()`
to `BlobsBundle::length()` for the same reason.
Per @MegaRedHand's review (approved with note: "I think we can remove
the unit tests"). The two tests in `transaction.rs` (`test_encoded_size_
exceeds_max_tx_size` and `test_encoded_size_below_max_tx_size`) only
re-derived the wire-size signal; the admission gate itself is covered
by `validate_transaction_rejects_oversize_non_blob` in the integration
test suite.
Codex + Claude review both flagged that the wire-size gate runs after
secp256k1 sender recovery in `add_transaction_to_pool`. For a tx that
fails the size check, the work was wasted; for a tx that passes, this
is just ordering. Matches geth's order: `ValidateTransaction` runs the
size check before any crypto.

`add_transaction_to_pool` now applies the cap right after the
`BlobTxNoBlobsBundle` rejection, before `contains_tx` and sender
recovery. The same check remains inside `validate_transaction` so
direct callers (integration tests, L2 paths) keep the guarantee
unchanged. The blob wrapper-size check is already first in
`add_blob_transaction_to_pool_inner` from a prior commit.
@ilitteri
Copy link
Copy Markdown
Collaborator Author

@MegaRedHand 3ee3f72 — removed the two encode-size sanity tests per your review.

@ilitteri
Copy link
Copy Markdown
Collaborator Author

9d21d06 — moved the non-blob size check before sender recovery per the @codex + @claude review feedback. The blob wrapper-size check is already first in add_blob_transaction_to_pool_inner.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@github-project-automation github-project-automation Bot moved this to In Review in ethrex_l1 May 12, 2026
@ilitteri ilitteri added this pull request to the merge queue May 12, 2026
Merged via the queue into main with commit c2e3666 May 12, 2026
56 checks passed
@ilitteri ilitteri deleted the feat/mempool-tx-size-cap branch May 12, 2026 19:56
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants