EIP-7863: Block-level Warming

Discussion topic for EIP-7863

Find an initial analysis of the EIP in the following:

Update Log

External Reviews

None as of 2025-01-18.

Outstanding Issues

2025-01-18: Clarify if block-level warming or directly move to multi-block warming

2025-01-18: Clarity warm-warm-for-all vs. one-warms-and-gets-refunded (costs distributed among transactions or accesses)

2 Likes

It appears your mechanism would strongly penalize the use of transaction-level access lists. Is this intentional?

I disagree that its penalizing anyone.
Access list would still make sense for ToB transactions and one can also think of mechanisms as proposed in EIP-3584: Block Access List that allow everyone from benefit from access lists.

One could also think of building probabilistic access list, putting rarely used storage slots into the access list and “hoping” that some others are already warm (e.g. because they are warm in 99% of the cases - WETH as an example).

With block-level warming there are no losers when compared to the status-quo, so “strongly penalizing” is clearly wrong.

Then you are wrong, and did this unintentionally, likely because you don’t understand the current mechanism. The cost for warming a slot that is already warm (or one that won’t be touched in the transaction) far exceeds the benefit of warming it for first-use, by a factor of 24 for accounts and 19 for storage.

This would be the only remaining use for access lists. The probabilities here are 4.17% for accounts and 5.26% for storage. These probabilities are so low and for most users it is impossible to identify which slots will be unique to your transaction. The result is that regular users won’t use them anymore, and block builders will have an even larger advantage in MEV than they already have. It would further encourage their collusion with searchers and thereby end private orderflow.

The mistake your are making is that you are comparing the absolute cost before and after when you should be comparing the marginal benefit before (100) and after (-2100). The losers are anyone still using access lists. Kindly admit this is a fault in your current design and find a way to fix it. I suggest making the the first instance of each block-warm entry in an access list free. This will prevent access lists from going extinct.

My main objection to block-level warming is that it prevents parallel execution due to the GAS opcode. Block transactions should be able to be executed in parallel.

2 Likes

Where do you take these probabilities from?

The EIP is still at the very beginning and access lists are being discussed. I love to see them being applied to the whole block and sharing the costs, but I think this comes with significant increased complexity compared to this rather simple change.

Access lists didn’t really take of and block level warming might just be tackling the problem, efficiency we leave on the table, from a different angle, and further iterations might come with handling them in some smarter way.

Parallelization, true, yeah. Though, I wouldn’t say it’s preventing. At most, adding some complexity. You say “should”, we both see how well those efforts are going. So the best time to do block warming was probably a few years ago.

It’s the collision probability where the expected value of including an entry in an access list is zero. If the probability is less than this, you should include it in the access list. Otherwise you should not include it.

I use them in every transaction. There are a few ways to make them better but their current design is opt-in. Theoretically block execution can warm the state earlier using these lists. Security would be a bit better if access lists were strict, because there would be restrictions on unexpected execution. Strict access lists can also make light client execution possible in combination with state proofs. I don’t want to destroy them therefore.

No one is saying to destroy them. There seems to be broad agreement that they are useful. However, we could explore ways to apply them at the block level and offer refunds to those who had to pay for creating the access list. This approach is more complex compared to block-level warming, which has its own challenges. But these difficulties shouldn’t stop us from making improvements in other areas.

We could also consider repricing access lists to further strengthen the incentives.

I don’t agree with the idea that this would completely ruin them. There will always be storage slots where you can be fairly certain they aren’t warmed yet—for example, checking my balance in an ERC20 token.

Do you have more details on this? What is the time frame analyzed, what data, where’s the code?

There is no need to collect data to calculate this probability. Here is an article on how to compute the expected value.

The first step is to set up the equation I described.

the collision probability where the expected value of including an entry in an access list is zero.

P(alreadyIncluded) * V(alreadyIncluded) + P(!alreadyIncluded) * V(!alreadyIncluded) = 0

The next step after setting up the algebraic equation is to plug in the marginal benefit numbers aforementioned. I will do that here using the account gas numbers, 2600 and 100.

