ethereum / EIPs

The Ethereum Improvement Proposal repository

Home Page:https://eips.ethereum.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

State trie clearing (invariant-preserving alternative)

gavofyork opened this issue · comments

EDITOR UPDATE (2017-08-15): This EIP is now located at https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md. Please go there for the correct specification. The text below may be incorrect or outdated, and is not maintained.

Specification

a. Account creation transactions and the CREATE operation SHALL, prior to the execution of the initialisation code, increment the nonce over and above its normal starting value by one (for normal networks, this will be simply 1, however test-nets with non-zero default starting nonces will be different).

b. Whereas CALL and SUICIDE would charge 25,000 gas when the destination is non-existent, now the charge SHALL only be levied if the operation transfers more than zero value and the destination account is dead.

c. No account may change state from non-existent to existent-but-empty. If an operation would do this, the account SHALL instead remain non-existent.

d. At the end of the transaction, any account touched by the execution of that transaction which is now empty SHALL instead become non-existent (i.e. deleted).

Where:

An account is considered to be touched when it is involved in any potentially state-changing operation. This includes, but is not limited to, being the recipient of a transfer of zero value.

An account is considered empty when it has no code and zero nonce and zero balance.

An account is considered dead when either it is non-existent or it is empty.

At the end of the transaction is immediately following the execution of the suicide list, prior to the determination of the state trie root for receipt population.

An account changes state when:

  • it is the target or refund of a SUICIDE operation for zero or more value;
  • it is the source or destination of a CALL operation or message-call transaction transferring zero or more value;
  • it is the source or newly-creation of a CREATE operation or contract-creation transaction endowing zero or more value;
  • as the block author ("miner") it is recipient of block-rewards or transaction-fees of zero or more.

Notes

In the present Ethereum protocol, it should be noted that very few state changes can ultimately result in accounts that are empty following the execution of the transaction. In fact there are only four contexts that current implementations need track:

  • an empty account has zero value transferred to it through CALL;
  • an empty account has zero value transferred to it through SUICIDE;
  • an empty account has zero value transferred to it through a message-call transaction;
  • an empty account has zero value transferred to it through a zero-gas-price fees transfer.

Rationale

Same as #158 except that several edge cases are avoided since we do not break invariants:

  • that an account can go from having code and storage to not having code or storage mid-way through the execution of a transaction; [corrected]
  • that a newly created account cannot be deleted prior to being deployed.

CREATE avoids zero in the nonce to avoid any suggestion of the oddity of CREATEd accounts being reaped half-way through their creation.

An account is considered empty when it has no code and zero nonce and balance.

For clarity, shouldn't this be

An account is considered empty when it has no code, no storage and zero nonce and balance.

@holiman I think that issue has been described in #158 and we want to ignore storage.

There is one other difference between this and #158: in #158, an account being empty also requires the storage root to be null, and it's only for the purposes of the account existence extern (used to sometimes charge 25000) gas that storage is ignored. Here, it seems like we delete an account if it has no nonce, balance or code even if it has storage.

I am confused: in #158, what are the cases in which a newly created account can be deleted prior to being deployed, or an account can go from having code and storage to not having code or storage mid-way through the execution of a transaction? Would be good to get more explanation there.

It's true; that said I'd argue it's a similar edge case: An account can only have storage and no code if it was put there in init and since code execution is the only means of an account's storage interacting with anything within Ethereum's state, it is therefore inert. In any case, I'm happy to require no storage, too if desired.

CREATE -- [A's init code executing under A's account] -> B.CALL -> SUICIDE(A) [A now gets deleted] -> A... {A has been "deleted" mid-way through creation yet is still the subject of execution leading to oddities should an SSTORE happen}

On the last point I think you're right - an account can't go from having code + storage to having no code + no storage within a transaction.

A has been "deleted" mid-way through creation

SUICIDE doesn't delete an account immediately; the deletion happens at the end of processing the transaction.

I'd prefer to require an empty storage root for deletion (though not for applying the 25000 gas penalty), as this way it allows the rule to be understood as nonexistence being a canonical encoding for (0, 0, '', '') rather than a weird custom rule that says that under some set of circumstances a bunch of storage keys have to be deleted too.

SUICIDE doesn't delete an account immediately; the deletion happens at the end of processing the transaction.

Indeed. However, in this case, A is the target, B is the source. B is suiciding to A with no funds, which, in #158, has the effect of placing B on the suicides list and immediately deleting the otherwise empty A.

though not for applying the 25000 gas penalty
...
rather than a weird custom rule

I'm not sure I see the difference between a "weird custom rule" for one thing and exactly the same custom rule applied to something else.

Furthermore, I'm not sure of the rationale for it - if it's simply there in order to help some particular client implementation's caching optimisations then I can't say I think it's a particularly great upside compared to the wart it introduces.

if it's simply there in order to help some particular client implementation's caching optimisations

I would say it's about simplifying the interface. If you ignore storage roots for the purpose of gas calculations, it means that the only kinds of queries that can be made during EVM execution are balance, nonce, code and individual state entry queries. If you admit the possibility of state emptiness queries, then that's a fundamentally different and more complex thing. It's sort of like why we decided not to add a "find the nearest key to X in the storage tree" opcode.

I see; but then if we're already specifying emptiness in some way that pointedly ignores storage, is it really such a weird custom rule to reuse that same definition elsewhere? Especially in light of the fact that empty-code implies that the emptiness of storage be strictly irrelevant.

An account is considered empty when it has no code and zero nonce and zero balance.

I disagree. An account should be considered empty when the code, nonce, balance and state root is empty.

Along the lines of state pruning, is there any reason we need to store multiple copies of code if the same contracts use the same code? ie if I upload y token contract and have a contract that does that / creates it a bunch of times, why does it store the code multiple times instead of just once

This EIP needs to specify the block number (2675000).

This EIP is now located at https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md. Please go there for the correct specification. The text in this issue may be incorrect or outdated, and is not maintained.