mirror of https://github.com/status-im/EIPs.git
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:
parent
e75245b058
commit
05447fbaee
|
@ -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/).
|
Loading…
Reference in New Issue