Mempool Account Transaction Capacity from Historical Activity (MATCHA)


s/o Toni for the artwork

by Thomas, based on ideas and discussions with Vitalik and Toni

Introduction

Frame Transactions (EIP-8141) allow applications to define their own validation and gas payment logic. To keep this programmable validation safe in the public mempool, EIP-8141 limits the validation work for each transaction through MAX_VERIFY_GAS and normally admits at most one pending Frame Transaction per sender. Clients also track validation dependencies so they only revalidate transactions whose dependencies may have changed after a new canonical block.

Keyed Nonces for Frame Transactions (EIP-8250) gives transactions independent nonce keys. This matters for privacy applications, where many users share one sender so their transactions do not expose separate public accounts or fragment the anonymity set. By removing nonce conflicts between unrelated spends, EIP-8250 makes it possible for a future mempool policy to admit several transactions from the same sender.

Relaxing the one transaction limit creates a new DoS vector. An attacker could deploy a fake privacy application and submit many valid spends with different nonce keys, all of which read the same validation setting. A single onchain change to that setting could invalidate the entire pending set and force clients to revalidate and remove every spend. By restoring the setting and resubmitting the spends, the attacker could repeat the attack at little onchain cost relative to the mempool work it creates.

Admitting several transactions also creates a second problem. A malicious user could target an honest privacy application by submitting many valid transactions with low fees. These transactions consume the shared sender’s capacity and prevent other users from entering the mempool. They may remain pending or leave without inclusion, in which case no fee is paid, so fee bids alone cannot provide a hard bound.

In this post, we introduce MATCHA, a local mempool policy for admitting several transactions from one sender while bounding the resulting DoS risk. Each admission beyond the first spends capacity called width, assigned to that sender. A sender earns new width only from the actual gas used by its Frame Transactions in finalized blocks, and spent width is not returned when a transaction leaves the mempool. This bounds repeated mass invalidation.

We also present how the proposed FOCIL extension for Frame Transactions (EIP-8369) would make valid transaction flooding riskier by increasing the chance that the attacker’s transactions are included and the attacker pays for their gas. Clients could strengthen this deterrent with an optional minimum remaining validity period or linearly increasing priority fees.

Goals

The goal of MATCHA is to let clients admit several Frame Transactions with independent EIP-8250 nonce keys from one shared sender, without whitelisting any application, paymaster, verifier, or proof system.

MATCHA must protect the public mempool even if the application is malicious.

Protect the mempool

  • Bound total mempool resource use. The client caps pending transaction data, dependency indexes, signature and proof verification, state reads, revalidation, and cleanup. It may also apply lower limits to each peer, but adding peers or sender addresses does not increase the client’s total limits.

  • Prevent nonce conflicts and double counting the payer balance. No two distinct pending transactions may use the same (sender, nonce_key). The total maximum cost reserved by pending transactions using one payer may not exceed that payer’s balance at the current head.

  • Bound repeated mass invalidation. Admitting an additional transaction, replacing it with different bytes, or rerunning its validation prefix spends width. Inclusion, invalidation, removal, expiry, or reorg does not return that width. Once it runs out, further additional admissions require new finalized activity from the sender.

  • Deter valid transactions that consume shared capacity. FOCIL for Frame Transactions increases the chance that a transaction which remains eligible is included and its payer is charged. Clients may strengthen this deterrent by rejecting transactions close to a predictable expiry or requiring linearly higher fees as more transactions remain pending. Importantly, these policies do not guarantee inclusion or payment, so width and the client’s total limits remain the hard DoS bound.

Note: MATCHA protects mempool resources, not application funds or fairness between users. Its FOCIL deterrence assumes that an honest application makes each user authorize and pay for the gas of their own transaction. Application solvency, settlement safety, and proof design are separate concerns.

How it works

Under EIP-8141 mempool rules, a client normally keeps at most one pending Frame Transaction per sender. MATCHA keeps this transaction as the baseline and may admit additional transactions when they use EIP-8250 nonce keys and satisfy the MATCHA admission profile. The EIP-8141 baseline spends no width, but every additional transaction does. If the baseline leaves while other transactions remain pending, none of them becomes the baseline; it becomes available again only when the sender’s pending set is empty. Frame Transactions that do not satisfy MATCHA continue to follow the “one transaction per sender” policy.

The client uses width only after the validation prefix succeeds and the transaction can be attributed to its sender and payer. All work and storage remain subject to limits shared by the whole client, including transaction data, signature and proof verification, state access, stored records, revalidation, and cleanup. When a limit is reached, the client rejects or defers further work.

Rejection under MATCHA does not make a transaction invalid for inclusion in a block or excuse its omission under FOCIL.

