Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed initial abstraction changes for Metropolis #86

Open
vbuterin opened this Issue Apr 2, 2016 · 44 comments

Comments

Projects
None yet
@vbuterin
Copy link
Collaborator

commented Apr 2, 2016

This document is outdated; see #208 for current details.

Specification

If block.number >= METROPOLIS_FORK_BLKNUM, then:

  1. If the signature of a transaction is (0, 0, 0) (ie. v = r = s = 0), then treat it as valid and set the sender address to 2**160 - 1
  2. Set the address of any contract created through a creation transaction to equal sha3(0 + init code) % 2**160, where + represents concatenation, replacing the earlier address formula of sha3(rlp.encode([sender, nonce]))
  3. Create a new opcode at 0xfb, CREATE_P2SH, which sets the creation address to sha3(sender + init code) % 2**160. If a contract at that address already exists, fails and returns 0 as if the init code had run out of gas.

Rationale

The goal of these changes is to set the stage for abstraction of account security. Instead of having an in-protocol mechanism where ECDSA and the default nonce scheme are enshrined as the only "standard" way to secure an account, we take initial steps toward a model where in the long term all accounts are contracts, contracts can pay for gas, and users are free to define their own security model.

Under EIP 86, we can expect users to store their ether in contracts, whose code might look like the following (example in Serpent):

# Get signature from tx data
sig_v = ~calldataload(0)
sig_r = ~calldataload(32)
sig_s = ~calldataload(64)
# Get tx arguments
tx_nonce = ~calldataload(96)
tx_to = ~calldataload(128)
tx_value = ~calldataload(160)
tx_gasprice = ~calldataload(192)
tx_data = string(~calldatasize() - 224)
~calldataload(tx_data, 224, ~calldatasize())
# Get signing hash
signing_data = string(~calldatasize() - 64)
~mstore(signing_data, tx.startgas)
~calldataload(signing_data + 32, 96, ~calldatasize() - 96)
signing_hash = sha3(signing_data:str)
# Perform usual checks
prev_nonce = ~sload(-1)
assert tx_nonce == prev_nonce + 1
assert self.balance >= tx_value + tx_gasprice * tx.startgas
assert ~ecrecover(signing_hash, sig_v, sig_r, sig_s) == <pubkey hash here>
# Update nonce
~sstore(-1, prev_nonce + 1)
# Pay for gas
~send(MINER_CONTRACT, tx_gasprice * tx.startgas)
# Make the main call
~call(msg.gas - 50000, tx_to, tx_value, tx_data, len(tx_data), 0, 0)
# Get remaining gas payments back
~call(20000, MINER_CONTRACT, 0, [msg.gas], 32, 0, 0)

This can be thought of as a "forwarding contract". It accepts data from the "entry point" address 2**160 - 1 (an account that anyone can send transactions from), expecting that data to be in the format [sig, nonce, to, value, gasprice, data]. The forwarding contract verifies the signature, and if the signature is correct it sets up a payment to the miner and then sends a call to the desired address with the provided value and data.

The benefits that this provides lie in the most interesting cases:

  • Multisig wallets: currently, sending from a multisig wallet requires each operation to be ratified by the participants, and each ratification is a transaction. This could be simplified by having one ratification transaction include signatures from the other participants, but even still it introduces complexity because the participants' accounts all need to be stocked up with ETH. With this EIP, it will be possible to just have the contract store the ETH, send a transaction containing all signatures to the contract directly, and the contract can pay the fees.
  • Ring signature mixers: the way that ring signature mixers work is that N individuals send 1 coin into a contract, and then use a linkable ring signature to withdraw 1 coin later on. The linkable ring signature ensures that the withdrawal transaction cannot be linked to the deposit, but if someone attempts to withdraw twice then those two signatures can be linked and the second one prevented. However, currently there is a privacy risk: to withdraw, you need to have coins to pay for gas, and if these coins are not properly mixed then you risk compromising your privacy. With this EIP, you can pay for gas straight our of your withdrawn coins.
  • Custom cryptography: users can upgrade to ed25519 signatures, Lamport hash ladder signatures or whatever other scheme they want on their own terms; they do not need to stick with ECDSA.
  • Non-cryptographic modifications: users can require transactions to have expiry times (this being standard would allow old empty/dust accounts to be flushed from the state securely), use k-parallelizable nonces (a scheme that allows transactions to be confirmed slightly out-of-order, reducing inter-transaction dependence), or make other modifications.

