Alert Source Discuss
🚧 Stagnant Standards Track: Core

EIP-1418: Blockchain Storage Rent Payment

At each block, deduct value from every account based on the quantity of storage used by that account.

Authors William Entriken (@fulldecent)
Created 2018-09-16
Discussion Link https://ethereum-magicians.org/t/eip-1418-storage-rent/10737
Requires EIP-1559

Abstract

At each block, deduct an amount of value (“rent”) from every account based on the quantity of storage used by that account.

Motivation

Ethereum is a public utility and we are underpricing the long-term costs of storage. Storage cost can be approximately modeled as bytes × time.

Specification

Updated transaction type

A new transaction type is introduced. Whereas EIP-1559 introduced warm access for contract state, this new type introduces warm access for contract code.

New state variables (per account)

  • σ[a]_rent – an amount of value, in Wei, this is a signed value
  • σ[a]_storageWords – number of words in storage

New constants

  • RENT_WORD_COST – The rent cost, in Wei, paid for each word-block
  • RENT_ACCOUNT_COST – The rent cost, in Wei, paid for each account-block
  • FORK_BLOCK – When implementation starts

New opcodes

  • RENTBALANCE(address) – G_BALANCE – Similar to BALANCE
    • This returns the logical σ[a]_rent value which is defined to reduce each block. It is possible for the implementation to calculate this value using the recommended implementation variables, rather than storing and updating σ[a]_rent every block for every account.
  • SENDRENT(address, amount) – G_BASE – Convert value to rent and send to account
    1. σ[account]_rent += amount
    2. σ[msg.sender]_balance -= amount

Updated opcodes

A new subroutine, paying for rent, is established as such:

PAYRENT(account)
    blocks_to_pay = NUMBER - σ[account]_rentLastPaid
    cost_per_block = RENT_ACCOUNT_COST + RENT_WORD_COST * (⌈∥σ[account]_code∥ / 32⌉ + * σ[a]_storageWords)
    rent_to_pay = blocks_to_pay * cost_per_block
    σ[account]_rent -= rent_to_pay
    if σ[account]_rent < 0
    		σ[account]_value += σ[account]_rent
    		σ[account]_rent = 0
    end
    if σ[account]_value < 0
    		σ[account]_rent = σ[account]_value
    		σ[account]_value = 0
    end
    σ[account]_rentLastPaid = NUMBER
    σ[account]_rentEvictBlock = NUMBER + ⌊σ[account]_rent / cost_per_block⌋
END PAYRENT
  • SSTORE(account, key, value)
    • Perform PAYRENT(account)
    • If account is evicted (i.e. NUMBER > σ[account]_rentEvictBlock) then transaction fails unless using the new transaction type and sufficient proofs are included to validate the old storage root and calculate the new root.
    • Do normal SSTORE operation
    • If the old value was zero for this [account, key] and the new value is non-zero, then σ[account]_storageWords++
    • If the old value was non-zero for this [account, key] and the new value is zero, then σ[account]_storageWords--, and if the result is negative then set to zero
  • SLOAD(account, key)
    • If account is evicted (i.e. NUMBER > σ[account]_rentEvictBlock) then transaction fails unless using the new transaction type and sufficient proofs are included to validate the existing storage root and the existing storage value.
    • Do normal SLOAD operation.
  • CALL (and derivatives)
    • If the target block is evicted (i.e. NUMBER > σ[account]_rentEvictBlock) then transaction fails unless using the new transaction type and sufficient proof is included to validate the existing code.
    • Do normal CALL operation
  • CREATE
    • Set σ[account]_rentLastPaid = NUMBER
    • Do normal CREATE operation
    • σ[account]_storageWord = 0
    • Note: it is possible there is a pre-existing rent balance here

New built-in contract

  • PAYRENT(address, amount) – Calls PAYRENT opcode
    • This is a convenience for humans to send Ether from their accounts and turn it into rent. Note that simple accounts (CODESIZE == 0) cannot call arbitrary opcodes, they can only call CREATE or CALL.
    • The gas cost of PAYRENT will be 10,000 or lower if possible.

Calculating σ[account]_storageWord for existing accounts

DRAFT…

It is not an acceptable upgrade if on the fork block it is necessary for only archive nodes to participate which know the full storage amount for each account.