MATCHA does not increase EIP-8141’s validation budget. An application with a larger proof must fit the active budget or use a separately defined public mempool profile.

width

width is the capacity available to a sender for pending Frame Transactions beyond the EIP-8141 baseline. A sender starts with no width and earns it from the actual gas used by its Frame Transactions in newly finalized blocks, up to a local cap. Each finalized block is credited only once:

width[sender] = min(
    width_cap,
    width[sender] + newly_finalized_gas[sender]
)

Each transaction has a charge based on the work budgeted to admit it. admission_gas(tx) covers transaction data, signatures, EIP-8250 nonce checks, the checks in Recent Roots for Frame Transactions (EIP-8272), and EIP-8141 execution through payment approval. For frame execution, it uses the declared execution gas limits, so the stored charge also covers a later revalidation that takes more work than the initial simulation. It excludes later application execution. State gas is bounded separately and is excluded because it does not measure validation work. A safety_factor covers client work that EVM gas does not measure accurately:

charge(tx) = ceil(safety_factor * admission_gas(tx))

The baseline spends no width. Before spending width, an additional transaction must pass the ordinary admission checks and be valid for a child of the current head, including max_fee_per_gas >= next_base_fee. Its sender must then have enough width, and the client deducts the transaction’s charge:

require width[sender] >= charge(tx)
width[sender] = width[sender] - charge(tx)

The remaining rules are:

  • Duplicates, replacements, and revalidation. Exact duplicates of a transaction already pending share one pool record and spend no width. Replacing the baseline also spends no width. Replacing an additional transaction with different bytes spends another charge. Rerunning its validation prefix spends its stored charge before the work begins. If the sender cannot cover that charge, the client removes the transaction without rerunning the prefix.

  • No refunds and local lifetime. Spent width is not returned when a transaction is included, invalidated, or removed. Every pending transaction is removed after a maximum local lifetime, without affecting its onchain validity. A removed transaction must pass admission again, including after a reorg, and spends another charge if it is additional. Rebuilding spends more width; once it runs out, further additional admissions require new finalized activity from the sender.

  • Nonce keys and payer balance. No two distinct pending transactions may use the same (sender, nonce_key). The client reserves each transaction’s maximum cost against the payer’s balance at the current head. After a new head, affected transactions remain unavailable for block construction until their combined reservations fit the new balance. These checks exist only in the client’s mempool.

A change to the payer balance does not by itself require rerunning validation. The client can reuse its earlier result if it confirms that validation would run with the same code and inputs. It must still check nonce keys, recent roots, expiry, fees and the payer’s total reservations at the new head. All these checks remain subject to the client’s resource limits.

Protection against width draining

Irreversible width bounds repeated mempool work, but an attacker can still consume an honest application’s available capacity by submitting valid transactions with very low fees to block concurrent admissions. If the pending set becomes empty, the EIP-8141 baseline becomes available again, but further additional admissions must wait for new finalized activity if the sender has exhausted its width.

MATCHA relies on a consensus extension implementing the FOCIL reference model in EIP-8369 to make this attack risky. If an honest includer selects a transaction eligible under that extension and the inclusion list reaches enough attesters, the builder must include it unless the omission checks establish that it is invalid or does not fit. EIP-8369 evaluates these checks at a position claimed by the builder. In an honest privacy application, inclusion charges the transaction’s user for its gas, and finalization earns fresh width for the shared sender. An additional transaction spends width again whenever it is replaced or its validation is rerun. Only the version that is included earns new width after finalization, so it may replenish less capacity than was spent along the way.

This deterrent is conditional: under Fork-choice enforced Inclusion Lists (FOCIL) (EIP-7805), transaction selection remains with the includer, inclusion lists have finite capacity, and a transaction can become invalid or fee invalid before inclusion. The hard DoS bound therefore remains spent width and the client’s total resource limits, but we think in practice relying on FOCIL might be enough to deter these width draining attacks.

There are also ideas clients could use to strengthen this deterrent with one or both of the following optional policies:

  • Minimum period before expiry. A client may require an additional transaction’s EIP-8141 expiry and every EIP-8272 recent root to remain valid through a target slot chosen by local policy. The longer this period is, the more opportunities FOCIL has to include the transaction, increasing the chance that the attacker has to pay for it.

  • Linear fees using load. A client may also track load, the sum of the charge values of the additional transactions that remain pending. Each new additional transaction must offer a minimum effective priority fee that rises linearly with this load. The client chooses base_price, the starting fee floor. When all charges are equal, the floor is base_price for the first additional transaction, twice base_price for the next, then three times, and so on. When a transaction leaves the mempool, its charge is removed from load even though its spent width is not returned.

    This makes a transaction later in a large batch carry a higher fee offer, so including it can expose the attacker to a cost that grows with the batch. The rule also raises fees for honest users when their shared sender is busy and does not reserve capacity for individual users, so it remains optional and does not replace width as the hard DoS bound.

