The way ZK-EVMs generally work today is that they chunk at the transaction boundary: a monolithic prover proves each transaction (or, if they are small, batch of transactions), and then you aggregate those proofs into a single proof. And so within this architecture, the size of a transaction determines the degree to which you can parallelize. The theoretical minimum latency bound on SNARK-proving a block is something like: tx_gas_limit + recursion_cost * log(number_of_txs)
. And so if we want safe shorter slot times, pushing down the max tx gas limit cap is one of the key variables for doing that.
Distributed proving also adds pressure to decrease the max tx gas limit because the whole point is that you have a prover that’s very large in aggregate but where each individual piece is smaller, and so single very large transactions are precisely the case where it would break.
I have suggested multiple times to ZK-EVM developers to chunk at the EVM step boundary instead of the transaction boundary to be able to better process multiple transactions. As far as I know almost all of them have not done it. The reason why is that it is much more complex to chunk within a transaction: you have to carry over all kinds of internal state (callstack, memory, refunds, TSTORE…) from one proof to the other. There’s massive dev effort and room for bugs there.
The way that this EIP can improve execution parallelism is in combination with block-level access lists, particularly the version where you require the post-state of each transaction to be included. Any node verifying such a block becomes able to run all transactions in parallel, even if they have cross-dependencies, and verify that each intermediate access list is correct in parallel.
The reason why this EIP improves worst-cases around quadratic execution bugs, is that (I’m pretty sure without exception) all of the quadratic execution bugs we have seen involve interactions within a transaction (eg. depth-N call stack followed by N transactions) and not between transactions. Between transactions there is no shared state (except the Ethereum state itself), and so much less surface area for quadratic execution bugs.
AND MOST IMPORTANTLY setting the limit below 30M is a breaking change, which is a major sin and should be avoided.
I really hope that we do not adopt “breaking changes are a major sin and should be avoided” as our philosophy. “Breaking changes should be done very carefully, the carefulness in proportion to how many things they break” is better. If we treat breaking changes as a sin, then we end up suffering an eternal ongoing penalty for the sake of avoiding a one-time pain, and the amount of backwards-compatibility cruft that we suffer will keep stacking up over time.
We have made breaking changes before, with very positive results. The most famous one was increasing the gas costs of storage IO opcodes in order to improve DoS resistance. The first few rounds of this (40 → 200 → 700) were security-critical. The last round of this was arguably not security-critical, we could have arguably survived SLOAD at 700, but:
- It was ok at lower gas limits but it would have made gaslimit increases to the level we see today unsafe.
- It would have made statelessness unviable.
An even stronger example was the SELFDESTRUCT nerf (recently, SELFDESTRUCT became only usable to destroy a contract created within the same transaction). The goal of the SELFDESTRUCT nerf was to establish a new invariant: there is a hard O(1) bound on how many state changes can happen within a single transaction. This in turn made client development significantly easier, because clients would be able to have a simple one-layer caching mechanism for the state, instead of a complicated structure that could handle giant contracts being SELFDESTRUCTED, then that operation being reverted, then calls going into that contract, etc.
These changes were good for Ethereum. As a general principle, having more hardline invariants that bound what can happen within a transaction, or a block, is a very good thing in hard-to-predict ways. It makes future client optimizations possible. It makes client code simpler, which increases safety, improves client diversity and reduces development costs for teams. It prevents classes of safety issues that we did not know existed.
A key goal that many people have expressed for this year is scaling the L1. But scaling the L1 is risky, to safety and decentralization. One very good strategy for meeting both goals at the same time is to increase the L1 gaslimit and at the same time establish more and stronger invariants that cut off problematic worst-case scenarios.
I expect that the amount of disruption that proposals like (i) multidimensional gas, (ii) per-code-chunk witness pricing, (iii) serious gas repricings for ZK-EVM safety will be greater than that caused by reducing the tx gas limit, and so we need to be prepared for some applications that are unusually resource-intensive along some margin to need to rewrite parts of their logic regardless.
In fact, I would argue that as the ecosystem matures, the cost of making any breaking change rises every year, and so by constraining the per-tx load now, we would actually reduce total pain, because we get more leeway to do these future repricings (which will happen at a time when the ecosystem is even more mature and entrenched) through increasing limits rather than decreasing them.