From cfd67714de6adc9e6868a07f0a4e9233af051929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= Date: Wed, 15 Jan 2025 17:33:00 +0100 Subject: [PATCH 1/4] Add block-warming EIP --- EIPS/eip-9999.md | 150 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 EIPS/eip-9999.md diff --git a/EIPS/eip-9999.md b/EIPS/eip-9999.md new file mode 100644 index 00000000000000..a9be288563a7d3 --- /dev/null +++ b/EIPS/eip-9999.md @@ -0,0 +1,150 @@ +--- +eip: 9999 +title: Block-level warming +description: Warm addresses and storage keys over the duration of a block +author: Toni Wahrstätter (@nerolation), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs) +discussions-to: "" +status: Draft +type: Standards Track +category: Core +created: 2025-01-15 +--- + +## 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](./eip-2929). +2. All subsequent accesses to the same slot within the same block incur only the warm access cost as of [EIP-2929](./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 `GAS_WARM_ACCESS` + * If no: + * Charge `GAS_COLD_ACCOUNT_ACCESS` + * Add address to `block_level_accessed_addresses` + * Check if storage key is in `block_level_accessed_storage_keys` + * If yes: charge `GAS_WARM_ACCESS` + * If no: + * Charge `GAS_COLD_SLOAD` + * 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 + +```python +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 + +```python +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. + +```python= +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. **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. + +4. **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. + +## 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 + +## Reference Implementation + +TBD, first draft here: +https://github.com/nerolation/execution-specs/tree/block-level-warming + +## 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. + +3. **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 + +Copyright and related rights waived via [CC0](../LICENSE.md). From 4fa8783b0be1c3e3352929a66dd74132440fa1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= Date: Wed, 15 Jan 2025 17:36:18 +0100 Subject: [PATCH 2/4] Add block-warming EIP --- EIPS/eip-9999.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-9999.md b/EIPS/eip-9999.md index a9be288563a7d3..6ac7811be56e78 100644 --- a/EIPS/eip-9999.md +++ b/EIPS/eip-9999.md @@ -98,7 +98,7 @@ def process_transaction(env: vm.Environment, tx: Transaction) -> Tuple[Uint, Tup 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. -```python= +```python def process_message_call(message: Message, env: Environment) -> MessageCallOutput: return MessageCallOutput( # ... other fields ... From df49e2c3de4e087eb6f461b510e075adcea0caf5 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:59:27 -0500 Subject: [PATCH 3/4] Update and rename eip-9999.md to eip-7863.md --- EIPS/{eip-9999.md => eip-7863.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename EIPS/{eip-9999.md => eip-7863.md} (99%) diff --git a/EIPS/eip-9999.md b/EIPS/eip-7863.md similarity index 99% rename from EIPS/eip-9999.md rename to EIPS/eip-7863.md index 6ac7811be56e78..4617364fbf48fc 100644 --- a/EIPS/eip-9999.md +++ b/EIPS/eip-7863.md @@ -1,6 +1,6 @@ --- -eip: 9999 -title: Block-level warming +eip: 7863 +title: Block-level Warming description: Warm addresses and storage keys over the duration of a block author: Toni Wahrstätter (@nerolation), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs) discussions-to: "" From 8fdc48d74f55e5408f5c0492403610e2e986897c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= Date: Sat, 18 Jan 2025 08:29:54 +0100 Subject: [PATCH 4/4] EIP-7863: Fix Linter issues --- EIPS/eip-7863.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/EIPS/eip-7863.md b/EIPS/eip-7863.md index 4617364fbf48fc..3f5cd2b50fa35d 100644 --- a/EIPS/eip-7863.md +++ b/EIPS/eip-7863.md @@ -2,8 +2,8 @@ eip: 7863 title: Block-level Warming description: Warm addresses and storage keys over the duration of a block -author: Toni Wahrstätter (@nerolation), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs) -discussions-to: "" +author: Toni Wahrstätter (@nerolation), Jochem Brouwer (@jochem-brouwer), Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs) +discussions-to: https://ethereum-magicians.org/t/eip-7863-block-level-warming/22572 status: Draft type: Standards Track category: Core @@ -30,8 +30,9 @@ As of Jan. 2025, the empirically messured efficiency gains are around 5%. ### 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](./eip-2929). -2. All subsequent accesses to the same slot within the same block incur only the warm access cost as of [EIP-2929](./eip-2929). + +1. The first access to a slot in a block incurs the cold access cost as of [EIP-2929](./eip-2929.md). +2. All subsequent accesses to the same slot within the same block incur only the warm access cost as of [EIP-2929](./eip-2929.md). 3. The warm/cold status resets at block boundaries ### Block Processing @@ -41,14 +42,14 @@ When a storage slot is accessed within a block: 2. For each transaction in the block: * Before processing storage access: * Check if touched address is in `block_level_accessed_addresses` - * If yes: charge `GAS_WARM_ACCESS` + * If yes: charge `WARM_STORAGE_READ_COST` * If no: - * Charge `GAS_COLD_ACCOUNT_ACCESS` + * 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 `GAS_WARM_ACCESS` + * If yes: charge `WARM_STORAGE_READ_COST` * If no: - * Charge `GAS_COLD_SLOAD` + * Charge `COLD_SLOAD_COST` * Add storage key to `block_level_accessed_storage_keys` @@ -117,14 +118,17 @@ The proposal builds on several key observations: 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. **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. +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. -4. **Alternative Block Warming Windows**: Instead of applying warming at the block level, more advanced multi-block warming approaches can be considered. Potential options include: +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. + 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 @@ -134,11 +138,6 @@ This change is not backward compatible and requires a hard fork activation. Howe 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 -## Reference Implementation - -TBD, first draft here: -https://github.com/nerolation/execution-specs/tree/block-level-warming - ## 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.