Next steps

The next step is to implement the core width policy in clients alongside FOCIL for Frame Transactions and test the two attacks MATCHA targets: repeated mass invalidation by a malicious application and width draining against an honest one, including repeated replacements before inclusion. These tests should calibrate the admission charge and capacity limits, measure how quickly eligible transactions are included during congestion, and determine whether FOCIL is sufficient on its own or whether a minimum validity period or linear fees using load justify their added complexity.

The tests should also measure honest throughput: including one transaction changes the shared payer balance and may trigger revalidation of the others before finalization replenishes width. We need to check whether ordinary activity can sustain useful concurrency, and when cheap dependency checks can avoid a full replay.

5 Likes

really like that width is earned from finalized gas and burned irreversibly, it sidesteps the stake and reputation traps at once.

the thing i keep circling on is that the charge is flat across every additional tx, while the work it actually prices, the cheap repeated invalidation loop, only shows up when the validation prefix reads shared live-contended state. if a prefix only touches single-writer state, its own nullifier, or a value bound to a recent root (8272, which you already fold into admission_gas), one onchain write can’t take down the pending set: a recent-root read is stale-acceptable by construction, and a nullifier is self-scoped, so falsifying it only drops that one spend, not its neighbours. those txs structurally can’t be in the mass-invalidation set, yet they still pay full width.

we wrote up the dimension behind this here (Order-dependence as the classifying dimension for frame-transaction mempool admission): the ruler for admission is order-dependence / contention, not the volume of state a check reads, and the recent-root tier is exactly the stale-acceptable middle class. it lines up with your VOPS-eligibility split, class 0 there is the same single-writer case.

so the question is whether width could be indexed by that: full charge when the prefix reads shared live-contended state, waived or reduced when it’s provably single-writer or recent-root-bound. that would also take the edge off cold-start for an honest pool that binds spends to a recent root, which is the natural design anyway.

the obvious counter is admission cost: can a client decide cheaply that a prefix reads no shared contended state without running it? if not, flat charging is the pragmatic call. and exempting those txs leans their capacity protection on FOCIL plus the fee ramp instead of width, which may be fine but is a real shift. curious if you already looked at contention-indexed width and ruled it out on the classification cost.

1 Like

I really like this! This is a much, much better solution for public mempool DoS concerns than ever enshrining special contracts as exceptions; very opposed to ever enshrining any special behavior. Contracts slowly earning leniency from the mempool via frames that succeed is a nice, neutral mechanism.

Could a similar idea be applied to the verify gas limits?

1 Like

thanks for your comments!

I think lower risk of repeated invalidation could justify a lower charge, even if the initial verification costs the same, so that could help with cold start

but the main q is which transactions would actually qualify because “single-writer” is not enough: an app can change its own validation setting and invalidate txns, and even if you have independent nullifiers and recent roots, you still have the shared payer balance. So a draining attack (draining ETH in this case not width) can just make the pending txns unaffordable even if their proofs are valid

@TimTinkers glad you like the idea, and I think it could!

Earned width could allow more expensive verification not just more pending txns, with larger budgets spending more width. Ofc you’d still need to keep the hard upper bound per check

one thing to think about is that some privacy applications need the proof to check authorization so an attacker could name an active pool as the sender and submit an invalid proof that takes a lot of work to reject

if we deduct width before checking authorisation it would let anyone drain the pool’s width, and it we deduct it afterwards it means invalid proofs consume work without spending any width

so it definitely seems worth considering as long as clients still have limits on their total verification work and queued requests, and we woudl need to test how much invalid proofs delay other honest transactions

the balance drain is a different axis from proof invalidation, and reserving the payer balance at admission closes it: a later drain can’t unfund a pending set whose funds are already held. so the qualifying set could be proof-side clean plus reserved balance, not just proof-side clean.

wrote up why that balance can’t be dropped, only reserved: Public-mempool gas sponsorship needs escrow, a bond, or trust

would you draw the line there?

The exact same MATCHA approach could apply to the paymaster as well imo: it earns width from gas it paid for in finalized transactions, and additional admissions and revalidations across all its sponsored senders spend that width.

If it drains its balance and invalidates the pending set, the spent width is not returned. So we don’t necessarily need to prevent the drain itself or lock the funds we just need to bound the repeated mempool work it can cause.

yeah you’re right, i had that wrong. checked the spec, the 8141 reservation is just each node’s own tally, nothing’s locked onchain. a sponsored tx that lands still spends the real balance through gas, so it caps a node’s exposure but doesn’t keep an admitted tx funded.

