Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Integrate EVM circuit into TaikoSuperCircuit #112

Open
Brechtpd opened this issue Jun 20, 2023 · 10 comments · Fixed by #136
Open

Integrate EVM circuit into TaikoSuperCircuit #112

Brechtpd opened this issue Jun 20, 2023 · 10 comments · Fixed by #136

Comments

@Brechtpd
Copy link

No description provided.

@johntaiko
Copy link

johntaiko commented Jul 19, 2023

EIP1559 in taiko's zkevm-circuits spec

Motivation

EIP-1559 is a new transaction type in Ethereum, it is a big change in Ethereum, and it is a big change in zkevm too. We need to support it in zkevm-circuits. This document is about how to support EIP-1559 in zkevm-circuits for Taiko Layer2.

We will divide the work into several stages, the first stage is about supporting EIP-1559 in evm-circuit and associated circuits, tables and etc. Like: anchor-circuit, tx-circuit, block-circuit, tx-table, block-table, etc. The remaining parts will be done in the next series of stages.

We need to agree on some trade-offs. The first trade-off is that we only support dynamic fee transaction in Taiko Layer2. The second trade-off is that the implementation is not exactly the same as the EIP-1559, so, it is not Type-1 compatible. And there will be some differences with the implementation in tiako-geth, like: the base_fee in Anchor transaction must be zero in circuit, but it is not zero in taiko-geth. So, we can not add extra logics for Anchor transaction in evm-circuit, it's very important, because the codebase of evm-circuit is complex enough, we should not add more logics in it. I will explain the details in the following sections.

Let's jump into the details.

The goals and non-goals

At the beginning, we need to define the goals and non-goals of this work.

Goals:

  • Support EIP-1559 in evm-circuit, tx-circuit, anchor-circuit, maybe block-circuit, and finally in zkevm-circuits
  • Support dynamic fee transaction
  • Support Anchor transaction

Non-goals:

  • Compatible with legacy transaction
  • Compatible with access list transaction

The changes between the components

evm-circuit-1

  • EIP-1559 and dynamic fee transaction need more fields.
    • Related fields need to be update: sign_hash, block_hash, etc.
  • The base_fee is sent to the treasury account.
  • The gas_tip is sent to the miner.

Changes in evm-circuit

1. BeginTx step

Now I can explain why we need to use zero base_fee in Anchor transaction. Because the Anchor transaction is a special case of BeginTx step, actually, the GOLDEN_TOUCH account doesn't pay any gas fee for Anchor transaction, but now in taiko-geth, we have a special logic to skip the check of balance, gas_fee_cap, gas_tip_cap, and base_fee. But it is not friendly to the evm-circuit, we need more constrains for Anchor transaction.

But if we set Anchor's base_fee to zero, then we don't need to add more constrains any more. So, we have two kinds of base_fee(zero or non-zero) in transactions, we need some extra works in tx-circuit, and let it support two kinds of base_fee.

  • Only Anchor transaction can have zero base_fee
  • Other transactions must have non-zero base_fee, which is checked in Anchor contract call.

Inspired by @smtmfft

2. Opcode steps

The EIP-1559 doesn't change the opcodes, and also doesn't add more opcodes, so i think we don't have any works in this steps. (Maybe need double check in the future)

Update:
Some opcode related to access_list need to be updated:

  • CALL
  • CALLCODE
  • DELEGATECALL
  • STATICCALL
  • CREATE
  • CREATE2
  • SELFDESTRUCT
  • SSTORE
  • BALANCE
  • SLOAD

3. EndTx step

This step is the most important step in taiko's EIP-1559, because we need to send the base_fee to a treasury account, instead of burning it.

We don't need to add more logics for Anchor transaction, because we have set the base_fee and gas_tip to zero in BeginTx step, so, we can just ignore the base_fee and gas_tip in EndTx step.

Demo code:

fn gen_end_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Error> {
    // ... balabala

    // add tip to coinbase(miner)
    let effective_tip = state.tx.tx.gas_price - state.block.base_fee;
    let (found, coinbase_account) = state.sdb.get_account(&state.block.coinbase);
    if !found {
        return Err(Error::AccountNotFound(state.block.coinbase));
    }
    let coinbase_balance_prev = coinbase_account.balance;
    let coinbase_balance =
        coinbase_balance_prev + effective_tip * (state.tx.gas() - exec_step.gas_left.0);
    state.account_write(
        &mut exec_step,
        state.block.coinbase,
        AccountField::Balance,
        coinbase_balance,
        coinbase_balance_prev,
    )?;

    // add base_fee to treasury
    let (found, treasury_account) = state.sdb.get_account(&treasury_account);
    if !found {
        return Err(Error::AccountNotFound(treasury_account));
    }
    let treasury_balance_prev = treasury_account.balance;
    let treasury_balance =
        treasury_balance_prev + effective_tip * (state.tx.gas() - exec_step.gas_left.0);
    state.account_write(
        &mut exec_step,
        treasury_account,
        AccountField::Balance,
        treasury_balance,
        treasury_balance_prev,
    )?;

    Ok(exec_step)
}

3.1. The treasury account

This is an account from our L1 contract, it is a special account, all base_fee will be sent to this account. So there is no deflation on taiko's layer2.

4. EndBlock step

Now nothing has been done, but some works in the future:

  • Calc the block hash after executing all transactions(maybe has invalid transactions)

Changes in tx-circuit and tx-table

Add extra three fields in the tx-table for fixing the dynamic fee transaction:

  • Add base_fee field
  • Add gas_tip field
  • Add access_list field

For Anchor transaction, tx-circuit must constrain the base_fee to zero, and other transactions's base_fee must be non-zero.

Another change is that the payload for signature hash adds more 3 fields: max_priority_fee_per_gas(gas_tip), max_fee_per_gas(gas_price), and access_list.

Changes in anchor-circuit

Previous, in anchor-circuit we only check some constant fields, like: gas_price, from, to, etc. But now we need to check the base_fee.

cb.require_equal(
    "base_fee = 0",
    base_fee,
    0.expr(),
);

Changes in block-circuit and block-table

For EIP-1559, the block hash has one more field: base_fee_per_gas, so, we need care about the block hash calculation.

Changes in rlp-circuit

For EIP-1559, the rlp-circuit need to update to handle the dynamic fee transaction(type-2).

The tasks

  • The evm-circuit
    • EndTx
    • Opcodes: access_list related opcode(dynamic gas opcode)
    • TODO: EndBlock
  • The tx-circuit and tx-table
    • Add base_fee , gas_tip and access_list fields
    • Update signature hash
    • The different base_fee between Anchor and other transactions
  • The anchor-circuit
    • Check the base_fee field
  • The block-circuit and block-table
    • Add base_fee_per_gas field
    • Update block hash

@Brechtpd
Copy link
Author

Looks good! Let's certainly do the EVM changes first by just adding the necessary data to the lookup tables, and then worry about updating the other circuits to verify this additional data.

Some small comments/questions:

We don't need to add more logics for Anchor transaction, because we have set the base_fee and gas_tip to zero in BeginTx step, so, we can just ignore the base_fee and gas_tip in EndTx step.

The fee calculation uses state.block.base_fee and not tx.basefee so it think we still need some special logic here for the anchor transaction because the basefee in the block table will be the original value. So something simple like:

let basefee = if tx_id == 0 { 0 } else { block.basefee };

I think would work. I think the only place tx.basefee is used is just to ensure that tx.basefee >= block.basefee.

But if we set Anchor's base_fee to zero, then we don't need to add more constrains any more.

For Anchor transaction, tx-circuit must constrain the base_fee to zero, and other transactions's base_fee must be non-zero.

I don't think we need to do this, I think just setting it to some deterministic value (like block.base_fee) is sufficient. But I could be missing something.

The EIP-1559 doesn't change the opcodes, and also doesn't add more opcodes, so i think we don't have any works in this steps. (Maybe need double check in the future)

The basefee opcode was added, but it's already supported.

For EIP-1559, the block hash has one more field: base_fee_per_gas, so, we need care about the block hash calculation.

