EIP-1087: Net storage gas metering for the EVM

@Arachnid unless I’m misunderstanding something this change would result in what is today being a full block becoming a block that is only partially full (due to the reduction in gas cost for storage) and as a result means that for a given block gas limit a block after this proposal was implemented will have more transactions, hence take longer to transfer and longer to validate.

this change reduces the overcharging of gas for no-op storage writes down to something resembling the actual costs

which is my point. If 8 million gas worth of transactions today becomes (for example) 4 million gas worth of transactions after this proposal is implemented it has the same network impact as doubling the block gas limit without implementing this proposal. I doubt that anyone would consider the latter to be a sensible idea.

Again, I’m all for this proposal and have railed against incorrect gas costs for storage in the past. I just worry that it might have an impact on the network and was wondering if that had been examined/quantified.

That’s right. But any block that improves as a result of this change was one that was easier to process than its used gas indicated previously, because the operations being charged for are being overcharged. A block that today costs 8M gas and tomorrow costs 4M gas would take a similar processing time to a block today that contains no SSTORE operations and costs 4M gas.

The gas limit matters largely to prevent DoS attacks. If this change doesn’t increase the cost of processing the most expensive possible 8M gas block, then it has no effect on that.

@Arachnid all fair enough, but am I then incorrect in thinking that this change will increase transfer and processing time for blocks, and hence increase the uncle rate (assuming that the block gas limit remains the same), for the common (non-pathological) case?

Higher uncle rates aren’t really that bad, and over time they will self heal due to miners decreasing block gas limits. All dapps must already be able to handle uncle blocks and reorgs, and uncles already are fairly common.

I personally believe that the cost of measuring this and then debating whether we should take action is not worth the benefit. Even if we did measure, I suspect there would be a lot more “debate” on whether to actually reduce the block size or not than if we just make this change and let the miners decide via block size voting.

Only for blocks that were previously over-charged for. It’ll reduce the difference between the slowest and fastest block.

If the slot was 0 before the transaction and is 0 now, refund 19800 gas.
If the slot was nonzero before the transaction and its value has not changed, refund 4800 gas.

Why do you handling these two cases differently? Actually the slot remains unchanged in both cases and needs no write operation.

P.S. My intention to support the the EIP is the efficient implementation of Reentrancy Locks.

In the former case, 20k gas was charged when the slot was first touched. In the latter case, only 5k was charged. In both cases the refund serves to zero that out.

As far as I understand, the reason why someone would keep changing the same storage position in the middle of a transaction (instead of using memory or stack) is the desire to communicate between different frames of the transaction in a way which is not input/outputs of a CALL. Crucially, it allows such communication across the intermediate invocations of a different, potentially untrusted contract (see example of Reetrancy Locks above).
I wonder if this is the best way to handle such communication. Perhaps it is better to have a pair of opcodes like TSTORE and TLOAD that will write Transitent storage, which survives between the frames of one transaction, but does not get persisted? And these opcodes will have flag gas cost?

I can see value in transaction storage @AlexeyAkhunov, though I still think there is value in this EIP as it stands since there are some situations where you actually do want to work with storage data multiple times in a single contract call.

@MicahZoltu The reason I am looking for alternatives to this is because I know that gas metering is going to become more complicated with this EIP, especially with the support of pre-EIP and post-EIP logic in the same code. I think if you want to work with storage data multiple times, you can either pay the high cost of SSTORE, or do stuff in transient storage, and then “commit it” to permanent if you want. Oh, now you gave me the idea for TCOMMIT opcode :slight_smile:

1 Like

I did consider something like this, but using sstore/sload has several advantages:

  • It uses an existing mechanism that is already in use for this purpose.
  • It introduces minimal additional complication to the EVM.
  • It also reduces excess gas costs in other situations where multiple changes are made to storage.

One example is ERC20-based contracts, where you might want to issue an approval that’s then used in the same transaction.

I think introducing new opcodes for this would be significantly more complex, particularly given the fact that clients are generally already tracking ‘dirty’ properties right now. It would also require new language support.