P(alreadyIncluded) * (-2600) + P(!alreadyIncluded) * (100) = 0

Because these probabilities sum to one, you can solve for the probabilities that yield zero for the expected value.

P(alreadyIncluded) * (-2400) + (1 - P(alreadyIncluded)) * (100) = 0
(-2500) * P(alreadyIncluded) + 100 = 0
P(alreadyIncluded) = 1/25

So the probability is 1/25 (aka 1:24). I previously miscalculated it as 1/24 in haste.

You would have to check hash preimages for substrings containing the sender address. It doesn’t work in the general case either; there are plenty of possible future designs because ethereum is Turing complete. I don’t think the implementers of eth_createAccessList are going to appreciate your change therefore. It is much simpler to implement the change I previously suggested, so that each transaction still references all of its own accesses. Users, wallets, and nodes shouldn’t have to reason about the probability of collision with earlier transactions in the block.

My suggestion achieves that, but I am open to other ideas.

I’d also be interested in ways for the first user touching each thing to pay the same as everyone else. Perhaps the warming gas cost could be shared somehow.

Ah gotcha, “so if the probability that your address is already warm >5%, you’'d not put it in there”.

One thing I was thinking of is separating access lists from transactions, and then validate them before starting execution. Invalid access lists are still included+paid for. This would allow us to handle them before execution and allow all txs to profit from warmed storage slots. Eventually, one could refund some of the costs.

1 Like

I was thinking a bunch about this and, imo, the best thing we could do is approach the cold/warm inconsistency overy transactions from a block-level perspective.
One could think of combining all access lists at the beginning of a transaction and then splitting the costs for access list creation among the accesses.
This comes with problems like “quality of access list” vs. “quantity”, meaning, we would want to avoid having WETH in every second access list when it’s best to have it only in a single one.
Duplicates in a block are waistful.

With block-level warming, block builders might have an incentive to warm the storage slots (accounts + storage keys) for the transactions in their block using access lists. This would give them another 100 gas per first access.
Regular users might still use access lists for storage slots that they are certain that the already-warm propability is <5%.

In the end, no matter if you today use access lists or not, with block level warming you’ll never be worse off compared to today. I think it’s reasonable to compare absolute values here and let and efficient market play out the dynamics for access lists. In times of PBS, efficient block packing is outsourced in >90% of block anyway.

efficient market play out the dynamics for access lists

The most efficient market is a single centralized builder-searcher determining the order of all transactions, maximizing extraction. This is not desirable, and we should not make changes that promote this behavior. Currently, searchers have a fair gas auction with builder-searchers and can occasionally win. After this change they would all be integrated.

Marginal costs do matter because they determine who wins the gas auction. You don’t care if block builders have this huge advantage over independent searchers, but eradicating independent searchers would be a huge step toward centralization. As aforementioned, the gas savings of knowing prior block accesses is 25x and 20x larger than the gas savings of the access list itself. We should not give that advantage to builders.

Regular users cannot perform this computation.

Agree. But it’s likely less wasteful than having no access lists at all. And the transaction should not be penalized for what they do not know. The best way to encourage them to include it is to ensure they’re always incentivized to include it. Another reason for each transaction to have their own list is that it is beneficial for the simulation of that individual transaction.

Use of access lists is not a constant. You may not think inclusion is penalized in terms of the absolute change in but they are penalized in marginal terms in reference to the decision to include or not. A poor understanding of marginalism is a recurring theme of your EIPs. Marginalism explains how microeconomic decisions are made and can be used to predict the outcomes of changes in incentives. I challenge you to study marginalism before thinking of ways to fix this.

Consider the decision matrix for whether to include an account item or not.

Decision Before Prior Access First Access
Include 100 100
Exclude 0 0
Decision After Prior Access First Access
Include -2400 100
Exclude 0 0

It is clear that this change does penalize access lists for every user except the omniscient builder, and that the builder’s advantage is substantial. I am also concerned that a builder can manipulate the gas of a transaction by changing a first access into a prior access or vice versa, causing a transaction to fail or succeed, or to cost more or less than expected (which can matter for priority fees).