(2) and (3) introduce a feature similar to bitcoin's P2SH, allowing users to send funds to addresses that provably map to only one particular piece of code. Something like this is crucial in the long term because, in a world where all accounts are contracts, we need to preserve the ability to send to an account before that account exists on-chain, as that's a basic functionality that exists in all blockchain protocols today.

Miner strategy

Note that miners would need to have a strategy for accepting these transactions. This strategy would need to be very discriminating, because otherwise they run the risk of accepting transactions that do not pay them any fees, and possibly even transactions that have no effect (eg. because the transaction was already included and so the nonce is no longer current). One simple approach is to have a whitelist for the codehash of accounts that they accept transactions being sent to; approved code would include logic that pays miners transaction fees. However, this is arguably too restrictive; a looser but still effective strategy would be to accept any code that fits the same general format as the above, consuming only a limited amount of gas to perform nonce and signature checks and having a guarantee that transaction fees will be paid to the miner. Another strategy is to, alongside other approaches, try to process any transaction that asks for less than 250,000 gas, and include it only if the miner's balance is appropriately higher after executing the transaction than before it.

See https://www.reddit.com/r/ethereum/comments/5ab69v/metropolis_protocol_change_proposal_highlight_for/ for reddit discussion.

@gavofyork

This comment has been minimized.

Copy link

commented Apr 2, 2016

mixhash and nonce are all substituted with the empty string

unlike for the powhash where they are not present at all :-/

@gavofyork

This comment has been minimized.

Copy link

commented Apr 2, 2016

1/2/3 mean state trie will grow indefinitely large (so far there would be an additional ~50MB on it, a substantial increase on the pruned, compressed state db)

@gavofyork

This comment has been minimized.

Copy link

commented Apr 2, 2016

key, mstart, msz -> key, mstart, msize or ky, mst, msz. don't mix naming conventions.

@eth1au

This comment has been minimized.

Copy link

commented Apr 2, 2016

Solid. V, can you show Gav the DK Gas email. JG

@gavofyork

This comment has been minimized.

Copy link

commented Apr 2, 2016

for 9, what happens when there's a collision?

@gavofyork

This comment has been minimized.

Copy link

commented Apr 2, 2016

it should also be noted that (12) and (13) are suggested miner alterations, but do not form part of the consensus protocol.

@eth1au

This comment has been minimized.

Copy link

commented Apr 2, 2016

Perhaps. Alas, essential weekend reading: http://www.mit.edu/~mrognlie/piketty_diminishing_returns.pdf

@vbuterin

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 2, 2016

Fixed the naming conventions for msz.

1/2/3 mean state trie will grow indefinitely large (so far there would be an additional ~50MB on it, a substantial increase on the pruned, compressed state db)

However, note that this growth is constant in tx load; right now, it's 20% of the state size, but in a future environment where we see 5-10 tx/sec, it will be much smaller, and we should optimize for that case. Additionally, if we are still uncomfortable with storing ALL state roots, then there is a compromise solution where we store the state roots at key block.number % CYCLE_LENGTH (eg. CYCLE_LENGTH = 65536), and in the STATEROOT and BLOCKHASH opcodes keep the condition that we return 0 if block.number - target_height >= CYCLE_LENGTH)

for 9, what happens when there's a collision?

I would say failure (either do nothing and return zero, or exception). We could remove the issue entirely by adding a nonce back in to the formula, but I want to avoid this because one of my goals with EIP 101 is the desire to remove the notion of in-protocol sequence numbers entirely, moving all of that logic to the protocol level. So if you want to create multiple instances of the same contract, you can just append a nonce after the code, and maintain and increment the nonce yourself in storage; this can all be done with a few EVM operations, and simplifies the protocol further as we don't have to deal with the current logic around when nonces are and aren't incremented within consensus code.