An acceptable upgrade will be if the required σ[account]_storageWord can be calculated (or estimated) incrementally based on new transaction activity.

DRAFT: I think it is possible to make such an acceptable upgrade using an unbiased estimator

  • add one bit of storage per SSTORE for legacy accounts on the first access of a given key
  • add log(n) bits for each trie level
  • assume that storage keys are a random variable

To think more about…

No changes to current opcode gas costs.

Rationale

No call

A contract will not know or react to the receipt of rent. This is okay. Workaround: if a contract really needed to know who provided rent payments then it could create a function in its ABI to attribute these payments. It is already possible to send payments to a contract without attribution by using SELFDESTRUCT. Other blockchains like TRON allow to transfer value to a contract without performing a call.

Eviction responsibility / lazy evaluation

The specification gives responsibility for eviction to the consensus clients. This is the most predictable behavior because it happens exactly when it should. Also there need not be any incentive mechanism (refund gas, bounty) for outside participants (off-chain) to monitor accounts and request removal.

It is possible that an arbitrary number of accounts will be evicted in one block. That doesn’t matter. Client implementations do not need to track which accounts are evicted, consensus is achieved just by agreeing on the conditions under which an account would be evicted.

No converting rent to value

Ether converted to rent cannot be converted back. Anybody that works in accounting and knows about gifts cards should tell you this is a good idea. It makes reasoning about the system much easier.

Accounts pay rent

Yes, they pay rent. It costs resources to account for their balances so we charge them rent.

Why do you need a separate rent account?

Because anybody/everybody can contribute to the rent account. If you depend on a contract, you should contribute to its rent.

But the contract can spend all of its value.

By maintaining a separate rent and value balance, this allows people to contribute to the rent while being confident that this is allowing the contract to stay around.

NOTE: cloning. With this EIP, it may become feasible to allow storage cloning. Yes really. Because the new clone will be paying rent. See other EIP, I think made by Augur team.

Economics & constants

An SSTORE executed in 2015 cost 20,000 gas and has survived about 6 million blocks. The gas price has been around 1 ~ 50 Gwei. So basically 4,000 Wei per block per word so far. Maybe storing an account is 10 times more intensive than storing a word. But actually G_transaction is 21,000 and G_sstore is 20,000 so these are similar and they can both create new accounts / words.

How about:

  • RENT_WORD_COST – 4,000 Wei
  • RENT_ACCOUNT_COST – 4,000 Wei
  • FORK_BLOCK – when implementation starts

The rent is priced in cold, hard Ether. It is not negotiated by clients, it is not dynamic.

A future EIP may change this pricing to be dynamic. For example to notarize a block, notaries may be required to prove they have the current storage dataset (excluding evictions). Additionally, they may also prove they have the dataset plus evictions to earn an additional fee. The relative storage of the evicted accounts, and the other accounts versus the value of the additional fee may be used as a feedback mechanism to set a market price for storage.

FYI, there are about 15B words in the Ethereum Mainnet dataset and about 100M total Ether mined. This means if all Ether was spent on storage at current proposed prices it would be 400 terabyte-years of storage. I’m not sure if it is helpful to look at it that way.

Backwards Compatibility

EIP-1559 already introduces a mechanism for nodes to participate without recording the full network state and for clients to warm cache with storage data in their type 2 transactions.

Users will need to be educated.

Many smart contracts allow anybody to use an arbitrary amount of storage in them. This can limit the usefulness of deploying this proposal on an existing chain.

Recommended implementation variables (per account)

  • σ[a]_rentLastPaid – a block number that is set when:
    • Value is transferred into an account (CREATE, CALL, SELFDESTRUCT)
    • Code is set for an account (CREATE)
    • An account’s storage is updated (SSTORE)
    • This begins with a logical value of FORK_BLOCK for all accounts
  • σ[a]_rentEvictBlock – the block number when this account will be evicted

Storage note

For every account that is evicted, clients may choose to delete that storage from disk. A future EIP may make an incentive to keep this extra data for a fee. A future EIP may create a mechanism for clients to exchange information about these storage states.

Security Considerations

Many smart contracts allow anybody to use an arbitrary amount of storage in them.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

William Entriken (@fulldecent), "EIP-1418: Blockchain Storage Rent Payment [DRAFT]," Ethereum Improvement Proposals, no. 1418, September 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1418.