How is this better than simply raising the gas limit 10-15%?

As a node implementor I only see more bookkeeping and accounting to do so that gas golfers can reduce their gas burnt numbers. The amount of computational work done in a node actually goes up on a per-gas basis. So based on that I view this as strictly worse than simply raising the gas limit.

Second, this will critically damage parallel execution efforts. By making the gas cost dependent on the state access of all prior transactions you are making the current transaction dependent on the side effects of the entire preceeding block. Parallel efforts depend greatly on slots having effective read locks (which can be shared) and effective write locks (which are exclusive). Having to consider the warm state removes the effective read locks and changes everything into an effective write lock, reducing the scope across which these effective locks can be shared across the block. It also increases the mandatory bookkeeping of this data. Prior to this we only needed to re-evaluate transactions if a prior transaction changed the value of a slot, now simply reading it causes changes to the execution semantics across transactional boundaries. USDC would be an example, where the paused field must be read but is (almost) never written to. The effective read lock now becomes effective write locks.

If there is still the desire to reduce the per-gas cost of warming a slot or account across the transaction, consider tracking who warms a block and if there are multiple warmers, at the end of the block refunding all users of the field pro-rata for the warming costs of the slot or account and updating the balances at the end of the transactional block. Realize though, that while the effective ether cost goes down the actual computational cost goes up. I do not think this is a net win against the alternative of just raising the block gas limit by 20%.

1 Like

Raising the gas limit comes with more load on all dimensions while the purpose of block-level warming is more to get the storage pricing closer to realities in clients, that can cache over multiple transactions.

I agree that it’s bad for parallel execution, and maybe some form of block-level access lists (described here) might be better than simple cumulative block-level warming, allowing clients to parallelized I/O and execution.

I see why such an approach sounds fairer but I fear this comes with significantly more complexity. I do agree with your concerns and think it’s a trade-off that we may want to consider carefully.

I agree with the scepticism above about “First Access Warms For All”. I see 4 problems with it:

  1. It’s unfair to users.
  2. It creates a minor form of MEV.
  3. It reduces the extent to which transactions are isolated from one another. In general, the design has been to treat two transactions in the same block and two transactions in different blocks the same.
  4. It creates execution dependencies between transactions with common reads. Currently, in principle, two transactions can be executed in parallel as long as neither of them reads an item that the other writes to.

I’d like to propose a different approach, where gas refunds are issued at the block level allowing the builder to include more transactions and lowering the future base fee. I think this mostly fixes those problems and should be simple to implement. Here is a formal spec:

  1. At the start of each block:
    • Initialize two empty sets block_level_accessed_addresses and block_level_accessed_storage_keys and one counter block_gas_refund
    • Add any pre-warmed items to the sets (e.g. coinbase, precompiles)
  2. For each transaction in the block:
    • Execution is entirely unchanged from current behaviour
    • At the end of the transaction:
      • Add each address in accessed_addresses (excluding pre-warmed) to block_level_accessed_addresses, if the address already present add COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST to block_gas_refund
      • Add each storage key in accessed_storage_keys to block_level_accessed_storage_keys, it the key is already present add COLD_SLOAD_COST - WARM_STORAGE_READ_COST to block_gas_refund
  3. At the end of the block:
    • net_gas_used = gas_used - block_gas_refund
    • Check net_gas_used <= block_gas_limit, rather than gas_used <= block_gas_limit
    • Calculate the base fee for the next block based on net_gas_used rather than gas_used

By dealing with block level access lists at the end of the transaction we avoid having to deal with interaction between them and reverts.

I’d like to propose what I think is a simple solution: tweak gas costs for accessLists.

Specifically, every txn is executed as-is today. But at the accessList level, rather than charging X for a given address/slot, X/n is charged (where n is the number of accessLists with that address/slot). Afaict, this has the following properties:

  1. leaves txn execution de-coupled
  2. encourages accessList usage (by creating potential for more gas savings from usage)
  3. end-users receive gas benefits directly
  4. no weird minor MEV

Also pretty simple to code / execute from a block producer perspective (I think). Any big drawbacks I’ve missed?