@vbuterin

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 3, 2016

Changed the hash with state root/mixhash/nonce as empty strings into the standard PoW hash, but with the state root replaced with the empty string. Added rationale to post; here it is copied for convenience:

The state root needs to be set to the empty string because (2) is itself a state change, so the actual final state root is not known (and neither are the mixhash and nonce).

@chriseth

This comment has been minimized.

Copy link
Contributor

commented Apr 5, 2016

What happens if a slot is SLOAD'ed from that has been SSTOREBYTES'ed before?

@vbuterin

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 6, 2016

Last 32 bytes, left-zero-padded.

@chriseth

This comment has been minimized.

Copy link
Contributor

commented Apr 6, 2016

@vbuterin Still many open questions regarding the variable-size slots. Is the general idea that SSTORE behaves identical to SSTOREBYTES with 32 bytes and SLOAD identical to SLOADBYTES plus padding? (I would prefer padding at the less-significant end, by the way).

So using SSIZE for not yet used slots returns zero, but it returns 32 for slots where we wrote 0. This is still a bit inconsistent with how solidity uses storage, but it might work.

Furthermore, I am not sure that the memory exceptions for SLOADBYTES are a good idea (different case than for CALL) because we do know the size.

Also it could be considered whether SLOADBYTES should zero-pad to multiples of 32 - that would be more consistent with our ABI.

@vbuterin

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 8, 2016

Is the general idea that SSTORE behaves identical to SSTOREBYTES with 32 bytes and SLOAD identical to SLOADBYTES plus padding?

Yes, with the exception that you can use SSTOREBYTES and not SSTORE to save 32 zero bytes, as you brought up.

Furthermore, I am not sure that the memory exceptions for SLOADBYTES are a good idea (different case than for CALL) because we do know the size.

True. I'm fine either way.

Also it could be considered whether SLOADBYTES should zero-pad to multiples of 32 - that would be more consistent with our ABI.

I would prefer no. It's not consistent with the ABI, but the ABI is a high-level construct. Return slices of CALL and the like are not zero-padded to multiples of 32, and it is consistent with that.

@chriseth

This comment has been minimized.

Copy link
Contributor

commented Apr 20, 2016

12: I think we should still perform some static analysis on the target contract, it is easy to spam miners with a lot of txes with small startgas.

@chriseth

This comment has been minimized.

Copy link
Contributor

commented Apr 20, 2016

General concern: If we move nonce checking into the contract, to we still enforce the requirement that the chain should never include two transactions with the same hash?

@chriseth

This comment has been minimized.

Copy link
Contributor

commented Apr 20, 2016

9: I would guess that none of the existing contracts relies on the actual way the new address is calculated in a create, so changing that is fine. What I'm worried about is that previously, two create calls with identical code from the same contract did not result in a collision but now they do, because existing code does not add a nonce to the init code.

@vbuterin

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 21, 2016

to we still enforce the requirement that the chain should never include two transactions with the same hash?

I would say no. One of the key positive side effects of an eventual universal adoption of EIP 101 is that transaction validity becomes statically evaluable, which means that blocks can be validated independently of having the prior state.

What I'm worried about is that previously, two create calls with identical code from the same contract did not result in a collision but now they do, because existing code does not add a nonce to the init code.

True. If we decide that this is a problem, one simple solution would be that if the destination address contains code, we keep incrementing the address by 1 (wrapping around if needed) until it doesn't, and run the code at the first address we find that is empty.

@Smithgift

This comment has been minimized.

Copy link

commented May 4, 2016

👍 on incrementing address on collision. In addition (ha) to all current contract-creating contracts continuing (ha ha) to function, it permanently resolves the question of two contracts accidentally having the same address.

@tromer

This comment has been minimized.

Copy link

commented Jul 29, 2016

