⚠️ This EIP is not recommended for general use or implementation as it is likely to change.

EIP-1418: Blockchain Storage Rent Payment Source

AuthorWilliam Entriken
Discussions-Tohttps://github.com/ethereum/EIPs/issues/1418
StatusDraft
TypeStandards Track
CategoryCore
Created2018-09-16

Simple Summary

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

Abstract

The naive implementation is to simply loop through every account on each block and deduct a certain fee.

We also show an equivalent technique that is easier to implement. Also we review practical considerations of switching to a fee-based rent system.

In other words, product=0; while(factor1--) product+= factor2; is slow, but equivalently product = factor1 * factor2 is fast. And we can reason about both at the same time.

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

New state variables (per account)

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

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)
  • σ[a]_rentEvictBlock – the block number when this account will be destructed

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
  • RENT_STIPEND – A small amount of rent created during transactions
  • FORK_BLOCK – When implementation starts
  • RENT_EVICTIONS_START – First block with evictions

New opcodes

  • RENTBALANCE(address) – G_BALANCE – Similar to BALANCE
    • This returns the logical σ[a]_rent value which is defined to reduce each block. The implementation may however instead simply store σ[a]_rentBalanceWhenLastPaid and subtract to get the updated value.
  • 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)
    
    ASSERT(σ[account]_rentEviction >= NUMBER) // TODO: I'm not sure if should be > or >=
    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
    σ[account]_rentLastPaid = NUMBER
    σ[account]_rentEvictBlock = NUMBER + ⌊σ[account]_rent / cost_per_block⌋
END PAYRENT
  • SSTORE(account, key, value)
    • Perform PAYRENT(account)
    • Do normal SSTORE operation
    • If the old value was zero for this [account, key] and the new value is non-zero, then σ[account]_storageWord++
    • If the old value was non-zero for this [account, key] and the new value is zero, then σ[account]_storageWords--
  • CALL (and derivatives)
    • If value > 0 then perform PAYRENT(account)
    • Do normal CALL operation
  • CREATE
    • Set σ[account]_rentLastPaid = NUMBER
    • Do normal CREATE operation
    • Note: it is possible there is a pre-existing rent balance here

Updated substate

The substate tuple is defined as:

A ≡ (As, Al, At, Ar)

This includes A_t, “the set of touched accounts, of which the empty ones are deleted at the end of a transaction”.

This definition is updated to: “the set of touched accounts, of which the empty ones or evicted ones (BLOCK >= σ[a]_rentEvictBlock) are deleted at the end of a transaction”

// TODO: I’m not sure if that should be > or >=

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.

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.

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 money to keep their balances so we charge them rent.

Shouldn’t we spend value (Ether balance) when rent is depleted?

Yes. This is a draft, I didn’t add this in yet. But basically:

  • Rename rentEvictBlock to rentUsingValueBlock
  • Update eviction calculation to include RENT + VALUE. Also update CALL (and friends) operations to recalculate eviction date when value is transferred. This is the new rentEvictBlock.
  • Update CALL (and friends), RENTBALANCE and SENDRENT operations. If NUMBER >= rentUsingValueBlock then proceed as if rent started paying using value.

Your contract can be evicted and disappeared

Yes, if you do not pay rent for your account or contract then you lose it all. User education is required.

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.

Permanent removal

All state about an account is destructed during eviction. The data cannot be recovered. That’s the point.

Hint to implementers: make sure this works:

  1. Send value to a new account (gets stipend)
  2. Pay rent to that account
  3. Wait until after the rent expires (account is gone)
  4. Send value to that account (gets stipend again)
  5. Deploy a contract (CREATE) to that account (stipend gets topped off)

Rationale – 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
  • RENT_EVICTIONS_START – a block about 360 days after FORK_BLOCK

The rent is priced in cold, hard Ether. It is not negotiated by clients, it is not dynamic. It is linear. Why is this a good idea? Because right now Ethereum is a system with multiple free variables – Ether/gas price, gas/opcodes costs, Ether/block reward. [Add some note here about reducing a system of equations…] So the end result is that we can peg one of the values and it will be okay.

Q: There is a finite-ish amount of Ether and this proposal introduces a word-price in Ether, do math for me. A: The current size of Ethereum is about ~1 TB, maybe half of that is branch nodes. So that’s like 15B words. There is about 100M Ether mined. The answer is that all the Ether can be spent on 400 terabyte-years of storage. I’m not sure if it is helpful to look at it that way.

Backwards compatibility

There is a 360-day transition period (related to RENT_STIPEND). On the block of the fork, every account is immediately funded with enough rent to pay for ~ 360 days’ worth of their current storage requirements. The formal implementation is that this new rule is applied if any existing account has σ[account]_rentLastPaid = 0. Therefore this can be implemented by clients lazily or eagerly.

Preexisting accounts which increase their storage needs will evict sooner than 360 days.

Users will need to be educated.

Test Cases

TO BE ADDED

Implementation

TO BE ADDED

Copyright

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

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