so width’s the right default, it bounds the repeated work. keeping an admitted tx funded through a drain needs a real onchain lock, that’s heavier and agreed it shouldn’t be forced on every paymaster. opt-in for the ones that want it.

Just curious. Could finalization-gated width become a bottleneck for bursty L2 posting? For example, if a rollup submitter uses multiple nonce keys, could its available mempool concurrency be exhausted while earlier L1 transactions are already included but not yet finalized?

Thank you to @Thomas, @Vitalik, and @Toni for formulating MATCHA. Enabling multi-transaction concurrency for single shared senders without introducing centralized relayer or verifier whitelists addresses a longstanding architectural bottleneck for privacy pools and smart contract account abstraction.

To help ground community and client team discussion around the mechanics, DoS boundaries, and trade-offs, here is a structured synthesis of how the policy operates and where key considerations lie:

1. The Core Tension: Concurrency vs. Mass Invalidation

Standard mempool policies enforce a strict one-pending-transaction-per-sender cap under Frame Transactions (EIP-8141). While Keyed Nonces (EIP-8250) allow multiple transactions to coexist from a single sender without sequential nonce collisions, lifting the one-tx restriction naively exposes nodes to two acute denial-of-service vectors:

  1. Repeated Mass Invalidation (Rogue App): An attacker submits hundreds of concurrent spends across independent nonce keys that all read a single shared validation state. Flipping that state on-chain invalidates the entire mempool batch simultaneously, imposing heavy client simulation/cleanup cycles at negligible attacker cost.

  2. Width Draining / Griefing (Honest App): An attacker floods an honest shared sender with low-fee transactions, monopolizing the sender’s pending capacity and locking out legitimate users without paying execution fees if the transactions drop or linger.

2. How MATCHA Solves the Problem: The Two-Tier Model

MATCHA addresses this without relying on permissioned paymaster whitelists by coupling a free baseline transaction with an irreversible capacity meter (width):

  • The Baseline (EIP-8141 Invariant): Every sender receives one baseline pending transaction that costs zero width. This guarantees that even an empty or un-finalized sender can process transactions sequentially.

  • width Accounting (Finality Gate): Additional concurrent transactions require the sender to spend width. A sender earns width strictly from actual gas consumed in newly finalized canonical blocks (capped locally).

  • Non-Refundable charge: Admitting, replacing, or revalidating an additional transaction deducts charge(tx). Spent width is never refunded—whether a transaction is included, dropped, expired, or invalidated. This enforces an asymptotic ceiling on repeated mass-invalidation attacks.

  • Economic Defense via FOCIL (EIP-8369): To deter width-draining griefing attacks, MATCHA pairs with FOCIL. Because inclusion lists mandate builder inclusion of valid transactions, an attacker attempting to jam an honest contract’s capacity faces a high probability that their low-fee spam is included and forced to pay gas on-chain.

3. Key Discussion Points & Trade-offs for the Ecosystem

  1. Local Hard Bounds vs. Consensus Layer Reliance:
    It is vital to distinguish between safety and deterrence. MATCHA’s hard safety boundary against node exhaustion is 100% local (width depletion and client-level global limits). FOCIL is an economic layer that penalizes griefers; client safety does not collapse if an inclusion list is contested or absent.

  2. Bootstrapping Latency for New Applications:
    Because width is earned strictly through finalized execution gas, newly deployed shared-sender contracts start with zero width. They must operate at the baseline of 1 transaction per block until finality (2 epochs / ~12.8 minutes) credits sufficient capacity. High-volume initial launches will need to account for this initial ramp-up.

  3. Application State Dependencies & Proof Collisions:
    Because spent width is non-refundable, an honest user whose transaction is invalidated by a competing spend (e.g., a nullifier collision or state root advance under EIP-8272) consumes the contract’s shared capacity without a refund. Application developers will need tight dependency isolation in their validation frames to protect the collective pool’s width.

  4. Optional Policies: Validity Windows vs. Linear Priority Fees:

    • Minimum Expiry Windows (EIP-8272): Gives FOCIL enough blocks to force inclusion and penalize spam, representing a low-overhead, effective deterrent.

    • Linear Priority Fees via load: Escalates fee floors as pending volume rises. While effective against bulk flooding, it also prices out honest users during organic usage spikes, making its status as an optional local policy appropriate.

Next Client Implementation Steps

Validating MATCHA will require client-level benchmarking on:

  • Revalidation overheads when shared payer balances change (confirming clients avoid full execution replay when validation bytecode and inputs remain static).

  • Calibration of the safety_factor against complex ZK-proof verification frames.

  • Empirical simulation of width recovery curves during periods of sustained network congestion.