EIP-1682: Storage Rent (#1682)

* add storage rent EIP

* clarifications

* Update eip-draft_storagerent.md

* Rename eip-draft_storagerent.md to eip-1682.md

* Added discussion URL

* Slight formatting fix

* Rename Simple Summary to Abstract (as per latest EIP-1 rules)

* Set status to Abandoned

* Added missing section placeholders
This commit is contained in:
Martin Holst Swende 2019-09-18 14:37:33 +02:00 committed by Alex Beregszaszi
parent e75245b058
commit 05447fbaee
1 changed files with 220 additions and 0 deletions

220
EIPS/eip-1682.md Normal file
View File

@ -0,0 +1,220 @@
---
eip: 1682
title: Storage Rent
author: Felix J Lange (@fjl), Martin Holst Swende (@holiman)
discussions-to: https://ethereum-magicians.org/t/storage-rent-eip/2357
status: Abandoned
type: Standards Track
category: Core
created: 2018-11-10
---
## Abstract
This EIP describes a scheme to charge for data in state, and 'archive' data which is no longer being paid for. It also describes how resurrection of 'archived' data happens.
## Motivation
The Ethereum blockchain in its current form is not sustainable because it grows
indefinitely. This is true of any blockchain, but Ethereum grows faster than most chains.
Many implementation strategies to slow down growth exist. A common strategy is 'state
pruning' which discards historical state, keeping only the active copy of contract data
and a few recent versions to deal with short-range chain reorganizations. Several
implementations also employ compression techniques to keep the active copy of the state as
small as possible.
A full node participating in consensus today requires storing large amounts of data even
with advanced storage optimizations applied. Future storage requirements are unbounded
because any data stored in a contract must be retained forever as dictated by the
protocol. This EIP attempts to correct this by adding new consensus rules that put an
upper bound on the size of the Ethereum state.
Adding these new rules changes fundamental guarantees of the system and requires a hard
fork. Users of Ethereum already pay for the creation and modification of accounts and
their storage entries. Under the rules introduced in this EIP, users must also pay to keep
accounts accessible. A similar rent scheme was proposed in [EIP-103] but rejected
even back then because the proposal would've upset peoples expectations. As implementers
of Ethereum, we still feel that state rent is the right path to long-term sustainability
of the Ethereum blockchain and that its undesirable implications can be overcome with
off-protocol tooling and careful design.
[EIP-103]: https://github.com/ethereum/EIPs/issues/35
## Specification
The cost of storing an account over time is called `rent`. The amount of `rent` due depends
on the size of the account. The `ether` that is paid for `rent` is destroyed. The `rent` is deducted whenever an account is touched.
`rent` can be paid from the account's regular `balance` or from its 'rent balance'. Accounts
can be endowed with `rent balance` through a new EVM opcode. When `rent` is charged, it is
first taken from the `rent balance`. When `rent balance` is zero, it is instead charged from the account's regular `balance` instead.
The reason to separate `balance` and `rent balance` is that certain contracts do not accept `ether` sends, or always send the entire balance off to some other destination. For these cases, a separate`rent balance` is required.
When an account's `balance` is insufficient to pay rent, the account becomes `inactive`. Its
storage and contract code are removed. Inactive accounts cannot be interacted with, i.e.
it behaves as if it has no contract code.
Inactive accounts can be restored by re-uploading their storage. To restore an inactive
account `A`, a new account `B` is created with arbitrary code and its storage modified
with `SSTORE` operations until it matches the storage root of `A`. Account `B` can restore
`A` through the `RESTORETO` opcode. This means the cost of restoring an account is
equivalent to recreating it via successive `SSTORE` operations.
### Changes To State
At the top level, a new key `size` is added to the accounts trie. This key tracks the
total number of trie nodes across all accounts, including storage trie nodes. To track
rent, the structure of account entries is changed as well.
Before processing the block in which this EIP becomes active, clients iterate the whole
state once to count the number of trie nodes and to change the representation of all
accounts to the new format.
#### Account Representation
```text
account = [nonce, balance, storageroot, codehash, rentbalance, rentblock, storagesize]
```
Each account gets three additional properties: `rentbalance`, `rentblock` and
`storagesize`.
The `rentbalace` field tracks the amount of `rent balance` available to the account. Upon
self-destruction any remaining `rent balance` is transferred to the beneficiary. Any
modification of the account recomputes its current `rent balance`.
The `rentblock` field tracks the block number in which the `rent balance` was last
recomputed. Upon creation, this field is initialized with the current block number.
`rentblock` is also updated with the current block number whenever the account is
modified.
The `storagesize` field tracks the amount of storage related to the account. It is a
number containing the number of storage slots currently set. The `storagesize` of an
inactive account is zero.
### Charging Rent
There is a new protocol constant `MAX_STORAGE_SIZE` that specifies the upper bound on the
number of state tree nodes:
```python
MAX_STORAGE_SIZE = 2**32 # ~160GB of state
```
A 'storage fee factor' for each block is derived from this constant such that fees
increase as the limit is approached.
```python
def storagefee_factor(block):
ramp = MAX_STORAGE_SIZE / (MAX_STORAGE_SIZE - total_storage_size(block))
return 2**22 * ramp
```
When a block is processed, `rent` is deducted from all accounts modified by transactions in
the block after the transactions have been processed. The amount due for each account is
based on the account's storage size.
```python
def rent(prestate, poststate, addr, currentblock):
fee = 0
for b in range(prestate[addr].rentblock+1, currentblock-1):
fee += storagefee_factor(b) * prestate[addr].storagesize
return fee + storagefee_factor(currentblock) * poststate[addr].storagesize
def charge_rent(prestate, poststate, addr, currentblock):
fee = rent(prestate, poststate, addr, currentblock)
if fee <= poststate[addr].rentbalance:
poststate[addr].rentbalance -= fee
else:
fee -= poststate[addr].rentbalance
poststate[addr].rentbalance = 0
poststate[addr].balance -= min(poststate[addr].balance, fee)
poststate[addr].rentblock = currentblock
```
### New EVM Opcodes
#### `PAYRENT <amount> <addr>`
At any time, the `rent balance` of an account may be topped up by the `PAYRENT` opcode.
`PAYRENT` deducts the given amount of `ether` from the account executing the opcode and adds
it to the `rent balance` of the address specified as beneficiary.
Any participant can pay the rent for any other participant.
Gas cost: TBD
#### `RENTBALANCE <addr>`
The `rent balance` of an account may be queried through the `RENTBALANCE` opcode. It pushes the
`rentbalance` field of the given address to the stack.
Gas cost: like `EXTCODEHASH`.
#### `SSIZE <addr>`
This opcode pushes the `storagesize` field of the given account to the stack.
Gas cost: like `EXTCODEHASH`.
#### `RESTORETO <addr> <codeaddr>`
This opcode restores the inactive account at the given address. This is a bit like
`SELFDESTRUCT` but has more specific semantics.
The account at `addr` must be `inactive` (i.e. have `storagesize` zero) and its
`storageroot` must match the `storageroot` of the contract executing `RESTORETO`. The
`codeaddr` specifies the address of a contract from which code is taken. The code of the
`codeaddr` account must match the `codehash` of `addr`.
If all these preconditions are met, `RESTORETO` transfers the storage of the account
executing the opcode to `addr` and resets its `storagesize` to the full size of the
storage. The code of `addr` is restored as well. `RESTORETO` also transfers any remaining
balance and rent balance to `addr`. The contract executing `RESTORETO` is deleted.
Gas cost: TBD
## Rationale
### Why do we need a separate rent balance?
Accounts need a separate rent balance because some contracts are non-payable, i.e. they
reject regular value transfers. Such contracts might not be able to keep themselves alive,
but users of those contracts can keep them alive by paying rent for them.
Having the additional balance also makes things easier for contracts that hold balance on
behalf of a user. Consider the canonical crowdfunding example, a contract which changes
behavior once a certain balance is reached and which tracks individual user's balances.
Deducting rent from the main balance of the contract would mess up the contract's
accounting, leaving it unable to pay back users accurately if the threshold balance isn't
reached.
### Why restoration?
One of the fundamental guarantees provided by Ethereum is that changes to contract storage
can only be made by the contract itself and storage will persist forever. If you hold a
token balance in a contract, you'll have those tokens forever. By adding restoration, we
can maintain this guarantee to a certain extent.
### Implementation Impact
The proposed changes tries to fit within the existing state transition model. Note that
there is no mechanism for deactivating accounts the moment they can't pay rent. Users must
touch accounts to ensure their storage is removed because we'd need to track all accounts
and their rent requirements in an auxlilary data structure otherwise.
## Backwards Compatibility
TBA
## Test Cases
TBA
## Implementation
TBA
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).