forked from DecentralizedClimateFoundation/DCIPs
221 lines
9.2 KiB
Markdown
221 lines
9.2 KiB
Markdown
---
|
|
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: Withdrawn
|
|
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](../LICENSE.md).
|