Description
Motivation
The Ethereum platform gives users access to a number of computational resources: particularly computational steps, memory, logs / bloom filter usage and permanent storage in the Ethereum state. For most of these resources, the gas system is roughly incentive-compatible, although price-fixing the exchange rates between these resources is, in the current framework, unfortunately required. Storage, however, is unique in that it is not ephemeral; once storage is filled, it must be stored by all full nodes forever until/if it is deliberately cleared. The cost of storage is bytes * time
, but users are only paying for bytes
.
A partial solution exists for Ethereum 1.0 in the form of the SSTORE
clearing refund, where a user gets up to a net 10000 gas back for converting a filled storage slot into an empty slot. However, this approach is imperfect for two reasons:
- During a block where transaction inclusion demand does not reach the gas limit (or even where the opportunity-cost gasprice is cheaper than average), miners have the incentive to fill up storage slots so as to use them as a "bank" that expensive transactions can use to grant themselves additional gas at a time when gasprice is expensive; essentially, conducting inter-temporal gas arbitrage with the additional negative externality of requiring all full nodes to store extra data in the meantime.
- There is no incentive to use storage slots for a short period of time instead of a long period of time.
Proposal
A proposal for solving this is to introduce "blockchain rent": rather than charging for bytes, actually charge for bytes * time. Here is a proposed protocol for doing so:
- In the account RLP, keep track of the variables
last_accessed
andtotal_storage_bytes
.last_accessed
is meant to represent the block number at which storage was most recently changed, andtotal_storage_bytes
is meant to represent the total number of bytes in account storage. - When you perform an
SSTORE(k, v)
operation on the account, letfee = (TOTFEE[block.number] - TOTFEE[last_accessed]) * account.total_storage_bytes
, whereTOTFEE[n]
is the sum of the data storage fees from block 0 to block n (eg.TOTFEE[n] = n * c
for some constantc
would work as a basic policy but one can come up with more dynamic policies that change per block). Setaccount.balance -= fee
,account.last_accessed = block.number
andaccount.total_storage_bytes += len(v) + (len(k) if v != '' else 0) - len(old value of account.storage[k]) - (len(k) if old value of account.storage[k] != '' else 0)
. Ifaccount.balance < 0
, destroy the account. - Create a special type of
ping
transaction which performs a dummySSTORE(0, account.storage[0])
operation on any desired account. Miners can run this if they see an account that needs to be deleted in order to process fees and delete it if it is in fact bankrupt. If and only if a ping leads to an account bankruptcy, it requires no gas.
For example, suppose that TOTFEE[n] = n * 10
(ie. 10 wei per block per second), a block was last accessed in block 321070, the current block is 312085, the account has a pre-existing balance of 1000, and it only has two keys in storage: {"cow": "dog", "horse": "doge"}
. We should have account.last_accessed = 321070
and account.total_storage_bytes = 3 + 3 + 5 + 4 = 15
. Now, suppose we have a transaction that sends 10000 wei and, in the process of executing code, sets SSTORE("moose", "deer")
. We compute:
- Balance increased from
1000
to11000
fee = (TOTFEE[312085] - TOTFEE[312070]) * 15 = (3120850 - 3120700) * 15 = 2250
- Balance decreased from
11000
to8750
due to the fee account.total_storage_bytes += len("deer") + len("moose") - 0 - 0
, ie. increased by 9 for a new total of15 + 9 = 24
. The values subtracted are both zero because there was nothing pre-existing in that key.account.last_accessed = 312085
Setting TOTFEE
The hardest challenge in this scheme is setting the TOTFEE
function. One could set this to a static value (ie. TOTFEE[n] = c * n
), but this is essentially a form of price-fixing and so is arguably hard to set in a way that is economically efficient. A somewhat more effective strategy is to set a space limit (arguably, this is no less reasonable than setting a gas limit), and targeting TOTFEE
based on that value. For example, consider a policy where we have some function for setting a space limit L
(eg. a flat L = 2**35
or a linearly growing L = 2**10 * (block.timestamp - genesis.timestamp)
). Then, every block, we set TOTFEE[n] = TOTFEE[n-1] + 2**(20 * L / (L - total_state_size))
(ie. the fee is 2**20 wei ~= 0.001 shannon initially, then when the total state size increases to 5% of the maximum it doubles, when it increases to 10% of the maximum it doubles again, and so forth, with the doubling intervals decreasing in size and ultimately converging to a singularity at L). In theory, the amount of storage usage should increase until it hits an equilibrium equal to the marginal demand for storage at a supply somewhere less than L
(in the case of the above example, roughly L / 2
is plausible); the main tradeoff to be made is in the steepness of the supply function. Steeper supply functions are more robust to mistakes made in the initial supply-function-setting process, but they also lead to more volatile rent charges.
Why should I want to support a proposal that adds in a "weird new tax" when storage usage per second is free now?
- The
SSTORE
gas costs may go down, benefiting dapp developers that only use storage for short time durations - The disk space requirements for a full node will go down
Activity
alexvandesande commentedon Nov 25, 2015
In that proposal, the only way someone else can pay a contract's fees is to send money to it, correct? Should there be a way in which someone could send money to a contract but that the contract itself could not spend to anything other than fees? The scenario I'm thinking is one where someone created a contract that always does something with any money it received and keeps a balance of zero because it was programmed at a time that that shouldn't matter.
For example: say I build a contract that whenever anyone sends money to it, it automatically sends it 50% to Bob and 50% to Alice. It's a very useful contract for both Bob and Alice and they would like to keep it alive, but they can't send money to it in order to pay its fees because it will always forward all the deposits back to them. Even if the contract was programmed with the rent scenario in mind, how can Bob and Alice even know how much is enough for the contract? If say, the contract is programmed to keep 0.1% of anything that it forwards, and if storage costs are low and the contract is used a lot, it might create a scenario where the contract has fees to pay it's rent for the next 30 years and Bob and Alice cannot retrieve any of these funds..
About how to set the fee, can't that be left to the miners? Each contract might offer a price and miners choose to accept it or not and contracts can only be deleted if no miner in the last N blocks have accepted their rent?
vbuterin commentedon Nov 25, 2015
Correct.
One option to solve the problem is for the contract to keep an internal "shadow balance" in storage, and then have a function for getting the difference between balance and shadow balance; someone wanting to send to the contract to fill it up would just call this function beforehand in order to learn how much to send, and then send that amount on top of what they were going to send.
The economics of this seem iffy; particularly, in this exact scheme, note that each miner receives 100% of the gain from including a contract but only pays a small fraction of the cost, so you'll get a much-larger-than-optimal quantity of contract inclusion.
[-]Blockchain rent[/-][+]EIP 103: Blockchain rent[/+][-]EIP 103: Blockchain rent[/-][+]EIP 103 (Serenity): Blockchain rent[/+]chriseth commentedon Nov 25, 2015
Having to keep contracts funded with ether all the time seems a bit awkward, although I can certainly see the need for and benefits of that. Which alternatives are there? Is it possible to pay for storage using gas? Can a contract perhaps deduct a certain amount of gas during the SSTORE that is used to hold the value in storage?
Would it be possible to set up a "Storage fund contract" that holds a certain amount of ether but is used by a group of contracts which can then retrieve just the required amount of ether to fund their existence?
Correctly balancing storage and computation fee is also a major concern, because we do not really know how costs for storage and computing will change in the future. Also with EIP-101 in mind - would it make sense to use a "storage token" that is different from ether and used to pay for storage? Perhaps some of the logic could be moved into the storage token contract.
Oh and perhaps a side note: Contract code can be used to emulate storage:
The only content of contract A's storage is a pointer to another contract P. It uses EXTCODECOPY to load data from P and whenever it wants to modify the data, it creates a new contract and updates the pointer. So I think we should also consider blockchain rent for code.
vbuterin commentedon Nov 25, 2015
I suppose you can set up a system where a contract accumulates a "debt" of gas fees and that debt has to be paid off before you can interact with the contract (ie. same as above, except you do
debt += fee
, and when you send a transaction to a contract you domsg.gas -= min(debt, msg.gas); debt -= min(debt, msg.gas)
first), and if the debt goes higher than the block gas limit then the contract can get deleted. However, any mechanism by which gas can be "stored" potentially allows for inter-temporal arbitrage (ie. paying down debts only when gas is marginally free); not sure about this but it might be the inevitable tradeoff for currency neutrality.Another approach here is a hybrid strategy: allow temporary storage using some kind of ether or gas fees, at low cost, and then have permanent storage at high cost.
simondlr commentedon Nov 25, 2015
I'm a fan of this. I feel there's something to be said for allowing some permanent storage on a blockchain. The "set & forget" nature of the "world computer" supported by various participants across the world is a big benefit. It's potentially dangerous to have some parts of a burgeoning contract ecosystem disappear, because rent wasn't paid (by whomever). Could lead to some cascading dapp failures. Also, who is set up to continue paying rent? If a build a dapp and rely on a contract that's paying rent, I want to be pretty damn well sure it's not going to drop off, or worse that I & others have to pay rent for it to keep going. If a developer says they will pay rent and they leave or die, will a community step up to continue paying rent?
Smithgift commentedon Nov 25, 2015
I'm greatly in favor of some kind of blockchain rent. There's an odd morality in essentially forcing future users to pay for current use.
I agree with chriseth about contract code being used as storage. If this is a possibility, it would be better to create a specific perma-store opcode. (Call it owning a part of the blockchain rather than renting it.) However, what does it mean if a contract has some owning-storage and some renting-storage and it can't pay the rent? Does the whole thing cease to exist? Or just the rented data? Either could be a landmine for a DApp developer.
Every currently existing DApp will have to have a new incentive structure to self-perpetuate. For example, demanding a small fee when you use it. In itself this isn't bad, but nearly all old contracts will disintegrate short generous donations. In a hybrid system, we could rule that all old DApps have retroactively used owning-storage, and that new DApps can use renting-storage as they please.
If EIP 101 happens, how will this interact with the new ETH-contract? There will be a contract at 0x00... whose data is altered by the protocol and not by itself. I suppose the ping transaction could actually be a normal transaction sent to 0x00... but I'm not sure how the contract would be able to delete other contracts in an elegant manner.
PeterBorah commentedon Nov 25, 2015
I'm pretty strongly opposed to this, unless there's absolutely no way around it. One of the unique properties of Ethereum is that contracts are trustworthy. I can build my contract on top of another one, and feel confident that that will continue to work.
In a blockchain rent world, that guarantee goes away. You now have to be paranoid that any contract you rely on might disappear at any time. Careful contracts will try to keep a fund around to rescue contracts they depend on, but that's never going to be a foolproof process. I have visions of a tiny utility contract used in a fundamental layer of contracts being forgotten about, and years later running out of funds, causing a quarter of active contracts to stop working. That's a failure you can't even recover from because of code immutability.
It also potentially opens up some attack vectors: I can call a contract a bunch of times, with the intention of filling up its storage and thereby making it too expensive to maintain. This costs me ether, of course, but it's only a one-time cost, while it might continue to cost the contract forever. And it might have disproportionate effects due to the cascading failure problem.
Finally, though probably least importantly, it adds a pretty serious amount of complexity to the process of creating a contract. Right now, the illusion that you're writing a normal web script is pretty good. After this change, there will be a whole host of complex issues around analyzing your code to determine future storage usage, checking dependencies to determine their likely reliability, etc.
PeterBorah commentedon Nov 25, 2015
Instead, we should be aiming to keep contract storage below Moore's Law. If we need to increase storage costs to do so, so be it.
vbuterin commentedon Nov 25, 2015
Ok, so it seems like parametrizing contract storage lifetime with 0 = current log, ∞ = current sstore and 0 < k < ∞ = new hybrid option might be a route worth exploring, perhaps where temp storage is encapsulated in a clear way so it isn't mistaken for permanent storage. For one trivial example of where storage with lifetime 1 might be useful, consider cheques.
PeterBorah commentedon Nov 25, 2015
I think I don't understand what you mean by "lifetime 1". A cheque might go a long time without being cashed, so it will need a lifetime of longer than 1 block.
I'm definitely more open to a hybrid approach, though, if the unreliability of temporary storage is emphasized.
wanderer commentedon Nov 25, 2015
on
TOTFEE
would it make sense to replacec
as some function of the average gasPrice over x previous block?EDIT: I kinda don't like targeting a
total_state_size
b/c we cannot predict hardware storage costs. But we can attempt infer the total system cost by the gasPrice.50 remaining items
tawaren commentedon Mar 15, 2017
In my opinion any mechanism that can lead to an unspecified deletion of a contract or unpreventable state change in general is a bad idea (sadly this already can happen with the selfdestruct and coinbase, at least this always leads to an increase in the balance and tracking the balance separately in storage solves this, even if suboptimally).
My argument against such mechanism is that I think "The code is Law" is one of Ethereums big selling points (at least from my view). For "The code is Law" a contracts behavior should only be defined by its code and the messages send to it in the past. Further every contract interacting with another contract must be sure that the same is true for the other contract and a sudden disappearing that is not specified in its code is from my viewpoint unacceptable and a fundamental philosophical change. With a blockchain rent in place a contract may vanish from one point in time to another without receiving a message triggering it (even if a message must be send for deletion, It would lead to behavior not specified in the executed code (namely an contract deletion))
One thing that is so promissing by "The code is Law" philosophy is that it allows for autonomous contracts, that after they are developed does not need any maintainer or developer that looks after it and just work as defined by their code forever (except a backward incompatible change happens)
The option of an opt-in solution where the option for an ethernal storage still exist is better but still introduces a complete new dimension to Ethereum. Currently each opcode does only have an immediate effect but with a rent system temporal effects are introduced. An opcode can suddenly mean something like: the storage slot X is changed to Y and somewhere in the future this contract may be deleted. As a programmer and programming language designer the existence of such an opcode horrifies me.
I personally think, that something like this would be a so fundamental change that Ethereum would leave a niche behind fillable by another blockchain or by an old version of Ethereum where we still had "The code is Law".
jamesray1 commentedon Nov 10, 2017
Referenced here: https://ethresear.ch/t/the-stateless-client-concept/172.
With a stateless client concept an advantage is: "All of the thorny questions about state storage economics that lead to the need for designs like rent (eg. #35 http://github.com/ethereum/EIPs/issues/872 http://github.com/ethereum/EIPs/issues/882) and even the current complex SSTORE cost/refund scheme disappear, and blockchain economics can focus purely on pricing bandwidth and computation, a much simpler problem)".
wjmelements commentedon Nov 18, 2017
Rent-free storage is an attractive feature in a distributed VM/application platform, but it should be more expensive, while freeing state should be more lucrative.
fix(contract): claimPlayer1UnReveal function
github-actions commentedon Jan 21, 2022
There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.
github-actions commentedon Feb 5, 2022
This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.
Re-enable eipw (ethereum#35)
Merge pull request ethereum#35 from ChainAgnostic/add-cname
Re-enable eipw (ethereum#35)