Post-Execution Transactions
Table of Contents
- Overview
- The Post-Execution Transaction Type
- Post-Exec Payload Envelope
- Block-Level Structural Rules
- DA Footprint
- Receipt
- Derivation
- Subblocks
- Rationale
Overview
Post-execution transactions are an EIP-2718 transaction type that lets a block carry sequencer-provided consensus data. Unlike user-submitted or deposited transactions, a post-exec transaction is created by the sequencer and appended to the block as its final transaction. A verifier applies its data as part of the block's state transition.
The post-exec transaction type is introduced by the Lagoon network upgrade, together with its
first payload schema. Before the Lagoon activation timestamp a block MUST NOT contain a 0x7D transaction.
The post-exec transaction is a generic envelope: it carries a versioned post-exec payload
whose interpretation is defined by separate policy specifications. Today only one schema is defined,
Sequencer-Defined Metering (version = 1), specified in sdm.md. Future policies extend
this document by defining additional schema versions.
This document specifies the envelope: the transaction type, the encoding, and the structural invariants that hold regardless of the active schema. It does not specify what the payload fields mean — that is the responsibility of the active schema's specification.
The Post-Execution Transaction Type
A post-execution transaction is an EIP-2718 typed transaction with type byte 0x7D (decimal 125).
Type byte 0x7D was selected because EIP-2718 transaction type identifiers may use values up to 0x7F; choosing a
high identifier minimizes the chance of collision with future Ethereum L1 transaction types. 0x7E is reserved for
deposited transactions, and 0x7F is left unused in case it is
later assigned to a variable-length encoding scheme.
Encoding
The EIP-2718 encoding of a post-exec transaction is:
0x7D || rlp_encoded_payload
where rlp_encoded_payload is the RLP encoding of the post-exec payload as a list,
and || denotes byte concatenation. The type byte is immediately followed by the payload's own RLP list; the
payload is not wrapped in an additional outer RLP list.
Transaction Hash
The transaction hash of a post-exec transaction is:
keccak256(0x7D || rlp_encoded_payload)
The hash is computed over the full EIP-2718 encoding, so the type byte is included in the hash preimage. Because the hash is a function of the payload alone, the payload's block number is what distinguishes payloads anchored to different block numbers. (Two blocks at the same height on competing forks that carry an identical payload share the same post-exec transaction hash, exactly as a regular transaction would.)
Generic Transaction Interface Representation
A post-exec transaction carries only a versioned post-exec payload; it has none of
the fields a user transaction carries (nonce, gas price, recipient, signature, …). When surfaced through a generic
transaction interface (e.g. eth_getTransactionByHash), it is represented by a minimal object containing only:
| Field | Value |
|---|---|
type | 0x7D |
hash | the transaction hash |
from | 0x0000000000000000000000000000000000000000 (zero address) |
gas | 0 |
value | 0 |
input | the RLP-encoded payload bytes |
Standard transaction fields that do not apply — including nonce, chainId, gasPrice, maxFeePerGas,
maxPriorityFeePerGas, to, accessList, and the signature fields — are omitted, not reported with placeholder
values. Block-context fields (blockHash, blockNumber, transactionIndex) are populated as for any other
included transaction.
A post-exec transaction never charges fees, never debits or credits an account simply by being included, and consumes no gas from the block gas pool. Any side effects on account balances are defined by the active schema, not by this envelope.
Signer and Signature
A post-exec transaction has no signer and no signature: there is no (v, r, s) triple in its EIP-2718 encoding or
in the transaction-hash preimage. Rather than a per-transaction signature, it is trusted because the block that
contains it is — unsafe blocks are gossiped in payloads signed by the sequencer, and safe blocks are derived from
the (signed) batcher transaction. The sequencer places it at a position that satisfies the
block-level structural rules.
Its recovered sender is the zero address 0x0000000000000000000000000000000000000000, surfaced as the transaction
object's from field. The signature fields are omitted from transaction responses rather than reported as zero.
Mempool and Propagation
A post-exec transaction is constructed by the sequencer as part of block production. Nodes MUST NOT accept
post-exec transactions through public transaction-pool interfaces (e.g. eth_sendRawTransaction) and MUST NOT
propagate them through transaction-gossip protocols. A post-exec transaction reaches verifiers only by being
included in a block.
Post-Exec Payload Envelope
The post-exec payload is RLP-encoded as a list whose first two fields are fixed:
[version, blockNumber, ...schema-defined fields...]
| Field | Type | Description |
|---|---|---|
version | uint8 | Schema version selecting the layout of the trailing fields. |
blockNumber | uint64 | The L2 block number this payload is anchored to. |
| trailing | varies | Defined by the active schema version. |
The leading two fields define the envelope; all remaining fields belong to the schema selected by version.
Schema Version
version is the post-exec payload schema version. When the new payload schema calls
for additional or different fields, a new version number is assigned and the new layout is documented as a
defined schema version.
Block Number
blockNumber anchors the payload to the L2 block number of the containing block. The anchoring serves two
purposes:
- It guarantees that otherwise identical payloads anchored to different block numbers have distinct transaction hashes.
- It detects misordered or replayed payloads at decode time, before any schema-specific validation runs.
The normative rule that blockNumber equals the containing block's number is stated in
Block-Level Structural Rules.
Defined Schema Versions
version | Schema |
|---|---|
1 | Sequencer-Defined Metering |
No other schema versions are currently defined. SDM (version = 1) is introduced by the
Lagoon network upgrade; see Overview.
Block-Level Structural Rules
The following rules hold for every block, regardless of which schema version is active. Any violation invalidates the block.
- At most one. A block contains at most one transaction with type byte
0x7D. - Last in block. When a
0x7Dtransaction is present, it MUST be the final transaction of the block. - Anchored to block. The
blockNumberfield of the embedded payload MUST equal the L2 block number of the containing block. - Recognized schema. The payload's
versionMUST be a defined schema version. - Schema must be active. When no schema version is active for the block's timestamp, the block MUST NOT
contain a
0x7Dtransaction.
Schema-specific validity rules (e.g. constraints on the trailing fields) are layered on top of these envelope rules and are specified by each schema's document. Both layers MUST hold for the block to be valid.
DA Footprint
The Jovian DA footprint block limit is modified to exclude
post-exec transactions. When computing a block's daFootprint, clients MUST treat a post-exec transaction as
having a DA footprint of zero. Clients MUST therefore skip transactions of type 0x7D when accumulating the
block's daFootprint, and a post-exec transaction's receipt MUST report blobGasUsed as zero.
Receipt
A post-exec transaction emits a receipt with type byte 0x7D.
Consensus Fields
The RLP-encoded consensus fields of the receipt are identical to those of an EIP-1559 receipt:
postStateOrStatus(EIP-658)cumulativeGasUsedlogsBloomlogs
A post-exec transaction is constructed by the protocol; it is not executed as EVM code, emits no logs, and consumes no gas from the block gas pool, so its own receipt records none of these:
postStateOrStatusMUST encode success (EIP-658 status1).logsMUST be empty.logsBloomMUST be the all-zero bloom filter.cumulativeGasUsedMUST equal thecumulativeGasUsedof the immediately preceding transaction's receipt. (In any L2 block the post-exec transaction has at least one preceding transaction — the L1 attributes deposit — so there is always a previous receipt to inherit from.)
The post-exec receipt participates in the block's receipts trie like any other receipt. The transaction's payload, however, is consensus-critical and drives state changes under the active schema — for SDM, the per-transaction fee settlement. Those changes are applied atomically with the transactions they refund, so they belong to those transactions' state deltas, not to a separate post-exec state transition. Schema-specific data is likewise surfaced on those transactions' receipts, not on the post-exec receipt.
JSON-RPC Fields
The receipt that eth_getTransactionReceipt and eth_getBlockReceipts return carries fee fields beyond the
consensus fields above. They fall into two groups, and a post-exec receipt reports the two groups differently: a
post-exec transaction pays no fees and is charged no gas and no DA footprint, but it sits in a block whose L1 fee
parameters are the same for every transaction in it.
Transaction-Scoped Fields
These describe what this transaction was charged. A post-exec transaction is charged nothing, so each of them MUST be present and zero:
| Field | Value |
|---|---|
gasUsed | 0 |
effectiveGasPrice | 0 |
l1Fee | 0 |
l1GasUsed | 0 |
blobGasUsed | 0, per § DA Footprint |
Clients MUST NOT omit l1Fee or l1GasUsed instead of reporting them as zero. Keeping them present and zero makes
a post-exec receipt the same shape as a regular transaction's receipt and matches gasUsed and effectiveGasPrice,
which are reported as zero rather than omitted. A consumer that sums l1Fee over a block's receipts then reaches
the same total whether or not it special-cases the post-exec receipt.
cumulativeGasUsed is also transaction-scoped, but it is block-cumulative rather than per-transaction: it inherits
the preceding receipt's value as specified in § Consensus Fields, and is therefore not zero.
opGasRefund is surfaced only on the receipts of the transactions that a refund applies to, so a post-exec receipt
MUST omit it or report it as null (see sdm.md § Receipt Extension).
Block-Scoped Fields
These describe the block's L1 fee parameters, read from the L1 attributes deposit. They are identical for every transaction in the block, so a post-exec receipt MUST report each of them with the same value — and the same presence or absence — as every other receipt in the same block:
l1GasPricel1BaseFeeScalarl1BlobBaseFeel1BlobBaseFeeScalaroperatorFeeScalaroperatorFeeConstantdaFootprintGasScalar
l1FeeScalar is reported only before Ecotone. Post-exec transactions require Lagoon,
which activates after Ecotone, so a post-exec receipt never reports it.
Derivation
Post-exec transactions are constructed by the sequencer during block production and travel inside the L2 block body — through both the unsafe p2p payload and the L1 batch — rather than being synthesized from L1 events the way deposited transactions are. They are included in the block payload that is submitted to the data availability layer alongside the user transactions and any deposited transactions.
The L1 batcher transaction format is unaffected: post-exec transactions appear inside L2 blocks, never as L1 batcher transactions. The future-tx-type decoding range described in derivation.md governs L1 receipts only and is unchanged.
Because a post-exec transaction is carried in the L2 block body, a span batch covering
that block must transpose it into the span batch txs structure. A post-exec transaction has no nonce, gas limit,
recipient or signature, so most of the per-transaction slots that structure reserves have no natural value for it.
The values they take, and the reconstruction rules that follow, are specified in
Span Batch Updates.
Subblocks
Subblocks stream an L2 block while the sequencer is still building it. A post-exec transaction is a function of the block's contents, so the sequencer recomputes it every time it extends the in-progress block. This section specifies how it is exposed on that stream. It constrains the subblock wire format only; it does not change any rule about the sealed block.
The decisions below are normative. Each is followed by a rationale and a consumer implication. The rationales are non-normative and subject to change; they are recorded so a consumer can tell why the field sits where it does without having to ask.
The post-exec transaction is carried in diff
A subblock exposes the in-progress block's post-exec transaction as diff.post_exec_tx, holding its
EIP-2718 encoding — the 0x7D type byte followed by the RLP-encoded payload. It is a field of
SubblockDelta, not a member of diff.transactions.
diff.post_exec_tx is absent when the in-progress block carries no post-exec transaction. Under SDM this is the
case whenever the sequencer has assigned no gas refunds, since a version-1 payload with an empty
gasRefundEntries list is invalid and no post-exec transaction is appended at all.
Rationale (non-normative, subject to change). A subblock is not a block. Its transactions are append-only and
immutable once streamed, whereas its diff describes the cumulative in-progress block and is restated by every
subblock. A post-exec transaction is derived from the state after everything executed so far, so its value is
recomputed as subblocks are added. That makes it mutable data, which is what diff is for.
Consumer implication. Read the post-exec transaction from diff, and expect its value to change from subblock to
subblock within one payload_id. Treat an absent post_exec_tx as "this block has no post-exec transaction so
far", not as an error and not as "not yet computed". Absence is not sticky either: a later subblock of the same
payload_id may introduce the field once a refund becomes due.
The post-exec transaction never appears in transactions
diff.transactions MUST NOT contain a 0x7D transaction, in any subblock, at any index.
Rationale (non-normative, subject to change). Placing it in transactions would require the sequencer to know
which subblock is the last one for the block, which it does not know while building. Appending it to an
append-only list in a subblock that turns out not to be last would publish a transaction that a later subblock
supersedes, and a consumer concatenating transactions across subblocks would reconstruct a transaction list
containing several 0x7D transactions in non-final positions — a list that violates the
block-level structural rules the sealed block satisfies.
Consumer implication. Do not look for the post-exec transaction in transactions, and do not expect the
concatenation of diff.transactions across a payload's subblocks to equal the sealed block's transaction list:
it is that list minus its post-exec transaction. transactions continues to carry every other transaction of the
block, including the deposited transactions in the first subblock.
Only the last subblock's post-exec transaction is canonical
The diff.post_exec_tx of the last subblock of a payload is the post-exec transaction of the sealed block. The
value carried by any earlier subblock is provisional.
Rationale (non-normative, subject to change). Each subblock's value reflects the block contents at that point in the build. Only the final contents determine the transaction that is actually included, and the payload is anchored to the block number rather than to any subblock, so intermediate values are not independently meaningful.
Consumer implication. The stream carries no marker identifying the last subblock of a payload, and the number of
subblocks per block is a target rather than a guarantee — a block may carry one fewer
or one more than usual. A consumer therefore MUST NOT treat any subblock's post_exec_tx as final while the block
is still being built. Determine that the block was sealed by other means, such as observing the next payload_id
or the canonical block arriving through normal L2 block propagation, and note that the in-progress block may be
abandoned rather than sealed, in which case no value from it was ever canonical.
A subblock's transactions may be empty
A subblock MAY have transactions: []. This applies to subblocks after index 0; the first subblock always
carries at least the block's deposited transactions.
Rationale (non-normative, subject to change). A subblock carries a state diff and the current post_exec_tx
whether or not it added transactions. A producer emits one per round regardless, so that the stream's cadence
does not depend on transaction arrival and consumers get a heartbeat during quiet rounds. Suppressing those
rounds would also withhold the updated diff.
Consumer implication. Handle an empty transactions list as ordinary: it is neither an error nor a signal that
nothing changed. Such a subblock still restates diff, including the current post_exec_tx, and still advances
index.
No receipt is streamed for the post-exec transaction
metadata.receipts MUST NOT contain an entry for a post-exec transaction. It covers the transactions in
diff.transactions only.
Rationale (non-normative, subject to change). Same as the reason it is absent from transactions: a receipt for
a provisional post-exec transaction would be superseded, and its
cumulativeGasUsed is inherited from the preceding transaction's receipt, so the value would shift as
later subblocks add transactions. Consumers can obtain the canonical receipt from the sealed block.
Consumer implication. Fetch the post-exec transaction's receipt from the sealed block rather than from the subblock stream. Do not infer from the missing receipt that the transaction failed or was dropped.
Rationale
Why a versioned payload. A version byte at the head of the payload lets the schema extend or replace its
trailing fields without re-spending an EIP-2718 type byte.
Why last in block. Placing the post-exec transaction at the end of the block gives it a unique, predictable position and matches the natural "after everything else" semantics of the data it carries.
Why exclude post-exec transactions from the DA footprint. A post-exec transaction is constructed only after the block's standard transactions have executed and its schema-defined payload is known. Including it in the DA footprint would require block builders to reserve or recompute footprint at finalization and would add a special late-stage accounting path for both producers and verifiers. Excluding it avoids that complexity at the cost of a small, bounded underestimate. A block contains at most one post-exec transaction, and the version-1 SDM payload contains at most one bounded-size entry per standard transaction, so the omitted encoded data grows at most linearly with the number of transactions in the block. The DA footprint is an estimate rather than an exact compressed-size calculation, and this bounded error does not materially change its purpose as a block-level limit.