如何移植EVM

简介

本文讨论如何在非EVM链上,移植EVM支持ETH交易。

  • 代码:github.com:ethereum/go-ethereum.git
  • 版本:1.14.6

总结

移植EVM步骤概述:

  1. 准备自己的statedb。非EVM链账户模型往往和以太坊不一致,EVM虚拟机在执行过程中,会访问statedb来更改账户状态,因此需要准备自己的statedb,搭建虚拟机和本地账户模型之间交互的桥梁。
  2. 准备运行环境Context以及Config。作为沙盒调用虚拟机的参数。
  3. 引用evm虚拟机github.com/ethereum/go-ethereum/core/vm。vm包是调用虚拟机的入口,入口为evm.Create和evm.Call。
  4. 实现自己的TransitionDb逻辑,主要是处理gas费的扣除,返回,以及矿工奖励。

ETH交易执行流程分析

1. EVM虚拟机工作方式

我们先了解下EVM的工作原理,这篇文章分析得非常详细易懂(https://learnblockchain.cn/2019/04/09/easy-evm)。

其中架构图如下:

核心思想为:

  • EVM作为沙盒运行,每执行一笔交易,就会创建一个独立的EVM。交易会被转化为Message格式,传递给EVM执行。
  • EVM运行前,需要准备环境,也就是一些参数,叫做Context,Config,用于访问链,block的信息。
  • EVM运行过程中,会访问StateDB,修改StateObject(也就是账户)状态,整个过程叫做StateTransition。注意修改后的状态保存在缓存中,并未真正落盘。通过访问StateDB,能够获取修改后的状态。
  • EVM内部会调用Interpreter执行OpCode。
  • EVM运行完成后,并未真实改变链上状态。而是将状态修改保存在StateDB缓存中,EVM返回Gas消耗,以及执行结果。
  • 调用者根据EVM的执行结果,执行最终Gas扣费,状态落盘上链逻辑。或者什么也不做,放弃本次状态更改,比如当前是eth_call调用时。

2. 交易执行入口

交易执行入口是ApplyTransaction。EVM虚拟机作为沙盒运行,需要配置具体的运行环境,主要组件为:

  • blockContext,EVM用于获取block信息。
  • txContext,获取交易信息。
  • statedb,用于保存状态变更。
  • config,链上配置,包括chainconfig以及rules灯。

代码如下:

// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
    //1. 把Transaction转化Message为类型
    msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
    if err != nil {
       return nil, err    
       }
    // Create a new context to be used in the EVM environment 
    //2. 创建BlockContext   
    blockContext := NewEVMBlockContext(header, bc, author)
    //3. 创建TxContext
    txContext := NewEVMTxContext(msg)
    vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
    //调用ApplyTransactionWithEVM
    return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
}

ApplyTransactionWithEVM调用ApplyMessage,最终执行TransitionDb。

3. 交易执行核心逻辑

