EIP-8141 frame transactions can verify recent roots through a canonical VERIFY frame. The frame carries one to sixteen (source_id, slot, root) tuples and calls a recent root contract that checks each tuple against its storage. Root sources write to the same contract. Each root is keyed by (source_id, slot), where source_id is derived from the writer address and a salt.
The frame runs before account validation in the public mempool. A failed check invalidates the transaction, while account validation can read the verified tuples through existing frame introspection. This requires no change to the FrameTx envelope and no new opcode.
Motivation
Public mempool validation under EIP-8141 cannot read arbitrary storage controlled by another account or application. Some validation rules still need recent application state, such as privacy tree roots, wallet authorization roots, or account validation roots.
The recent root contract stores each entry under a key determined by its source and slot. Except after a chain reorganization, that entry cannot change while a transaction may reference it. The canonical frame lets clients check and index these dependencies without changing the transaction envelope.
Privacy applications, for example, keep a tree of commitments and prove spends against a recent tree root. The application writes roots by slot, and a spend transaction verifies one of those roots before its account validation frame runs.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
This specification extends EIP-8141. Terms not defined here, including FrameTx, VERIFY, expiry_verify, the validation prefix, MAX_VERIFY_GAS, FRAMEPARAM, FRAMEDATALOAD, and FRAMEDATACOPY, have the meanings defined in EIP-8141.
Constants
Name
Value
RECENT_ROOT_ADDRESS
0x0000000000000000000000000000000000008272
RECENT_ROOT_CODE
TBD
RECENT_ROOT_LENGTH
8192
RECENT_ROOT_USABLE_WINDOW
8191
MAX_RECENT_ROOT_REFERENCES
16
RECENT_ROOT_TUPLE_BYTES
72
RECENT_ROOT_ENTRY_DOMAIN
keccak256("RECENT_ROOT_ENTRY")
RECENT_ROOT_STORAGE_DOMAIN
keccak256("RECENT_ROOT_STORAGE")
All concatenations below use encodings with fixed lengths. Domains are 32 bytes. Addresses are 20 bytes. Slots and indices are unsigned integers encoded in eight bytes, with the most significant byte first. Roots, salts, source identifiers, entry hashes, and storage keys are 32 bytes.
Current slot
During block execution, current_slot is the consensus slot of the beacon block containing the execution payload.
Execution clients MUST obtain current_slot from the EIP-7843slotNumber field. Clients MUST NOT derive it from block.timestamp using a fixed slot duration.
During public mempool validation, current_slot is one greater than the slotNumber in the latest canonical execution header. The simulated SLOTNUM value MUST equal this slot. This value deliberately does not advance during empty slots. Before including a transaction in a payload, a client MUST revalidate it with SLOTNUM set to that payload’s slotNumber.
References MUST target slots strictly before current_slot. A root written during slot S becomes referenceable beginning in slot S + 1.
Root sources
A root source is identified by:
source_id = keccak256(source_address || salt)
where source_address is an address and salt is a bytes32 value.
The source address MAY be an externally owned account or a contract, and MAY use multiple root sources through different salts. Applications using a root source are responsible for controlling who can write to it and how salts are allocated.
Entry and storage keys
The committed entry for (source_id, slot, root) is:
In static context, the write MUST fail and storage MUST remain unchanged.
Only execution whose storage context is RECENT_ROOT_ADDRESS writes recent root storage. DELEGATECALL and CALLCODE use the invoking account’s storage context, while EIP-7702 delegated code uses the delegating account’s storage context. None modifies recent root storage unless that context is RECENT_ROOT_ADDRESS.
Each (source_id, S) has at most one referenceable root on the canonical chain. Multiple writes by the same source address and salt during slot S target the same storage key. The final write in canonical block execution order overwrites earlier writes, and only that root is referenceable beginning in slot S + 1.
A successful write returns no data or logs.
Validation operation
The validation operation has n * RECENT_ROOT_TUPLE_BYTES bytes of calldata, where:
1 <= n <= MAX_RECENT_ROOT_REFERENCES
The calldata is the concatenation of n tuples without a selector or length prefix:
source_id: bytes32
slot: uint64_be
root: bytes32
The contract MUST revert for empty calldata, for a length that is not a multiple of RECENT_ROOT_TUPLE_BYTES, or for more than MAX_RECENT_ROOT_REFERENCES tuples. The 64-byte write encoding and every valid validation encoding are disjoint.
The call MUST revert if any tuple fails. Duplicate tuples are valid and are checked independently. If every tuple passes, the call MUST succeed with no return data, logs, or state changes.
The validation operation MUST read the current slot with SLOTNUM, MUST NOT call another account, and MUST read only the recent root storage keys derived from its calldata.
All calls use ordinary EVM execution and gas accounting.
Recent root verifier frame
A recent root verifier frame is a frame with all of the following properties:
For public mempool classification, this frame is named recent_root_verify.
The frame executes the validation operation through the STATICCALL semantics of EIP-8141. If the contract reverts or the frame halts exceptionally, EIP-8141 makes the transaction invalid.
The frame consumes ordinary EVM gas. Its target access and storage reads follow the normal warm and cold access rules, and its data follows the normal EIP-8141 transaction data pricing. This EIP adds no special intrinsic gas, warming rule, or exemption from block gas.
EIP-8141’s canonical signature hash already covers the complete frame list, including each frame’s data. This EIP does not change the transaction payload or signature hash.
Application introspection
Applications read a recent root verifier frame through EIP-8141’s existing FRAMEPARAM, FRAMEDATALOAD, and FRAMEDATACOPY instructions. This EIP adds no transaction parameter or opcode.
Application validation can identify a completed recent root verifier frame through FRAMEPARAM by checking resolved_target == RECENT_ROOT_ADDRESS, mode == VERIFY, flags == 0, limits.state == 0, a valid recent root data length, and status == 1.
In a transaction eligible for the public mempool, the recent root verifier frame is frame 0, or frame 1 when an expiry_verify frame is present.
Public mempool handling
EIP-8141’s public mempool policy is extended to permit the current_slot value defined above and the code and storage reads required by a recent_root_verify frame, subject to the rules below.
A transaction eligible for the public mempool MUST contain no more than one recent root verifier frame. If present, it MUST appear immediately after an optional expiry_verify frame and before every other frame:
For the purpose of matching EIP-8141’s four recognized validation prefix shapes, clients MUST skip both optional leading protocol verifier frames. The EIP-8141 rule that deploy is first applies after these frames are skipped.
Clients MUST check this frame shape and data encoding before reading sender state.
A recent root verifier frame is subject to EIP-8141’s generic validation trace and opcode rules with exactly two additional permissions. These permissions apply only while this frame executes at the top level and the runtime code at RECENT_ROOT_ADDRESS exactly equals RECENT_ROOT_CODE:
SLOTNUM MAY execute inside RECENT_ROOT_CODE.
SLOAD MAY read RECENT_ROOT_ADDRESS[storage_key] for storage keys derived from the frame’s tuples as specified above.
No nested call receives either permission. No other opcode, call, storage read, or storage write exception is added. Clients MUST reject the transaction from the public mempool if the code at RECENT_ROOT_ADDRESS does not equal RECENT_ROOT_CODE.
The frame MUST execute successfully. During public mempool validation, a client MAY evaluate this frame directly instead of executing RECENT_ROOT_CODE, even when other frames in the validation prefix require simulation. This direct evaluation MUST produce the same frame receipt and gas use as EVM execution. It MUST also apply the same warm account and storage key updates and rollbacks as EVM execution. A client MUST reject the transaction if EVM execution would revert or halt exceptionally, including when frame.limits.execution is insufficient.
Clients MUST count the recent root verifier frame’s limits.execution toward EIP-8141’s MAX_VERIFY_GAS limit.
Clients MUST reject a transaction from the public mempool if any tuple has slot >= current_slot.
Clients MUST evict a pending transaction if any tuple has slot >= current_slot. Otherwise, they MUST evict it when any tuple reaches:
current_slot - slot >= RECENT_ROOT_LENGTH
To avoid admitting a transaction that expires almost immediately, clients SHOULD NOT admit a transaction whose oldest reference is within a margin chosen by the node.
On admission, clients MUST record each distinct (storage_key, entry_hash) dependency and the first current_slot at which it expires. Clients SHOULD index pending transactions by these values so that a slot advance or chain reorganization does not require scanning unrelated transactions. Clients MUST also track the code and activation status of RECENT_ROOT_ADDRESS as dependencies. These records MUST be removed when the transaction leaves the public mempool. When one transaction replaces another, clients MUST replace these records atomically with the transaction.
When any recorded dependency may have changed, clients MUST recheck the complete age and storage predicates for each affected transaction and evict transactions that no longer satisfy them. Reorganization handling MUST consider state changes on both the removed and added branches, including a referenced entry removed by rollback without a replacement write. A change to the code or activation status of RECENT_ROOT_ADDRESS requires revalidation of every pending transaction containing a recent root verifier frame. The ordinary EIP-8141 revalidation rules continue to apply to all other dependencies.
Activation
This EIP MUST activate at or after EIP-8141 and EIP-7843.
For the first block in which this EIP is active, clients MUST initialize RECENT_ROOT_ADDRESS against the parent state before executing any transaction.
If RECENT_ROOT_ADDRESS does not exist, clients MUST create it with balance 0, nonce 1, code RECENT_ROOT_CODE, and empty storage.
If RECENT_ROOT_ADDRESS already exists with empty code and empty storage, clients MUST set its code to RECENT_ROOT_CODE, set its nonce to max(existing_nonce, 1), preserve its balance, and leave storage empty.
The fork configuration MUST choose a RECENT_ROOT_ADDRESS with empty code and empty storage in the parent state of the first active block. If this condition is false, the first active block is invalid.
For all other blocks, clients MUST NOT run this initialization. Clients MUST handle reorganizations across activation by applying or undoing this transition according to the canonical chain.
Rationale
EIP-8141 public mempool validation normally rejects reads from shared mutable storage. Recent root storage is a narrow exception because a valid entry cannot be overwritten during its usable window. A write in the current slot uses the only ring buffer index that could collide with a root exactly RECENT_ROOT_LENGTH slots old, and that older root is already expired.
Canonical frame instead of an envelope field
The canonical frame reuses EIP-8141’s transaction encoding, signature hash, execution semantics, gas accounting, and frame introspection. An envelope field would require a new transaction schema, native checks before frame execution, special gas and warming rules, and a new introspection opcode.
A contract and an informal transaction pool convention are not enough. EIP-8141 has a closed public mempool validation prefix grammar and normally bans both SLOTNUM and reads from storage outside tx.sender. The protocol must define the frame’s shape, position, work bound, allowed storage reads, and revalidation rules.
Full tuples in frame data
Including (source_id, slot, root) in the frame lets clients derive every storage dependency without executing application validation. After a reorganization, a client can identify and evict transactions that reference an orphaned root.
The packed encoding uses 72 bytes per tuple and has no redundant count or selector. Its valid lengths do not collide with the write operation, which uses 64 bytes.
The stored entry commits to source_id, slot, and root. This prevents a root from another source or a stale occupant of the same ring buffer index from satisfying the tuple.
Fixed early position
The recent root verifier frame runs after the optional expiry check and before deployment or account validation. This order rejects invalid or orphaned roots before more expensive application validation runs and lets every dependent validation frame inspect a successful frame.
The fixed position affects public mempool eligibility, not block validity. Privately submitted transactions remain subject to ordinary EIP-8141 execution, including its rule that a failed VERIFY frame invalidates the transaction.
Public mempool validation budget
The frame’s limits.execution counts toward MAX_VERIFY_GAS, together with the declared execution limits of the other frames in the validation prefix and the cost of checking the transaction signatures. This uses EIP-8141’s existing validation budget. EIP-8141 can change that budget later if needed.
Direct evaluation does not change the frame’s execution gas or fees. The frame’s declared execution limit still contributes to the transaction’s maximum cost and block gas reservation, and the payer still pays for the gas used.
Window choice
References are limited to slots strictly before current_slot. During slot S, writes update index S mod RECENT_ROOT_LENGTH, but references to S are invalid and references old enough to share that index are expired. Writes in the current slot cannot invalidate valid references.
RECENT_ROOT_LENGTH = 8192 gives RECENT_ROOT_USABLE_WINDOW = 8191, because the current slot is not referenceable.
For public mempool simulation, the latest canonical header slot plus one is the earliest possible slot for the next payload. A reference close to expiry may become stale if slots are skipped, so builders revalidate it against the exact payload slot before inclusion.
Implicit source creation
No creation transaction is required. A root source is created when a source address first writes with a new (source_address, salt) pair. Each root source has a bounded rolling window. Aggregate storage grows linearly with the number of written root sources, and writes that create storage entries pay the ordinary state growth cost.
Backwards Compatibility
This EIP does not change the EIP-8141 transaction payload or signature hash and does not modify other transaction types.
The account at RECENT_ROOT_ADDRESS must have empty code and storage before activation. Calls to that address change behavior after activation because RECENT_ROOT_CODE is installed there.
References to slots before activation are not satisfiable because recent root storage is empty at activation.
Test Cases
Implementations should cover at least the following cases. Rejection from the public mempool does not by itself make a transaction invalid in a block. The contract and EIP-8141 columns state any separate block execution or static validity result.
Case
Contract or EIP-8141 result
Public mempool result
One valid tuple from the previous slot
success
accept if all other EIP-8141 rules pass
Sixteen valid tuples with distinct cold storage keys
success when limits.execution is sufficient
accept if all other EIP-8141 rules pass
Duplicate valid tuples
success
accept
Wrong root, source, or slot
revert
reject
Tuple for the current or a future slot
revert
reject
Ages 8191 and 8192 slots
success, then revert
accept, then reject
Empty validation data, data of length 71 or 73 bytes, or data with seventeen tuples
revert
reject
Nonzero VERIFY value
statically invalid under EIP-8141
reject
flags == 1
contract executes normally
reject; not recent_root_verify
flags contains ATOMIC_BATCH_FLAG
statically invalid under EIP-8141
reject
Nonzero state gas limit
contract executes normally
reject; not recent_root_verify
More than one matching frame
each frame executes normally
reject
Matching frame after deployment or account validation
executes normally
reject
limits.execution is one gas less than the recent root verifier frame requires
halts exceptionally
reject
Signature verification cost plus all validation prefix execution limits, including recent_root_verify, equals MAX_VERIFY_GAS
ordinary execution
accept
Signature verification cost plus all validation prefix execution limits, including recent_root_verify, exceeds MAX_VERIFY_GAS by one
ordinary execution if sufficiently funded
reject
Direct evaluation instead of EVM execution
identical frame receipt, gas use, and warm access sets
same admission result
Root removed by a reorganization
revert after revalidation
evict
Activation installs RECENT_ROOT_CODE with empty storage
subsequent writes and validation use RECENT_ROOT_CODE
revalidate; reject until every referenced entry exists
Activation finds empty code and storage with an existing balance and nonce
preserve balance and set nonce to max(existing_nonce, 1)
revalidate
Activation finds nonempty code or storage
first active block is invalid
not applicable
A later block follows successful activation
do not rerun initialization or clear storage
retain and revalidate normally
Reorganization crosses back before activation
this EIP is inactive
evict
The case with sixteen tuples must include the cold target account access charged at frame entry by EIP-8141, not only the contract’s internal execution.
Each of EIP-8141’s four recognized validation prefix shapes must be tested with no protocol verifier, with recent_root_verify, with expiry_verify, and with expiry_verify followed by recent_root_verify. Reversed or duplicate protocol verifiers must be rejected from the public mempool.
With storage[storage_key] = entry_hash, the validation operation succeeds.
Security Considerations
Consensus treats root as an opaque bytes32. Applications define what it commits to and should bind the expected (source_id, slot, root) tuple to the statement being authorized. Before treating frame data as verified, application validation should check the completed frame’s defining fields and success status as described in Application introspection. A successful recent root verifier frame proves only that the tuple is currently stored and recent.
EIP-8141’s canonical signature hash covers the frame data, but EIP-8141 also permits validation based on an explicit 32-byte signature message. Validation logic that does not authorize the canonical signature hash must bind the recent root verifier frame and every other relevant frame itself.
For each (source_id, slot), only the last root written in canonical execution order is referenceable. An application that needs multiple roots from the same source and slot should write an aggregate commitment.
This EIP does not guarantee inclusion of root writes. Applications that rely on timely publication need their own publication path, redundant root sources, or an inclusion policy for write transactions.
The same (source_address, salt) pair produces the same source_id on different chains, but each chain maintains its own recent root state. Proofs, bridge messages, and attestations produced off chain that carry recent root references must bind the intended chain domain outside the tuple.
Recent roots create persistent storage under RECENT_ROOT_ADDRESS. Existing root sources overwrite at most RECENT_ROOT_LENGTH cells, while new root sources create additional cells. A future proposal may add a source registration cost or a surcharge for the first write if ordinary state growth pricing is insufficient.
Because the recent root verifier frame counts toward MAX_VERIFY_GAS, EIP-8141 bounds the total work a node may do to check signatures and execute the validation prefix before admitting a transaction to the public mempool. A reorganization or synchronized expiry may evict many transactions that use one popular root, but dependency indexing lets clients identify affected transactions without rerunning unrelated validation prefixes.