Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7863: Block-level Warming

Warm addresses and storage keys over the duration of a block

Authors Toni Wahrstätter (@nerolation), Jochem Brouwer (@jochem-brouwer), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs)
Created 2025-01-15
Discussion Link https://ethereum-magicians.org/t/eip-7863-block-level-warming/22572

Abstract

This proposal introduces block-level address and storage key warming, allowing accessed addresses and storage keys to maintain their “warm” status throughout an entire block’s execution. Accessed slots can be effectively cached at the block level, allowing for this opimization.

Motivation

Currently, the EVM’s storage slot warming mechanism operates at the transaction level, requiring each transaction to “warm up” slots independently, even when accessing the same storage locations within the same block. This design does not take advantage of the fact that modern node implementations can effectively cache storage access patterns at the block level. By extending the slot warming duration to the block level, we can:

  1. Reduce redundant warming costs for frequently accessed slots
  2. Better align gas costs with actual computational overhead
  3. Improve overall network throughput without compromising security.

As of Jan. 2025, the empirically messured efficiency gains are around 5%.

Specification

Mechanics

When a storage slot is accessed within a block:

  1. The first access to a slot in a block incurs the cold access cost as of EIP-2929.
  2. All subsequent accesses to the same slot within the same block incur only the warm access cost as of EIP-2929.
  3. The warm/cold status resets at block boundaries

Block Processing

  1. At the start of each block:
    • Initialize two empty sets block_level_accessed_addresses and block_level_accessed_storage_keys
  2. For each transaction in the block:
    • Before processing storage access:
      • Check if touched address is in block_level_accessed_addresses
      • If yes: charge WARM_STORAGE_READ_COST
      • If no:
        • Charge COLD_ACCOUNT_ACCESS_COST
        • Add address to block_level_accessed_addresses
      • Check if storage key is in block_level_accessed_storage_keys
      • If yes: charge WARM_STORAGE_READ_COST
      • If no:
        • Charge COLD_SLOAD_COST
        • Add storage key to block_level_accessed_storage_keys

Implementation Details

The implementation modifies the block execution process to maintain block-level sets of accessed addresses and storage slots.

Block-Level Storage Management

def apply_body(...):
    # Initialize block-level tracking sets
    block_level_accessed_addresses = set()
    block_level_accessed_storage_keys = set()
    
    for i, tx in enumerate(map(decode_transaction, transactions)):
        # Create environment with block-level context
        env = vm.Environment(
            # ... other parameters ...
            block_level_accessed_addresses=block_level_accessed_addresses,
            block_level_accessed_storage_keys=block_level_accessed_storage_keys
        )
        
        # Process transaction and update block-level sets
        gas_used, accessed_addresses, accessed_storage_keys, logs, error = process_transaction(env, tx)
        block_level_accessed_addresses += accessed_addresses
        block_level_accessed_storage_keys += accessed_storage_keys

This code shows how block-level slot warming is implemented at the execution layer. The block_level_accessed_addresses and block_level_accessed_storage_keys sets are maintained throughout the block’s execution and passed to each transaction’s environment.

Transaction Processing

def process_transaction(env: vm.Environment, tx: Transaction) -> Tuple[Uint, Tuple[Log, ...], Optional[Exception]]:
    preaccessed_addresses = set()
    preaccessed_storage_keys = set()
    
    # Add block-level pre-accessed slots
    preaccessed_addresses.add(env.block_level_accessed_addresses)
    preaccessed_storage_keys.add(env.block_level_accessed_storage_keys)
    
    # Handle access lists from transaction
    ...

This adds the block-level-accessed addresses and storage keys to the preaccessed addresses and storage keys. As a result, from the perspective of a transaction, block-level accessed addresses and storage keys are treated the same as precompiles or the coinbase address.

def process_message_call(message: Message, env: Environment) -> MessageCallOutput:
    return MessageCallOutput(
        # ... other fields ...
        accessed_addresses=evm.accessed_addresses,
        accessed_storage_keys=evm.accessed_storage_keys
    )

The message call processing tracks accessed addresses and storage keys during execution, which are then propagated back up to the transaction level and ultimately to the block level.

Rationale

The proposal builds on several key observations:

  1. Caching Efficiency: Modern Ethereum clients already implement sophisticated caching mechanisms at the block level. Extending address and storage key warming to match this caching behavior better aligns gas costs with actual computational costs.

  2. Backward Compatibility: The worst-case scenario for any transaction remains unchanged - it will never pay more than the current cold access cost for its first access to a slot.

  3. Handling Reverts: In EIP-2929, if a sub-call reverts, all accessed addresses and slots are reverted to a cold state. Under block-level warming, it remains an open question whether a failing transaction (i.e., one that reverts) should ‘unwarm’ previously warmed addresses and slots for subsequent transactions within the same block. Some propose removing this revert-induced “cold reset” altogether to maintain consistency with block-level warming. This point warrants further discussion, particularly around whether or not preserving a warmed state even upon failure aligns with the broader goal of better matching gas costs to actual client caching behavior.

  4. First Access Warms For All: Under the proposed mechanism, the first transaction in a block that accesses and warms multiple addresses or storage slots bears the entire cost of warming, even if subsequent transactions benefit from those already-warmed slots without incurring additional costs. This approach is deemed acceptable because early execution within a block—where warming typically occurs—is generally occupied for transactions placed by professional builders aiming for top-of-block execution. Since the resulting cost discrepancy is relatively minor, a more complex cost-sharing model is avoided in favor of maintaining simplicity. An alternative to the one-warms-for-all mechanism involves distributing the warming costs across all accesses within a block. In this approach, each transaction must at least be able to pay the cold access cost. Once all transactions in the block are executed, the total warming costs are evenly distributed, and transaction originators are refunded accordingly.

  5. Alternative Block Warming Windows: Instead of applying warming at the block level, more advanced multi-block warming approaches can be considered. Potential options include:

    • Warming addresses and storage keys over the duration of an epoch
    • Using a ring buffer spanning x blocks

    Since the Execution Layer (EL) currently operates without regard to epoch boundaries, it may be preferable to maintain this design. Therefore, the second option, involving a ring buffer of size x, could be more suitable. For the ring buffer, one approach might be to store the relevant warmed information in the block header for those x blocks, but this introduces additional overhead and can complicate statelessness. Careful design would be required to manage these trade-offs (e.g., header size, client complexity) if a multi-block warming solution were adopted.

Backwards Compatibility

This change is not backward compatible and requires a hard fork activation. However, it does not introduce any breaking changes for existing contracts, as:

  1. All existing transactions will continue to work as before
  2. Gas costs will only decrease, never increase
  3. Contract logic depending on gas costs will remain valid, as the worst-case scenario is unchanged

Security Considerations

  1. Memory Usage: The set of warm slots is still bounded by block gas limit / minimum gas cost per slot access, preventing DoS attacks through excessive memory usage.

  2. MEV Considerations: Block producers can optimize transaction ordering to maximize slot warming benefits for themselves. Third-party block builders can backrun transactions that warmed specific addresses or storage keys.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Toni Wahrstätter (@nerolation), Jochem Brouwer (@jochem-brouwer), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), "EIP-7863: Block-level Warming [DRAFT]," Ethereum Improvement Proposals, no. 7863, January 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7863.