交易执行核心逻辑在TransitionDb中。其中核心流程为:

  1. 执行金额检查,以及扣除最大gas费,gasLimit * gasPrice。
  2. 计算固定Gas。
  3. 调用虚拟机执行交易,其中合约的创建调用evm.Create函数。转账和合约调用调用evm.Call。虚拟机执行完交易后,会统计出剩下的Gas。
  4. 返回用户剩下的Gas, 包括Refund机制的退费,以及执行剩下的Gas。
  5. 给矿工付费Tip。
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
    // First check this message satisfies all consensus rules before
    // applying the message. The rules include these clauses
    //
    // 1. the nonce of the message caller is correct
    // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
    // 3. the amount of gas required is available in the block
    // 4. the purchased gas is enough to cover intrinsic usage
    // 5. there is no overflow when calculating intrinsic gas
    // 6. caller has enough balance to cover asset transfer for **topmost** call

    // Check clauses 1-3, buy gas if everything is correct
        //执行金额检查,以及扣除最大gas费,gasLimit * gasPrice
    if err := st.preCheck(); err != nil {
        return nil, err
    }

    var (
        msg              = st.msg
        sender           = vm.AccountRef(msg.From)
        rules            = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
        contractCreation = msg.To == nil
    )

    // Check clauses 4-5, subtract intrinsic gas if everything is correct
        // 计算固定Gas
    gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
    if err != nil {
        return nil, err
    }
    if st.gasRemaining < gas {
        return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
    }
    if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
        t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
    }
    st.gasRemaining -= gas

    if rules.IsEIP4762 {
        st.evm.AccessEvents.AddTxOrigin(msg.From)

        if targetAddr := msg.To; targetAddr != nil {
            st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
        }
    }

    // Check clause 6
    value, overflow := uint256.FromBig(msg.Value)
    if overflow {
        return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
    }
    if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
        return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
    }

    // Check whether the init code size has been exceeded.
    if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
        return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
    }

    // Execute the preparatory steps for state transition which includes:
    // - prepare accessList(post-berlin)
    // - reset transient storage(eip 1153)
    st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)

    var (
        ret   []byte
        vmerr error // vm errors do not effect consensus and are therefore not assigned to err
    )
        //调用虚拟机执行交易,其中合约的创建调用evm.Create函数。转账和合约调用调用evm.Call
        //虚拟机执行完交易后,会统计出剩下的Gas
    if contractCreation {
        ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
    } else {
        // Increment the nonce for the next transaction
        st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
        ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
    }

        //返回用户剩下的Gas, 包括Refund机制的退费,以及执行剩下的Gas
    var gasRefund uint64
    if !rules.IsLondon {
        // Before EIP-3529: refunds were capped to gasUsed / 2
        gasRefund = st.refundGas(params.RefundQuotient)
    } else {
        // After EIP-3529: refunds are capped to gasUsed / 5
        gasRefund = st.refundGas(params.RefundQuotientEIP3529)
    }
    effectiveTip := msg.GasPrice
    if rules.IsLondon {
        effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
    }
    effectiveTipU256, _ := uint256.FromBig(effectiveTip)

    if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
        // Skip fee payment when NoBaseFee is set and the fee fields
        // are 0. This avoids a negative effectiveTip being applied to
        // the coinbase when simulating calls.
    } else {
        fee := new(uint256.Int).SetUint64(st.gasUsed())
        fee.Mul(fee, effectiveTipU256)
                //给矿工账户付费Tip
        st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)

        // add the coinbase to the witness iff the fee is greater than 0
        if rules.IsEIP4762 && fee.Sign() != 0 {
            st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
        }
    }

        //返回执行结果
    return &ExecutionResult{
        UsedGas:     st.gasUsed(),
        RefundedGas: gasRefund,
        Err:         vmerr,
        ReturnData:  ret,
    }, nil
}

4. 虚拟机执行入口

从上面交易执行逻辑得知,虚拟机(github.com/ethereum/go-ethereum/core/vm)的真正执行入口函数为:

  • evm.Create,合约创建。
  • evm.Call,转账或合约调用。
if contractCreation {
    //Create函数是创建合约
    ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
} else {
    // Increment the nonce for the next transaction    
    st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
    ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}

实现EVM移植

经过上面分析,EVM的移植就很清楚了。对照EVM工作方式,移植者需要做到以下工作:

  • 准备环境,构建Context,Config。
  • 把交易转化为Message格式,创建一个新的EVM。
  • 准备StateDB。
  • 调用者根据EVM的执行结果,执行最终Gas扣费,状态落盘上链逻辑。或者什么也不做,放弃本次状态更改,比如当前是eth_call调用时。(可参照go-ethereum的TransitionDb函数)

实例

具体实例可参照evmos(https://github.com/evmos/evmos)。在cosmos链上移植了EVM虚拟机。

Ref



《 “如何移植EVM” 》 有 4 条评论

  1. can i buy cytotec prices zhevitra hace dao tomar naproxeno y alcohol Cohen personally oversees about 4 billion, along with a small group of traders, in a portfolio called the Cohen account, which represents a good chunk of the estimated 6 billion he has invested with his 21 year old hedge fund, Reuters has previously reported

  2. Moon RJ, Curtis EM, Cooper C, Davies JH, Harvey NC Priligy Pulves oral suspension is strawberry bubble gum flavor

  3. priligy where to buy 9 mmol L within 21 days under appropriate anti hyperglycemic treatment, interrupt dosing until improvement to Grade 1, then resume at 50 mg and follow FG value specific recommendations

  4. pqf64cd8e32f5ac7553c150bd05d6f2252bb73f68dpq 的头像
    pqf64cd8e32f5ac7553c150bd05d6f2252bb73f68dpq

    pq118a9989815489c24b81b160782015890ed2085epq

回复 agodelo 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About Me

一位程序员,会弹吉他,喜欢读诗。
有一颗感恩的心,一位美丽的妻子,两个可爱的女儿
mail: geraldlee0825@gmail.com
github: https://github.com/lisuxiaoqi
medium: https://medium.com/@geraldlee0825