Letting contracts pay fees is also needed by Zerocash over Ethereum (zcash-hackworks/babyzoe#1).

@pirapira

This comment has been minimized.

Copy link
Member

commented Mar 1, 2017

#208 seems to be the new version.

@Dexaran

This comment has been minimized.

Copy link

commented Apr 30, 2017

As I understand there will be a special contract at sha3(sender + init code) % 2**160 address. This special contract will be linked to the account. A special contract can be called when a transaction occurs to an account (the address associated with that contract). Is it right?
It seems like I'm missing the point.

@Smithgift

This comment has been minimized.

Copy link

commented Apr 30, 2017

@Dexaran: The point is that it's much safer to say "Pay this uncreated contract's address's X ether" when that address is stable. Currently doing so requires never accidentally using the right nonce.

@Dexaran

This comment has been minimized.

Copy link

commented Apr 30, 2017

I'm asking about realization details. I need to adapt ERC #223 implementation to Metropolis.
I need to know how should token transaction work with Externally Owned Account(EOA) in Metropolis.

I'm now assembling receivers code size to know if receiver is a contract. If receiver is contract (code size > 0) tokenFallback function should be executed. If the receiver is an EOA, the tokenFallback function should not be executed.

I need to know:

  1. Will each EOA be assigned with a special contract that must be executed for each transaction in the EOA?
  2. Can the EOA-assigned contract contain the tokenFallback function, which should be executed?

I'm ready to implement any useful suggestion about adaptation of ERC #223 to Metropolis.

@Smithgift

This comment has been minimized.

Copy link

commented Apr 30, 2017

Ah. As far as I know, this version does not force externally owned accounts to be contracts, but that will eventually become the case (in future hardforks).

When that occurs, what's in each contract will be up to the users. So they might have a given function implemented, or they might not.

@Dexaran

This comment has been minimized.

Copy link

commented May 1, 2017

Thank you for explanation @Smithgift
Now my questions are:

  1. How can I check whether the recipient is a contract or an EOA with a bytecode in Metropolis?
  2. How can I check if the tokenFallback function is implemented by the recipient? Is there a way to check if the contract implements a certain function?
@Smithgift

This comment has been minimized.

Copy link

commented May 1, 2017

  1. I don't think there's any theoretical difference between a contract and an EOA-with-bytecode. You might be able to examine the code to see if it matches common patterns for an EOA.
  2. I think you might be able to examine the dispatcher of a contract to see if a given function with a given signature is present. It's a little beyond me exactly how to do that, though. The Ethereum stackexchange or solidity gitter might be of more help.
@Dexaran

This comment has been minimized.

Copy link

commented May 1, 2017

There is a great difference between contract and EOA-with-bytecode for ERC #223 token transaction:

If the receiver is an EOA with the implementation of tokenFallback, then tokenFallback must be executed.

If receiver is an EOA without tokenFallback implementation, then token transaction must submit without a try to execute tokenFallback (a try to execute tokenFallback will fail a transaction in this case).

If the receiver is a contract, tokenFallback must be executed without distinctions whether tokenFallback is implemented or not. (if there is no tokenFallback implementation on this receiver contract then transaction must fail)

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

commented May 12, 2017

Is this still slated for Metropolis? Is "mid-summer" still looking reasonably likely for Metropolis?

I ask because I have a need to create a proxy-contract with a non-trivial ownership pattern and I'm wondering if I should try to create it now, and deal with all of the UI complexities that come with trying to interact with dApps through a proxy contract (I believe at the moment this is basically impossible if I want to use their UI) or if I should wait for Metropolis, which it sounds like will allow for this without having to write a custom UI for every dApp out there.

I believe uPort also has this problem, where if you want to use a dApp with uPort the dApp needs to be built to support uPort or you need to use uPort as your transaction processor (instead of a local node) so they can handle all of the complexities server-side with wrapping up your transaction calls and piping them through their proxy.

@fulldecent

This comment has been minimized.

Copy link
Contributor

commented Mar 22, 2018

@Arachnid You nominated this as an "EIPs that should be merged". Can you please share your notes on that here?

1 similar comment
@fulldecent

This comment has been minimized.

Copy link
Contributor

commented Mar 22, 2018

@Arachnid You nominated this as an "EIPs that should be merged". Can you please share your notes on that here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.