1 Like

Implementing this pattern is quite complex, especially across multiple contracts, and impossible in some cases such as when you don’t know if this will be the last time your contract is called or not within the current transaction.

I am not very sure of that. It might actually be cleaner to implement this as a new opcode pair, because the current code for storage gas accounting stays the same. And implementation is basically the same as memory, with the difference that it is the same copy for all the frames. Gas cost could be exactly the same as for memory right now, without any refunds and references to transaction boundaries.

As for the language support, I can see how it can be done for Solidity, for example, quite easily - you declare variable like storage variable, but use modifier “transient” or something like that.

Generally, I am not for tweaking the existing instructions to be more “versatile” - but instead for having perhaps more instruction with simpler semantics.

I am going to write an EIP for transient storage opcodes and see what happens there

1 Like

That doesn’t seem particularly persuasive - instead clients will have to write a whole lot of other, new code.

Sounds good!

@AlexeyAkhunov,

I had discussed very similar proposal with @chriseth and @pirapira as implementation of effective ReentranceLock exact one year ago. Instead of ‘TSTORE’ I used ‘CSTORE’ as for context storage (shared context storage for transaction lifetime), but in other aspects it was the similar approach.

The access model for the transaction storage had to remain the same as for permanent storage (a contract should be able to write only into own transient variables of the shared storage). Thus the transaction (or context) storage became very similar to current storage model. Finally Yoichi came with the idea to implement effective ReentrancyLock by setting gas costs to zero if there is no actual storage changes made (this proposal).

I have asked @pirapira & @chriseth about details of our discussion one year ago.
Possibly they will came with details I have already forgot.

Here’s an initial implementation of this EIP for go-ethereum: core, params: implement EIP-1087, net storage gas metering by karalabe · Pull Request #17208 · ethereum/go-ethereum · GitHub

Note, we’ve hit a few ambiguities, that we resolved in a way that seemed logical, but I’d really like the EIP to be polished up a bit to touch on these issues:

An EVM-global ‘dirty map’ is maintained, tracking all storage slots in all contracts that have been modified in the current transaction.

If the user attempts to set a clean storage slot (arbitrary value) to the existing value, the dirty flag should not be set. This is important, otherwise the user can game the metering system by setting a clean-empty slot to zero and then to a value (e.g. key = 0; key = 5), avoiding the initial 20K gas cost.

When a storage slot is written to for the first time, the slot is marked as dirty.

Please specify that “unless it’s set to the same value”, see above and below for the problematic part.

If the slot was previously set to 0, and is being set to 0, only 200 gas is deducted.

If this operation also sets the dirty flag, the user can game per the above example. If this operation does not set the dirty flag, then we should allow the same feature for any value (i.e. please change to “If the slot was previously set to X, and is being set to X, only 200 gas is deducted.”).

3 Likes

A second important corner case is around dirtiness and revertals. The EIP doesn’t really specify what happens if an inner transaction makes a storage slot dirty, but then reverts. Should the slot be reverted to clean (thus incurring an additional gas hit when modifying it again), or should the slot be kept dirty?

Our code currently reverts the dirtiness too but I guess we can change if needed. However this is an important subtlety to emphasize in the spec.

2 Likes

The spec looks pretty solid (but no implementation yet), but I’d like to ask to maybe rephrase it with simpler sentences. Also try not to refer the same action with different English, e.g. “a storage slot is written to with the value”, “a storage slot’s value is changed”.

1 Like

Gas netting is not really making SLOAD/SSTORE more versatile, it seems more like a fix. Does either of these two changes have to exclude the other?

I agree your opcodes have an advantage and that is for runtime enforcement of “transience”. Seems it would be hard to do without new opcodes. Seems if this pattern starts being encouraged (even in languages), and actual storage is used, it’s still possible for transient values to remain set after the transaction is over, opening up for some pretty weird bugs.

Btw, chriseth is thinking about adding mutability flags to Solidity, perhaps this could be thought of as as a kind of mutability “flavor” rather then as its own flag?