Will be done by @ggkitsas:

const BASE_FEE_SIZE: usize = WORD_SIZE;
But I guess easy to just add it to the block table so you don't have to wait until this PR is merged in if this would be a blocker.

@smtmfft
Copy link
Collaborator

smtmfft commented Jul 19, 2023

Missing access list fee change, Opcode fee varies according to access list settings. I am thinking if we can claim that one special L3 forbids access list, so that we can make 2 phases circuit dev for fast testnet deployment.

@Brechtpd
Copy link
Author

Ah yes good point. Let's make the node ignore the access list which hopefully isn't that hard to do?

Forbidding seems harder because people can just change the block creation code and still get these transactions included in a block and then these transactions would have to be marked as invalid which we would then also have to support on the circuits side.

@johntaiko
Copy link

Missing access list fee change, Opcode fee varies according to access list settings. I am thinking if we can claim that one special L3 forbids access list, so that we can make 2 phases circuit dev for fast testnet deployment.

Updated

@smtmfft
Copy link
Collaborator

smtmfft commented Jul 19, 2023

So far we did not see any access list in our testnet, so I think it should be OK. But even not possible, still we can do that in our devnet, so all 1559 related circuit dev could be in 2 phases (1. w/o access list, 2. w/ access list) which means a fast testnet deployment, so that we can observe the circuit's behavior in real env more frequently.

@johntaiko
Copy link

johntaiko commented Jul 19, 2023

The fee calculation uses state.block.base_fee and not tx.basefee so it think we still need some special logic here for the anchor transaction because the basefee in the block table will be the original value. So something simple like:

Or, use tx.basefee instead of state.block.base_fee.

I don't think we need to do this, I think just setting it to some deterministic value (like block.base_fee) is sufficient. But I could be missing something.

Anchor transaction with zero basefee and gastip will eventually have zero gasfee. So at BeginTx and EndTx we don't need to treat the anchor transaction specially, before we have to ignore deductions. but GOLDEN_TOUCH has no balance. Everything minus zero gasfee*gaslimit equals zero.

@smtmfft
Copy link
Collaborator

smtmfft commented Jul 19, 2023

Also summarized my thinkings on 1559 supporting circuits: https://www.notion.so/taikoxyz/1559-Development-Thinkings-a84ac894fb984b71ad847913ae031378

@Brechtpd
Copy link
Author

The fee calculation uses state.block.base_fee and not tx.basefee so it think we still need some special logic here for the anchor transaction because the basefee in the block table will be the original value. So something simple like:

Or, use tx.basefee instead of state.block.base_fee.

But tx.basefee can be larger than state.block.base_fee, which will increase the fee payment for the transaction which changes how EIP-1559 works. The tx.basefee is the maximum acceptable basefee accepted by the transaction, not the one that should always be used for the fee payment.

I don't think we need to do this, I think just setting it to some deterministic value (like block.base_fee) is sufficient. But I could be missing something.

Anchor transaction with zero basefee and gastip will eventually have zero gasfee. So at BeginTx and EndTx we don't need to treat the anchor transaction specially, before we have to ignore deductions. but GOLDEN_TOUCH has no balance. Everything minus zero gasfee*gaslimit equals zero.

It will still be a bit special because we'll still have to ignore the tx.basefee >= block.basefee check if we follow your approach.

Reminds me that there is an additional invalid tx now: if tx.basefee < block.basefee the tx should be marked as invalid.

@johntaiko
Copy link

It will still be a bit special because we'll still have to ignore the tx.basefee >= block.basefee check if we follow your approach.

Reminds me that there is an additional invalid tx now: if tx.basefee < block.basefee the tx should be marked as invalid.

Ok, for both of these two reasons, the above two approaches have the same workload.

So, it's better for us to use block.basefee, and add if else at the BeginTx and EndTx for Anchor transaction.

@johntaiko johntaiko linked a pull request Aug 11, 2023 that will close this issue
15 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Status: 🏗 In progress
Development

Successfully merging a pull request may close this issue.

3 participants