Skip to content

Account abstraction for main chain #859

Closed
@vbuterin

Description

The following is a copy of the account abstraction proposal discussed here but coalesced into one piece and adapted for the ethereum main chain by adding back in mandatory in-transaction nonces.

Specification

A new type of transaction is allowed, with the following format:

    [
        chain_id,      # 1 on mainnet
        target,        # account the tx goes to
        nonce,        # transaction nonce for replay protection
        data,          # transaction data
        start_gas,     # starting gas
        code,           # initcode of the target (for account creation, most of the time should be empty)
        salt,        # contract creation salt (must be 0 or 32 bytes)
    ]

Executing a transaction of this format is done according to the following rules:

  1. Set exec_gas = start_gas - intrinsic_gas, where intrinsic_gas is 21000 + 4 gas per zero byte in data+code + 68 gas per nonzero byte in data+code.
  2. If the account code of the target is empty, then attempt to create the account with the specified code. Specifically, follow these steps:
    • Assert salt is nonempty
    • Assert sha3(salt++ code)[12:] == target
    • Attempt to create a contract with to address target, init code code, sender ENTRY_POINT (ie. 0xffff...ff), value 0, gas remaining_gas - CONTRACT_CREATION_COST, gasprice 0 (calldata equals init code, just like contract creations work now)
    • Assert that the contract creation succeeds.
    • Decrease exec_gas to the amount of gas remaining after the contract creation finishes.
  3. Assert that the transaction nonce matches the target account nonce. Increment the target account nonce by 1.
  4. Process a message with sender ENTRY_POINT, to address target, value 0, gasprice 0, gas exec_gas.
  5. Assert that the execution either succeeded, or PAYGAS_CALLED = True (see below). Refund unpaid gas to the target (ie. set target.balance += remaining_gas * PAYGAS_GASPRICE).

If any of the asserts fails, the transaction is invalid. Note that this includes the assert in step 5; that is, if a top-level transaction execution fails, and PAYGAS_CALLED is False, then the entire transaction is invalid.

PAYGAS

The abstraction is simplified with a new PAYGAS opcode. PAYGAS simultaneously serves two purposes:

  1. Paying for gas.
  2. Serving as a logical demarcator of the "verification parts of a transaction" and the "execution parts of a transaction".

We add two variables to the execution context (ie. in a similar position as the selfdestructs list): PAYGAS_CALLED and PAYGAS_GASPRICE, initialized to False and 0 respectively. The PAYGAS opcode takes a single stack argument, gasprice. Its logic is as follows:

  1. Check that PAYGAS_CALLED = False; if not, then simply pop the top element off the stack, and stop and push 0 onto the stack.
  2. Subtract gasprice * tx.start_gas from the callee account's balance. If not enough funds, stop and push 0 onto the stack
  3. If steps (1) and (2) passed, set PAYGAS_CALLED = True, and PAYGAS_GASPRICE = gasprice, and push 1 onto the stack.

Account strategy

The owner of an account will generally want to have account code that looks something like:

  1. Check the signature
  2. Call PAYGAS
  3. Call the actual destination account

Where the transaction data encodes the signature as well as the destination, value, gasprice and data of the intended message that is to be sent from an account.

It will be possible to send ETH to not-yet-created accounts by simply computing their address from the hash of the init code, and initializing the code for such an account would be done at the same time as sending the first transaction.

Miner strategy

We note that any transaction whose execution reaches the PAYGAS opcode is guaranteed to pay for gas, even if the execution exits with an exception after that point, and any transaction that exits with an exception before reaching PAYGAS will not be includeable into a block.

Miners can use the following strategy to accept transactions. Every miner can set a private value, CHECK_LIMIT, eg. to 200000. When a miner or network node sees a transaction, they execute it on top of the current head state for a maximum of CHECK_LIMIT gas (the 200-per-byte cost of creating a new contract does NOT count toward the limit). If the transaction execution hits PAYGAS before this limit, then the miner or network node accepts the transaction and acts as though the gasprice called with PAYGAS is the transaction's gasprice; if it does not, then the miner or network node rejects the transaction.

When a miner actually includes transactions in a block, PAYGAS may pay a different gasprice than when the miner first saw it, for example if the argument to PAYGAS depends on state; in this case, throw out the transaction if the gasprice is lower than it was during the first scan.

Setting CHECK_LIMIT is a simple tradeoff: if CHECK_LIMIT is higher, miners can accept transactions from accounts that make more complex checks before calling PAYGAS (eg. Lamport sigs, threshold sigs), but setting CHECK_LIMIT higher also makes miners more vulnerable to DoS attacks. Miners may want to start off with high CHECK_LIMIT but dynamically adjust it downwards if they detect a DoS attack to keep CPU usage below some threshold.

Specification version 1.1

  • Add a PAYGAS_CALLER parameter; if/when PAYGAS is called successfully, set the value to equal the address of the current executing account (ie. msg.to)
  • Refund the PAYGAS_CALLER instead of the transaction target

What does this abstract and what does it not abstract?

  • It will be possible for accounts to use whatever signature scheme they want
  • Nonces will remain mandatory; this is a compromise to preserve the "each transaction can only appear once in the chain" invariant
  • It will be possible for verification conditions to be more complex, saving gas in a variety of scenarios. One particularly interesting usecase is capped ICOs. For example, if there are 10000 transactions going into an ICO but the cap can only accept 2000, then currently all 10000 would get included with the last 8000 being no-ops, but with this scheme one could make a setup where the 8000 transactions that fail all cannot be included in the chain
  • It will continue to be difficult to use ERC20s to pay for gas

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions