This EIP enshrines two types of blobs into the protocol, namely Ahead-Of-Time (AOT) and Just-In-Time (JIT) blobs. In practice these exist today already. However, enshrining them in the way we do, has major benefits vis-a-vis performance and security guarantees. As the name suggests, AOT blobs are propagated ahead of time (pre-propagated) using a ticket-based propagation mechanism while current Just-In-Time (JIT) blobs are propagated on the critical path as today’s blobs. Blob data for either blob type is propagated through the CL subnets.
Motivation
Ethereum’s current blob data availability design, introduced in EIP-4844 and extended with PeerDAS (EIP-7594), hinges on blob data propagation on the critical path. While pre-propagation of blob data through the EL blobpool is possible, it comes with no upper bounds on the load being handled and as such can lead to inconsistent local node views - which in turn translates to vulnerability to DOS attacks especially if pre-propagation is coupled with data-availability sampling (DAS).
Ticket based, ahead-of-time propagation provides a way to overcome a number of issues associated with pre-propagation through the blobpool. Thus, it provides a reliable and secure way of combining DAS with pre-propagation. This in turn allows significantly higher blob throughput without increasing the block propagation window i.e extending the critical path - which comes with its own drawbacks e.g through the free option problem. The free option problem is understood as the ability of the builder to freely choose whether to invalidate a given block during the propagation window providing them the option of only following through when market conditions move in their favor resulting in potentially missed slots. The longer the propagation window, the more severe the problem can become.
This ticket based mechanism is based on the principle that holding a ticket is a prerequisit for the network to propagate one’s blob data. Tickets are acquired through a dedicated contract and as a result, there is at any time a globally agreed upon bounded set of eligible blob senders. Holding a ticket gives the user the right to propagate blob data using the consensus layer gossip subnets, effectively moving pre-propagation from the EL blobpool to the CL - utilizing the existing network infrastructure. Further, since all users pay in advance for the network resources consumed, by acquiring the corresponding ticket, the cost for honest and dishonest usage is equal. This ensures DoS resistance and establishes a common set of blobs seen by nodes in each slot. In addition, the system allows flexible timing for data propagation within a bounded time window which reduces congestion and enables higher overall throughput.
JIT blobs are retained for use cases that require last-moment data commitment, such as L2 sequencers that finalize batch contents close to the inclusion deadline. The two channels share a unified fee mechanism so that total blob demand is accurately reflected in pricing.
A single blob base fee governs both AOT and JIT blobs, updated via the existing fake_exponential mechanism but driven by the combined demand from JIT usage and AOT ticket sales. Today’s excess_blob_gas and blob_gas_used header fields are removed, with fee state moving entirely into the ticket contract. Separate per-block capacity limits (B1 for JIT, B2 total) allow flexible allocation between the two blob types while reserving minimum capacity ( R ) for JIT blobs.
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.
Parameters
Constant
Value
Description
TICKET_CONTRACT_ADDRESS
TBD
System contract address for blob ticket operations
ZERO_ADDRESS
0x0000000000000000000000000000000000000000
Zero address used to “burn” ticket payments
SYSTEM_ADDRESS
0xfffffffffffffffffffffffffffffffffffffffe
Address used for system calls
MAX_JIT_BLOBS_PER_BLOCK
TBD
Maximum JIT blobs per block (B1)
MAX_TOTAL_BLOBS_PER_BLOCK
TBD
Maximum total blobs per block (B2)
JIT_RESERVED
TBD
Minimum reserved capacity for JIT blobs (R)
MAX_AOT_BLOBS_PER_BLOCK
MAX_TOTAL_BLOBS_PER_BLOCK - JIT_RESERVED
Derived AOT blob cap
TICKET_LOOKAHEAD
TBD
Number of slots between ticket purchase and target slot
Blob transactions (Type 3, as defined in EIP-4844) are classified into two categories based on their max_fee_per_blob_gas field:
JIT: max_fee_per_blob_gas >= blob_base_fee (where blob_base_fee is the current blob base fee). The transaction pays the blob base fee at inclusion. The transaction’s blob data is propagated at block time.
AOT: max_fee_per_blob_gas == 0. The transaction does not pay any blob fee at inclusion. It MUST be backed by a valid ticket. The blob data is propagated ahead of time via the consensus layer.
A blob transaction with max_fee_per_blob_gas between 1 and blob_base_fee - 1 (inclusive) is invalid and MUST be rejected. A blob transaction MUST be entirely JIT or entirely AOT. The signed max_fee_per_blob_gas == 0 for AOT transactions prevents reclassification by the builder: the execution layer rejects any transaction with max_fee_per_blob_gas below the current blob base fee as a JIT transaction, so an AOT transaction cannot be included as JIT.
Note: Whether AOT blob transactions reuse Type 3 with max_fee_per_blob_gas == 0 or use a new transaction type without the blob fee field is a deferred design decision. Both approaches are mechanically equivalent.
Block Header Changes
The following fields, introduced in EIP-4844, are deprecated from the block header:
excess_blob_gas
blob_gas_used
The blob base fee state is maintained entirely within the ticket contract. Clients read the blob base fee from the contract at the start of each block and cache it for the duration of block processing.
Block Validation
Block validation MUST enforce the following rules for blob transactions:
defvalidate_blob_transactions(block:Block,bf_aot:int):jit_blob_count=0aot_blob_count=0fortxinblock.transactions:ifnotis_blob_tx(tx):continuen_blobs=len(tx.blob_versioned_hashes)iftx.max_fee_per_blob_gas>0:# JIT blob transaction
asserttx.max_fee_per_blob_gas>=bf_aot,"JIT blob fee too low"# Deduct and burn blob fee: n_blobs * GAS_PER_BLOB * bf_aot
blob_fee=n_blobs*GAS_PER_BLOB*bf_aotasserttx.sender.balance>=blob_feetx.sender.balance-=blob_fee# blob_fee is burned (not transferred to coinbase)
jit_blob_count+=n_blobselse:# AOT blob transaction
# No blob fee charged (already paid at ticket purchase)
aot_blob_count+=n_blobs# Capacity checks
assertjit_blob_count<=MAX_JIT_BLOBS_PER_BLOCK
Where blob_base_fee is the blob base fee read from the ticket contract at the start of the block.
EL Mempool Changes
Blob sidecars (the blob data alongside its KZG commitment and proof) are no longer propagated in the execution layer mempool. The EL mempool carries only blob transaction bodies (without blob data).
Propagation of any blob transaction in the EL mempool MUST be gated on ticket ownership. For each address, the mempool tracks a blob allowance: the sum of blob_count across all active tickets (i.e., tickets whose derived target_slot has not yet passed) owned by that address minus the sum of blob transactions corresponding to that address present in the mempool. A blob transaction is eligible for propagation only if that sender has a sufficient blob allowance.
Where pending_blobs(S) is the total blob count across all blob transactions from S already in the mempool, and total_tickets(S) is the sum of blob_count across all active tickets with owner == S. A mempool node SHOULD accept and relay a blob transaction from sender S only if it has sufficient blob allowance:
blob_allowance(S) > len(tx.blob_versioned_hashes)
This gating applies regardless of whether the blob transaction is JIT or AOT (i.e., regardless of max_fee_per_blob_gas). Without a ticket, an address has zero blob allowance and its blob transactions cannot propagate through the public mempool. JIT blob transactions without a ticket are delivered directly to the builder out of protocol.
Replacements
Replacement of blob transactions with a valid ticket is possible but can be carried out only a limited number of times since, unlike regular txs, it might never get on chain if the corresponding data has not propagated. The user has the choice of either sending a replacement transaction with the same versioned hashes or also changing the versioned hashes - and thus the blob content. In the later case, for the transaction to be valid the corresponding blob data would have to be sent - potentially using another ticket.
Opcode Changes
Opcode
Byte
Change
BLOBHASH
0x49
No change. Per-transaction indexing works for both JIT and AOT.
BLOBBASEFEE
0x4A
Returns the blob base fee cached from the ticket contract at block start.
The BLOBBASEFEE opcode continues to return the blob base fee, but the source changes from a header field computation to a contract state read cached at the beginning of block processing.
System Call Ordering
Block processing follows this order:
Start of block: The client reads blob_base_fee from the ticket contract and caches it. Existing system calls execute (beacon root contract per EIP-4788, block hash history contract per EIP-2935).
Transaction processing: JIT blob transactions are validated and charged the blob fee. AOT blob transactions are validated without blob fee charges. Ticket purchases are processed as normal transactions to the ticket contract.
End of block: The ticket contract system call executes with jit_blob_gas_used as input, updating the excess accumulator and blob base fee for the next block. Other end-of-block system calls follow (withdrawal request contract per EIP-7002, consolidation request contract per EIP-7251).
Ticket Contract
The ticket contract is a system contract deployed at TICKET_CONTRACT_ADDRESS via the synthetic-sender technique (same deployment method as EIP-4788 and EIP-7002).
Storage Layout
Slot
Name
Description
0
EXCESS_BLOB_GAS_SLOT
Running excess blob gas accumulator
1
CACHED_FEE_SLOT
Cached blob_base_fee for current block
2
TICKET_COUNT_SLOT
Per-block ticket counter (reset each block)
3
NEXT_TICKET_ID_SLOT
Monotonically increasing ticket ID
4
TICKET_RING_BUFFER_OFFSET
Start of per-bucket metadata storage
5
TICKET_DATA_OFFSET
Start of ticket data storage
The ring buffer is organized as a ring of per-slot buckets of length:
TICKET_RING_BUFFER_SIZE=TICKET_LOOKAHEAD
In slot s, all tickets purchased are for target slot:
target_slot=s+TICKET_LOOKAHEAD
Since the ring length is exactly TICKET_LOOKAHEAD, tickets purchased in slot s are always written into bucket:
bucket_index=s%TICKET_RING_BUFFER_SIZE
This bucket is exactly the one that becomes reusable once slot s is reached. To avoid clearing the bucket on every purchase within the same slot, the contract tracks the last slot for which the current-slot bucket was cleared. Each bucket occupies 2 metadata slots:
Offset
Field
Size
0
bucket_blob_count
uint256
1
bucket_entry_count
uint256
2
bucket_current_slot
uint256
There are TICKET_RING_BUFFER_SIZE many buckets. Each bucket tracks both the total reserved AOT blob capacity bucket_blob_count (< MAX_AOT_BLOBS_PER_BLOCK), the number of ticket records stored for that slot bucket_entry_count and the slot for which the tickets have been sold forbucket_current_slot. Each ticket occupies 5 storage slots:
The parameter target_slot determines the slot during which the ticket is valid and can be used to propagate a blob. The target slot for a ticket is derived as:
where TICKET_LOOKAHEAD is a parameter, measured in slots and can be set to a value e.g. 1 epoch. A ticket is valid only during that exact target_slot.
Code Paths
The contract has three code paths, following the pattern established by EIP-7002:
1. Buy Tickets (User Call with Value)
If the call has nonzero msg.value and calldata encoding (blob_count, bls_pubkey):
defbuy_tickets(blob_count:uint8,bls_pubkey:bytes48):fee=get_fee()total_cost=blob_count*GAS_PER_BLOB*feerequire(msg.value>=total_cost,"Insufficient payment")current_slot=slot(block.timestamp)bucket_index=current_slot%TICKET_RING_BUFFER_SIZEmeta_base=TICKET_RING_BUFFER_OFFSET+bucket_index*3bucket_blob_count=sload(meta_base+0)bucket_entry_count=sload(meta_base+1)bucket_current_slot=sload(meta_base+2)ifbucket_current_slot!=current_slot:sstore(meta_base+0,0)# bucket_blob_count set to zero
sstore(meta_base+1,0)# bucket_entry_count set to zero
sstore(meta_base+2,current_slot)# bucket_current_slot set to current_slot
require(bucket_blob_count+blob_count<=MAX_AOT_BLOBS_PER_BLOCK,"AOT capacity exceeded")# restrict the number of AOT blobs in a slot.
# Append ticket record to bucket
ticket_id=sload(NEXT_TICKET_ID_SLOT)bucket_base=TICKET_DATA_OFFSET+bucket_index*MAX_AOT_BLOBS_PER_BLOCK*5base=bucket_base+bucket_entry_count*5sstore(base+0,ticket_id)sstore(base+1,block.timestamp)sstore(base+2,msg.sender++blob_count)sstore(base+3,bls_pubkey[0:32])sstore(base+4,bls_pubkey[32:48])# Increment counters
sstore(meta_base+0,bucket_blob_count+blob_count)sstore(meta_base+1,bucket_entry_count+1)sstore(NEXT_TICKET_ID_SLOT,ticket_id+1)count=sload(TICKET_COUNT_SLOT)sstore(TICKET_COUNT_SLOT,count+blob_count)# Burn payment (value stays in contract or sent to a burn address)
transfer(to=ZERO_ADDRESS,value=msg.value)
No refunds are issued. This is the primary defense against capacity griefing.
2. Fee Getter (Zero-Value Call with Empty Calldata)
When called with zero value and empty calldata, the contract returns the current blob base fee:
defget_fee()->int:returnsload(CACHED_FEE_SLOT)
3. System Call (End of Block)
Called as SYSTEM_ADDRESS with jit_blob_gas_used (uint256) as calldata at the end of each block:
defsystem_call(jit_blob_gas_used:uint256):require(msg.sender==SYSTEM_ADDRESS)# Compute total blob gas for this block
aot_blob_gas_sold=sload(TICKET_COUNT_SLOT)*GAS_PER_BLOBtotal_blob_gas=jit_blob_gas_used+aot_blob_gas_sold# Update excess accumulator (with EIP-7918 reserve price logic)
old_excess=sload(EXCESS_BLOB_GAS_SLOT)old_fee=sload(CACHED_FEE_SLOT)ifold_excess+total_blob_gas<TARGET_BLOB_GAS:new_excess=0elifBLOB_BASE_COST*block.basefee>GAS_PER_BLOB*old_fee:# Below reserve price: do not subtract full target, only proportional
new_excess=old_excess+total_blob_gas*(MAX_TOTAL_BLOB_GAS-TARGET_BLOB_GAS)//MAX_TOTAL_BLOB_GASelse:new_excess=old_excess+total_blob_gas-TARGET_BLOB_GASsstore(EXCESS_BLOB_GAS_SLOT,new_excess)# Compute new blob base fee
new_fee=fake_exponential(MIN_BLOB_BASE_FEE,new_excess,BLOB_BASE_FEE_UPDATE_FRACTION)sstore(CACHED_FEE_SLOT,new_fee)# Reset per-block ticket counter
sstore(TICKET_COUNT_SLOT,0)
Ticket expiry semantics
Ticket expiry is tied directly to slot progression.
A ticket sold in slot s is valid only in slot:
slot(selling_block_timestamp)+TICKET_LOOKAHEAD
Because the ring buffer length is exactly TICKET_LOOKAHEAD, the bucket used for purchases in slot s is reused only when slot s + TICKET_LOOKAHEAD arrives. The bucket is cleared once on the first purchase in slot s, so earlier tickets from the previous cycle are removed without erasing purchases already made in the current slot. The bucket metadata separately tracks reserved blob capacity (bucket_blob_count) and stored ticket records (bucket_entry_count), which avoids holes in the ticket storage layout when a single purchase reserves multiple blobs.
Fee Mechanism
A single blob base fee base_fee governs both JIT and AOT blobs:
Blob type
When paid
Amount
Destination
JIT
At transaction inclusion
blob_count * GAS_PER_BLOB * base_fee
Burned
AOT base fee
At ticket purchase
blob_count * GAS_PER_BLOB * base_fee
Burned
AOT at inclusion
N/A
Nothing
N/A
Both JIT blob gas usage and AOT ticket sales feed into the excess accumulator via the end-of-block system call. The fee responds to total blob demand across both channels.
The EIP-7918 reserve price mechanism is implemented inside the ticket contract’s excess accumulator update. When BLOB_BASE_COST * base_fee_per_gas > GAS_PER_BLOB * current_blob_base_fee (i.e., the blob fee is below the reserve price), the accumulator subtracts less than the full TARGET_BLOB_GAS, keeping excess elevated and preventing the fee from falling further.
Consensus Layer Changes
The following consensus layer changes are described at a high level. The full specification is provided in the companion consensus layer specification.
Execution Payload Modifications
Under EIP-7732 (ePBS), the ExecutionPayloadBid is modified:
The existing blob_kzg_commitments field is replaced with:
jit_blob_kzg_commitments: List of KZG commitments for JIT blobs (included in the bid, needed for critical-path data column propagation before payload reveal).
aot_blob_kzg_commitments_root: The hash_tree_root of the AOT KZG commitments list (cryptographic binding in the bid; full list provided in the envelope since AOT data is already pre-propagated).
The ExecutionPayloadEnvelope is extended with:
aot_blob_kzg_commitments: Full list of AOT KZG commitments.
New gossip subnets aot_data_column_sidecar_{subnet_id} are introduced for AOT blob data propagation. A new container AotDataColumnSidecar carries the column data, KZG proofs, commitments, target slot, ticket ID, and BLS signature.
Validation rules for AOT gossip subnets include:
The target slot MUST be within [current_slot, current_slot + AOT_PROPAGATION_WINDOW_SLOTS].
The ticket ID MUST correspond to a valid ticket (obtained via the engine API).
The BLS signature MUST be valid for the ticket’s registered public key over the commitments and ticket ID.
KZG proof verification MUST pass.
The sidecar MUST be on the correct subnet.
Only the first valid sidecar per (ticket_id, column_index) is accepted.
JIT data columns continue to propagate on the existing data_column_sidecar_{subnet_id} subnets at block time, validated against the jit_blob_kzg_commitments from the ExecutionPayloadBid. Both JIT and AOT use a shared custody model (same column indices).
Data Availability
The is_data_available check is extended to verify availability of both JIT and AOT data columns. A single unified boolean is used for Payload Timeliness Committee (PTC) attestation.
Engine API Changes
The following engine API changes are described at a high level. The full specification is provided in the companion engine API specification.
engine_forkchoiceUpdatedV5
The response is extended with an activeTickets field: an array of TicketInfoV1 objects containing ticketId, sellingBlockTimestamp, owner (address), blsPubkey, and blobCount. The CL uses the BLS pubkey to gate AOT gossip validation; the EL uses the owner address to gate blob transaction propagation in the mempool. Returned on every forkchoiceUpdated call when payloadStatus is VALID; null when syncing or pre-fork.
PayloadAttributesV5
Extended with availableAotBlobCommitments: an array of KZG commitments for which the CL has pre-propagated data available. The builder SHOULD only include AOT blob transactions whose commitments appear in this list.
engine_newPayloadV6
Accepts two separate versioned hash lists: expectedJitBlobVersionedHashes and expectedAotBlobVersionedHashes. The EL validates both against the blob transactions in the payload.
engine_getPayloadV7
The blobsBundle contains JIT blob data only. A new aotBlobInfo field provides an array of AotBlobInfoV1 objects (versioned hash, KZG commitment, ticket ID) so the CL can match AOT blobs against its pre-propagated data cache.
engine_getBlobsV3
No structural change. Queries for AOT versioned hashes return null from getBlobsV3 since the EL does not hold AOT blob data.
Rationale
Unified fee mechanism
A single blob base fee for both JIT and AOT channels ensures that total demand is accurately reflected in pricing. If JIT and AOT had separate fees, demand could shift between channels to exploit price differences, creating an inefficient two-tier market. By combining jit_blob_gas_used and aot_blob_gas_sold as inputs to the same excess accumulator, the fee mechanism treats all blob demand symmetrically.
Fee state in a system contract
Moving the blob base fee from header fields (excess_blob_gas, blob_gas_used) into a system contract simplifies the fee update logic. The ticket contract naturally needs to compute the fee for ticket purchases, and co-locating the fee state avoids duplication. The BLOBBASEFEE opcode reads from a cached value set at block start, preserving identical EVM semantics for existing contracts.
Non-refundable tickets
Ticket fees are burned at purchase time with no refund mechanism. This is the primary defense against capacity griefing: an attacker who buys tickets to reserve capacity but never propagates data still pays the full blob base fee. The cost of griefing thus scales with the cost of legitimate usage, making sustained attacks economically prohibitive.
Separate JIT and AOT capacity limits
Three capacity parameters (B1, B2, R) provide flexibility:
MAX_JIT_BLOBS_PER_BLOCK (B1) caps JIT blobs to bound critical-path propagation latency.
MAX_TOTAL_BLOBS_PER_BLOCK (B2) caps total blobs for overall data availability.
JIT_RESERVED ( R ) ensures minimum JIT capacity even when AOT demand is high, preventing AOT from fully crowding out JIT usage.
The AOT cap B2 - R can then be derived.
BLS key commitment in tickets
Tickets commit to a BLS public key rather than directly to KZG commitments. This allows the ticket holder to defer data preparation until after purchase, while still preventing unauthorized use of the ticket. The BLS signature over (kzg_commitment, ticket_id) binds specific data to a specific ticket at propagation time.
Ring buffer storage
Ring buffer storage for tickets follows the pattern established by EIP-4788 and EIP-2935. Tickets expire naturally as the ring buffer rotates, eliminating the need for explicit cleanup. The buffer size is set to accommodate the maximum number of concurrent active tickets: TICKET_LOOKAHEAD * MAX_AOT_BLOBS_PER_BLOCK.
No target slot in contract storage
The ticket contract records the selling block’s timestamp but not the target slot. The target slot slot(selling_block_timestamp) + TICKET_LOOKAHEAD is derived externally by the CL and EL mempool. This keeps the contract simpler and avoids coupling it to slot timing, which is a CL concept.
Capacity parameter communication
The capacity parameters MAX_JIT_BLOBS_PER_BLOCK, MAX_TOTAL_BLOBS_PER_BLOCK, and JIT_RESERVED follow the pattern established by EIP-7742, where blob count limits are communicated from the CL to the EL via the engine API rather than being hardcoded in the EL. This allows the CL to adjust parameters without EL client updates.
Censorship resistance
This EIP does not include mechanisms for mandatory inclusion of AOT blob transactions. While ticket transactions can be included in FOCIL ILs (EIP-7805), a builder can still censor by observing that AOT data is available yet choosing not to include the corresponding transaction. Addressing this requires forced inclusion lists and PTC availability voting, which are left to future work to avoid coupling this proposal to those designs.
Backwards Compatibility
This EIP introduces several breaking changes:
Block header format: The removal of excess_blob_gas and blob_gas_used fields changes the block header RLP encoding. All EL clients MUST be updated.
Blob fee computation: Contracts and tooling that compute the blob base fee from header fields MUST be updated to read from the ticket contract or use the BLOBBASEFEE opcode.
Blob transaction processing: The split into JIT and AOT classification changes how blob transactions are validated. Existing blob transactions with max_fee_per_blob_gas > 0 continue to work as JIT blobs.
EL mempool: The removal of blob sidecars from the EL mempool changes how blob data is submitted. Blob data MUST be sent directly to builders (JIT) or propagated via the CL (AOT).
Existing contracts that use BLOBHASH (0x49) or BLOBBASEFEE (0x4A) continue to function without modification. The opcodes’ semantics are preserved.
Security Considerations
Capacity griefing via ticket purchase
An attacker could purchase AOT tickets without ever propagating the corresponding blob data. The non-refundable ticket fee is the primary mitigation: the cost of griefing equals the cost of legitimate usage. Additionally, since builders choose which AOT blob transactions to include, unfulfilled tickets (those without available data) are simply not included, and the capacity reverts to availability for JIT blobs up to the total cap B2. In the future we might want to introduce a deposit which gets returned uppon using a ticket thus having a cost of griefing that is greater than the cost of legitimate cost.
Fee manipulation
Because both JIT usage and AOT ticket sales drive the fee update, an attacker could attempt to manipulate the blob base fee by purchasing tickets (inflating the fee) or withholding JIT transactions (deflating it). The combined demand signal makes this more expensive than manipulating either channel alone. The fake_exponential update function, inherited from EIP-4844, provides the same manipulation resistance properties as the current blob fee mechanism.
Builder centralization
JIT blob transactions are sent directly to builders rather than propagated through the public mempool. This increases builders’ role as gatekeepers for JIT blob inclusion. However, this reflects the existing trajectory under EIP-7732 (ePBS) and is mitigated by the AOT channel. In the future we might want to add censorship-resistance mechanism to AOT blobs making these even less reliable on builder cooperation.
AOT gossip validation
AOT data column sidecars require ticket-gated and BLS signature validation on the CL gossip layer. Invalid sidecars MUST be rejected to prevent amplification attacks. The validation cost (BLS signature verification plus KZG proof verification) is bounded by the maximum number of active tickets and the propagation window. Rate limiting by (ticket_id, column_index) prevents replay.
Ticket front-running
A ticket purchase reveals the buyer’s intent to use blob capacity at a future slot. Front-runners could observe pending ticket purchases and buy tickets first, increasing the fee. This risk is comparable to existing mempool front-running of any fee-paying transaction and is mitigated by the same techniques (private transaction submission, MEV-protection services).
Reorg handling
On a chain reorganization, ticket state may change: tickets from reorged blocks are invalid, and the blob base fee may differ on the new chain. The engine API’s forkchoiceUpdated response provides the updated ticket set, and CL nodes MUST refresh their ticket cache on reorgs. AOT data already propagated for tickets that become invalid is harmlessly ignored.