以太坊虚拟机工作原理深入刨析(中)
Al1ex 发表于 四川 区块链安全 616浏览 · 2024-02-04 05:49

文章前言

本篇文章是《以太坊虚拟机工作原理深入刨析》的续篇

源码分析

数据结构

下面的代码定义了与以太坊合约相关的结构体和接口,ContractRef是一个合约引用的接口,它定义了一个Address()方法用于获取合约的地址,AccountRef是实现了ContractRef接口的结构体,在EVM初始化期间用于获取地址,Address()方法将AccountRef转换为common.Address类型的地址,这里的Contract表示状态数据库中的一个以太坊合约,它包含合约的代码、调用参数等信息,Contract实现了ContractRef接口,而CallerAddress是初始化该合约的调用者的地址,当使用"call"方法进行委托调用时,该值需要初始化为调用者的调用者的地址

// filedir:go-ethereum-1.10.2\core\vm\contract.go L25
// ContractRef is a reference to the contract's backing object
type ContractRef interface {
    Address() common.Address
}

// AccountRef implements ContractRef.
//
// Account references are used during EVM initialisation and
// it's primary use is to fetch addresses. Removing this object
// proves difficult because of the cached jump destinations which
// are fetched from the parent contract (i.e. the caller), which
// is a ContractRef.
type AccountRef common.Address

// Address casts AccountRef to a Address
func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }

// Contract represents an ethereum contract in the state database. It contains
// the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
    // CallerAddress is the result of the caller which initialised this
    // contract. However when the "call method" is delegated this value
    // needs to be initialised to that of the caller's caller.
    CallerAddress common.Address
    caller        ContractRef
    self          ContractRef

    jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
    analysis  bitvec                 // Locally cached result of JUMPDEST analysis

    Code     []byte
    CodeHash common.Hash
    CodeAddr *common.Address
    Input    []byte

    Gas   uint64
    value *big.Int
}

下面的EVM数据结构则主要提供了当前区块链相关信息、StateDb访问、调用栈信息、当前区块链配置、参数信息、VM配置信息、解释器信息等:

// filedir:go-ethereum-1.10.2\core\vm\evm.go    L105
// EVM is the Ethereum Virtual Machine base object and provides
// the necessary tools to run a contract on the given state with
// the provided context. It should be noted that any error
// generated through any of the calls should be considered a
// revert-state-and-consume-all-gas operation, no checks on
// specific errors should ever be performed. The interpreter makes
// sure that any errors generated are to be considered faulty code.
//
// The EVM should never be reused and is not thread safe.
type EVM struct {
    // Context provides auxiliary blockchain related information
    Context BlockContext
    TxContext
    // StateDB gives access to the underlying state
    StateDB StateDB
    // Depth is the current call stack
    depth int

    // chainConfig contains information about the current chain
    chainConfig *params.ChainConfig
    // chain rules contains the chain rules for the current epoch
    chainRules params.Rules
    // virtual machine configuration options used to initialise the
    // evm.
    vmConfig Config
    // global (to this context) ethereum virtual machine
    // used throughout the execution of the tx.
    interpreters []Interpreter
    interpreter  Interpreter
    // abort is used to abort the EVM calling operations
    // NOTE: must be set atomically
    abort int32
    // callGasTemp holds the gas available for the current call. This is needed because the
    // available gas is calculated in gasCall* according to the 63/64 rule and later
    // applied in opCall*.
    callGasTemp uint64
}

BlockContext提供了EVM辅助信息,一旦提供不应该再次进行修改,这里的CanTransfer用于检查账户是否有足够的ether进行转账,transfer用于从一个账户给另一个账户转账,GetHash用于返回入参n对应的Hash值:

// BlockContext provides the EVM with auxiliary information. Once provided
// it shouldn't be modified.
type BlockContext struct {
    // CanTransfer returns whether the account contains
    // sufficient ether to transfer the value
    CanTransfer CanTransferFunc
    // Transfer transfers ether from one account to the other
    Transfer TransferFunc
    // GetHash returns the hash corresponding to n
    GetHash GetHashFunc

    // Block information
    Coinbase    common.Address // Provides information for COINBASE
    GasLimit    uint64         // Provides information for GASLIMIT
    BlockNumber *big.Int       // Provides information for NUMBER
    Time        *big.Int       // Provides information for TIME
    Difficulty  *big.Int       // Provides information for DIFFICULTY
}

TxContext用于提供EVM的交易信息,所有字段在交易过程中可以被修改:

// TxContext provides the EVM with information about a transaction.
// All fields can change between transactions.
type TxContext struct {
    // Message information
    Origin   common.Address // Provides information for ORIGIN
    GasPrice *big.Int       // Provides information for GASPRICE
}

EVM对象

NewEVM用于返回一个EVM对象,期间会根据链配置和虚拟机配置的不同选择相应的解释器并将其添加到EVM的解释器列表中,最后返回创建的EVM实例,如果是EWASM网络则抛出一个错误,表示暂时不支持EWASM解释器

// filedir:go-ethereum-1.10.2\core\vm\evm.go L143
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
    evm := &EVM{
        Context:      blockCtx,
        TxContext:    txCtx,
        StateDB:      statedb,
        vmConfig:     vmConfig,
        chainConfig:  chainConfig,
        chainRules:   chainConfig.Rules(blockCtx.BlockNumber),
        interpreters: make([]Interpreter, 0, 1),
    }

    if chainConfig.IsEWASM(blockCtx.BlockNumber) {
        // to be implemented by EVM-C and Wagon PRs.
        // if vmConfig.EWASMInterpreter != "" {
        //  extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
        //  path := extIntOpts[0]
        //  options := []string{}
        //  if len(extIntOpts) > 1 {
        //    options = extIntOpts[1..]
        //  }
        //  evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
        // } else {
        //  evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
        // }
        panic("No supported ewasm interpreter yet.")
    }

    // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
    // as we always want to have the built-in EVM as the failover option.
    evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
    evm.interpreter = evm.interpreters[0]

    return evm
}

合约创建

通过上面的交易剖析部分的分析我们了解到当交易的接受地址为空地址时则会调用evm.Create方法来创建合约,这里我们对其进行简易跟踪分析:

// filedir:go-ethereum-1.10.2\core\vm\evm.go L500
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
    contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
    return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
}

从上述代码中可以看到这里首先调用crypto.CreateAddress函数根据调用者地址以及当前Nonce值来生成一个合约地址,之后再调用evm.create来创建合约,在这里首先会检查合约创建时的递归调用次数是否大于最大递归调用次数,之后检查合约创建者是否有足够的ether,之后获取并更新Nonce值,之后检查当前合约的地址是否已经存在,随机通过evm.StateDB.CreateAccount(address)创建合约账户,并将交易中的以太币数值(value)转入到新建的账户里:

// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
    // Depth check execution. Fail if we're trying to execute above the
    // limit.
    if evm.depth > int(params.CallCreateDepth) {
        return nil, common.Address{}, gas, ErrDepth
    }
    if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, common.Address{}, gas, ErrInsufficientBalance
    }
    nonce := evm.StateDB.GetNonce(caller.Address())
    evm.StateDB.SetNonce(caller.Address(), nonce+1)
    // We add this to the access list _before_ taking a snapshot. Even if the creation fails,
    // the access-list change should not be rolled back
    if evm.chainRules.IsBerlin {
        evm.StateDB.AddAddressToAccessList(address)
    }
    // Ensure there's no existing contract already at the designated address
    contractHash := evm.StateDB.GetCodeHash(address)
    if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
        return nil, common.Address{}, 0, ErrContractAddressCollision
    }
    // Create a new account on the state
    snapshot := evm.StateDB.Snapshot()
    evm.StateDB.CreateAccount(address)
    if evm.chainRules.IsEIP158 {
        evm.StateDB.SetNonce(address, 1)
    }
    evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)

    // Initialise a new contract and set the code that is to be used by the EVM.
    // The contract is a scoped environment for this execution context only.
    contract := NewContract(caller, AccountRef(address), value, gas)
    contract.SetCodeOptionalHash(&address, codeAndHash)

    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, address, gas, nil
    }

    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
    }
    start := time.Now()

    ret, err := run(evm, contract, nil, false)

    // check whether the max code size has been exceeded
    maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize
    // if the contract creation ran successfully and no errors were returned
    // calculate the gas required to store the code. If the code could not
    // be stored due to not enough gas set an error and let it be handled
    // by the error checking condition below.
    if err == nil && !maxCodeSizeExceeded {
        createDataGas := uint64(len(ret)) * params.CreateDataGas
        if contract.UseGas(createDataGas) {
            evm.StateDB.SetCode(address, ret)
        } else {
            err = ErrCodeStoreOutOfGas
        }
    }

    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != ErrExecutionReverted {
            contract.UseGas(contract.Gas)
        }
    }
    // Assign err if contract code size exceeds the max while the err is still empty.
    if maxCodeSizeExceeded && err == nil {
        err = ErrMaxCodeSizeExceeded
    }
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
    }
    return ret, address, contract.Gas, err

}

之后调用NewContract来初始化一个新的合约执行环境对象,之后检查当前以太坊虚拟机的配置是否被配置为不可递归模式,如果EVM不可递归且当前合约正在递归过程中则直接返回,之后检查是否开启模式,以及当前的递归深度是否为0,如果是则跟踪执行流程,之后调用run函数来执行合约的代码:

// Initialise a new contract and set the code that is to be used by the EVM.
    // The contract is a scoped environment for this execution context only.
    contract := NewContract(caller, AccountRef(address), value, gas)
    contract.SetCodeOptionalHash(&address, codeAndHash)

    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, address, gas, nil
    }

    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
    }
    start := time.Now()

    ret, err := run(evm, contract, nil, false)

run通过一个for循环从当前EVM对象中选择一个运行的解释器来运行当前合约并返回其结果:

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
    for _, interpreter := range evm.interpreters {
        if interpreter.CanRun(contract.Code) {
            if evm.interpreter != interpreter {
                // Ensure that the interpreter pointer is set back
                // to its current value upon return.
                defer func(i Interpreter) {
                    evm.interpreter = i
                }(evm.interpreter)
                evm.interpreter = interpreter
            }
            return interpreter.Run(contract, input, readOnly)
        }
    }
    return nil, errors.New("no compatible interpreter")
}

Run函数如下所示,这也是以太坊虚拟机(EVM)的解释器的主要运行循环,它根据合约的代码逐条执行操作码并根据操作码调用相应的操作函数,在执行过程中会验证栈的状态、管理Gas消耗、调整内存大小并根据操作码的特性进行相应的处理,最后返回执行结果和可能的错误

// filedir: go-ethereum-1.10.2\core\vm\interpreter.go   L134
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {

    // Increment the call depth which is restricted to 1024
    in.evm.depth++
    defer func() { in.evm.depth-- }()

    // Make sure the readOnly is only set if we aren't in readOnly yet.
    // This makes also sure that the readOnly flag isn't removed for child calls.
    if readOnly && !in.readOnly {
        in.readOnly = true
        defer func() { in.readOnly = false }()
    }

    // Reset the previous call's return data. It's unimportant to preserve the old buffer
    // as every returning call will return new data anyway.
    in.returnData = nil

    // Don't bother with the execution if there's no code.
    if len(contract.Code) == 0 {
        return nil, nil
    }

    var (
        op          OpCode        // current opcode
        mem         = NewMemory() // bound memory
        stack       = newstack()  // local stack
        callContext = &ScopeContext{
            Memory:   mem,
            Stack:    stack,
            Contract: contract,
        }
        // For optimisation reason we're using uint64 as the program counter.
        // It's theoretically possible to go above 2^64. The YP defines the PC
        // to be uint256. Practically much less so feasible.
        pc   = uint64(0) // program counter
        cost uint64
        // copies used by tracer
        pcCopy  uint64 // needed for the deferred Tracer
        gasCopy uint64 // for Tracer to log gas remaining before execution
        logged  bool   // deferred Tracer should ignore already logged steps
        res     []byte // result of the opcode execution function
    )
    // Don't move this deferrred function, it's placed before the capturestate-deferred method,
    // so that it get's executed _after_: the capturestate needs the stacks before
    // they are returned to the pools
    defer func() {
        returnStack(stack)
    }()
    contract.Input = input

    if in.cfg.Debug {
        defer func() {
            if err != nil {
                if !logged {
                    in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
                } else {
                    in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
                }
            }
        }()
    }
    // The Interpreter main run loop (contextual). This loop runs until either an
    // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
    // the execution of one of the operations or until the done flag is set by the
    // parent context.
    steps := 0
    for {
        steps++
        if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 {
            break
        }
        if in.cfg.Debug {
            // Capture pre-execution values for tracing.
            logged, pcCopy, gasCopy = false, pc, contract.Gas
        }

        // Get the operation from the jump table and validate the stack to ensure there are
        // enough stack items available to perform the operation.
        op = contract.GetOp(pc)
        operation := in.cfg.JumpTable[op]
        if operation == nil {
            return nil, &ErrInvalidOpCode{opcode: op}
        }
        // Validate stack
        if sLen := stack.len(); sLen < operation.minStack {
            return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
        } else if sLen > operation.maxStack {
            return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
        }
        // If the operation is valid, enforce and write restrictions
        if in.readOnly && in.evm.chainRules.IsByzantium {
            // If the interpreter is operating in readonly mode, make sure no
            // state-modifying operation is performed. The 3rd stack item
            // for a call operation is the value. Transferring value from one
            // account to the others means the state is modified and should also
            // return with an error.
            if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
                return nil, ErrWriteProtection
            }
        }
        // Static portion of gas
        cost = operation.constantGas // For tracing
        if !contract.UseGas(operation.constantGas) {
            return nil, ErrOutOfGas
        }

        var memorySize uint64
        // calculate the new memory size and expand the memory to fit
        // the operation
        // Memory check needs to be done prior to evaluating the dynamic gas portion,
        // to detect calculation overflows
        if operation.memorySize != nil {
            memSize, overflow := operation.memorySize(stack)
            if overflow {
                return nil, ErrGasUintOverflow
            }
            // memory is expanded in words of 32 bytes. Gas
            // is also calculated in words.
            if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
                return nil, ErrGasUintOverflow
            }
        }
        // Dynamic portion of gas
        // consume the gas and return an error if not enough gas is available.
        // cost is explicitly set so that the capture state defer method can get the proper cost
        if operation.dynamicGas != nil {
            var dynamicCost uint64
            dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
            cost += dynamicCost // total cost, for debug tracing
            if err != nil || !contract.UseGas(dynamicCost) {
                return nil, ErrOutOfGas
            }
        }
        if memorySize > 0 {
            mem.Resize(memorySize)
        }

        if in.cfg.Debug {
            in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
            logged = true
        }

        // execute the operation
        res, err = operation.execute(&pc, in, callContext)
        // if the operation clears the return data (e.g. it has returning data)
        // set the last return to the result of the operation.
        if operation.returns {
            in.returnData = common.CopyBytes(res)
        }

        switch {
        case err != nil:
            return nil, err
        case operation.reverts:
            return res, ErrExecutionReverted
        case operation.halts:
            return res, nil
        case !operation.jumps:
            pc++
        }
    }
    return nil, nil
}

之后检查合约代码长度是否超过最大的限制,如果未超过,则调用StateDB.SetCode将合约代码存储到以太坊状态数据库的合约账户中,最后返回合约字节码以及合约的地址以及合约所耗费的gas费用:

// check whether the max code size has been exceeded
    maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize
    // if the contract creation ran successfully and no errors were returned
    // calculate the gas required to store the code. If the code could not
    // be stored due to not enough gas set an error and let it be handled
    // by the error checking condition below.
    if err == nil && !maxCodeSizeExceeded {
        createDataGas := uint64(len(ret)) * params.CreateDataGas
        if contract.UseGas(createDataGas) {
            evm.StateDB.SetCode(address, ret)
        } else {
            err = ErrCodeStoreOutOfGas
        }
    }

    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != ErrExecutionReverted {
            contract.UseGas(contract.Gas)
        }
    }
    // Assign err if contract code size exceeds the max while the err is still empty.
    if maxCodeSizeExceeded && err == nil {
        err = ErrMaxCodeSizeExceeded
    }
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
    }
    return ret, address, contract.Gas, err

}

合约调用

EVM提供了以下四个方法来实现合约的调用:EVM.Call、EVM.CallCode、EVM.DelegateCall、EVM.StaticCall

EVM.Call

下面的这段代码是以太坊虚拟机(EVM)的Call函数的实现,它执行与指定地址关联的合约代码并处理必要的转账操作,在执行过程中会进行递归和深度限制的检查,验证转账金额是否足够,创建状态快照,执行合约代码并在出现错误时回滚状态,最后返回执行结果、剩余的Gas和可能的错误

// filedir:go-ethereum-1.10.2\core\vm\evm.go    L204
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }
    // Fail if we're trying to execute above the call depth limit
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }
    // Fail if we're trying to transfer more than the available balance
    if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }
    snapshot := evm.StateDB.Snapshot()
    p, isPrecompile := evm.precompile(addr)

    if !evm.StateDB.Exist(addr) {
        if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
            // Calling a non existing account, don't do anything, but ping the tracer
            if evm.vmConfig.Debug && evm.depth == 0 {
                evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
                evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
            }
            return nil, gas, nil
        }
        evm.StateDB.CreateAccount(addr)
    }
    evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)

    // Capture the tracer start/end events in debug mode
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
        defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
            evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
        }(gas, time.Now())
    }

    if isPrecompile {
        ret, gas, err = RunPrecompiledContract(p, input, gas)
    } else {
        // Initialise a new contract and set the code that is to be used by the EVM.
        // The contract is a scoped environment for this execution context only.
        code := evm.StateDB.GetCode(addr)
        if len(code) == 0 {
            ret, err = nil, nil // gas is unchanged
        } else {
            addrCopy := addr
            // If the account has no code, we can abort here
            // The depth-check is already done, and precompiles handled above
            contract := NewContract(caller, AccountRef(addrCopy), value, gas)
            contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
            ret, err = run(evm, contract, input, false)
            gas = contract.Gas
        }
    }
    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if err != nil {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != ErrExecutionReverted {
            gas = 0
        }
        // TODO: consider clearing up unused snapshots:
        //} else {
        //  evm.StateDB.DiscardSnapshot(snapshot)
    }
    return ret, gas, err
}

下面的代码负责处理与合约调用相关的逻辑,包括递归调用的限制、深度控制、余额检查等,同时还负责处理转账操作和创建账户并在执行错误或转账失败时回滚状态,这个函数的具体实现可能包含更多的逻辑来执行合约代码、处理结果和错误等

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }
    // Fail if we're trying to execute above the call depth limit
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }
    // Fail if we're trying to transfer more than the available balance
    if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }
    ......

创建快照并检查地址是否存在,如果地址不存在且不是之前编译好的原生合约(即native Go写的预编译合约)、value值为0则直接返回正常且不消耗gas费用,只做简单的Tracer即可,如果地址存在且是之前编译好的原生合约且valuse值不为0则直接调用Transfer函数进行转账操作:

snapshot := evm.StateDB.Snapshot()
    p, isPrecompile := evm.precompile(addr)

    if !evm.StateDB.Exist(addr) {
        if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
            // Calling a non existing account, don't do anything, but ping the tracer
            if evm.vmConfig.Debug && evm.depth == 0 {
                evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
                evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
            }
            return nil, gas, nil
        }
        evm.StateDB.CreateAccount(addr)
    }
    evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)

Transfer函数实现如下:

// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
    db.SubBalance(sender, amount)
    db.AddBalance(recipient, amount)
}

如果开启debug模式则抓取start和end事件:

// Capture the tracer start/end events in debug mode
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
        defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
            evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
        }(gas, time.Now())
    }

之后检查是否是编译好的原生合约,如果是则调用RunPrecompiledContract对合约进行预编译操作,否则通过evm.StateDB.GetCode(addr)获取合约地址所对应的代码之后使用run进行调用:

if isPrecompile {
        ret, gas, err = RunPrecompiledContract(p, input, gas)
    } else {
        // Initialise a new contract and set the code that is to be used by the EVM.
        // The contract is a scoped environment for this execution context only.
        code := evm.StateDB.GetCode(addr)
        if len(code) == 0 {
            ret, err = nil, nil // gas is unchanged
        } else {
            addrCopy := addr
            // If the account has no code, we can abort here
            // The depth-check is already done, and precompiles handled above
            contract := NewContract(caller, AccountRef(addrCopy), value, gas)
            contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
            ret, err = run(evm, contract, input, false)
            gas = contract.Gas
        }
    }

我们先来看一下RunPrecompiledContract函数,在这里首先会调用RequiredGas来计算所需要的gas费用,之后检查提供的费用是否足够,如果不够则直接返回,同时不消耗gas并提示错误信息,如果gas足够则调用run函数处理操作:

// filedir:go-ethereum-1.10.2\core\vm\contracts.go  L145
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
// It returns
// - the returned bytes,
// - the _remaining_ gas,
// - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
    gasCost := p.RequiredGas(input)
    if suppliedGas < gasCost {
        return nil, 0, ErrOutOfGas
    }
    suppliedGas -= gasCost
    output, err := p.Run(input)
    return output, suppliedGas, err
}

run函数如下所示,在这里会从输入测参数中检索出r,s,v,之后验证签名,之后为了确保input没有修改我们需要将v换回原来的位置,input是(hash, v, r, s),在验证签名时进行了一次转换,转为(r, s, v),现在我们需要还原,所以在r,s之前插入v即可,之后验证公钥的有效性,,之后返回地址:

// filedir:go-ethereum-1.10.2\core\vm\contracts.go  L167
func (c *ecrecover) Run(input []byte) ([]byte, error) {
    const ecRecoverInputLength = 128

    input = common.RightPadBytes(input, ecRecoverInputLength)
    // "input" is (hash, v, r, s), each 32 bytes
    // but for ecrecover we want (r, s, v)

    r := new(big.Int).SetBytes(input[64:96])
    s := new(big.Int).SetBytes(input[96:128])
    v := input[63] - 27

    // tighter sig s values input homestead only apply to tx sigs
    if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) {
        return nil, nil
    }
    // We must make sure not to modify the 'input', so placing the 'v' along with
    // the signature needs to be done on a new allocation
    sig := make([]byte, 65)
    copy(sig, input[64:128])
    sig[64] = v
    // v needs to be at the end for libsecp256k1
    pubKey, err := crypto.Ecrecover(input[:32], sig)
    // make sure the public key is a valid one
    if err != nil {
        return nil, nil
    }

    // the first byte of pubkey is bitcoin heritage
    return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil
}

在else语句中会调用run函数来执行合约,之后返回字节码信息:

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
    for _, interpreter := range evm.interpreters {
        if interpreter.CanRun(contract.Code) {
            if evm.interpreter != interpreter {
                // Ensure that the interpreter pointer is set back
                // to its current value upon return.
                defer func(i Interpreter) {
                    evm.interpreter = i
                }(evm.interpreter)
                evm.interpreter = interpreter
            }
            return interpreter.Run(contract, input, readOnly)
        }
    }
    return nil, errors.New("no compatible interpreter")
}

调用interpreter.Run(contract, input, readOnly)来执行代码并返回结果,这里通过contract.GetOp(pc)获取操作码,之后由vm.Config.JumpTable中的operation解释指令,之后通过operation.execute执行指令:

// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {

    // Increment the call depth which is restricted to 1024
    in.evm.depth++
    defer func() { in.evm.depth-- }()

    // Make sure the readOnly is only set if we aren't in readOnly yet.
    // This makes also sure that the readOnly flag isn't removed for child calls.
    if readOnly && !in.readOnly {
        in.readOnly = true
        defer func() { in.readOnly = false }()
    }

    // Reset the previous call's return data. It's unimportant to preserve the old buffer
    // as every returning call will return new data anyway.
    in.returnData = nil

    // Don't bother with the execution if there's no code.
    if len(contract.Code) == 0 {
        return nil, nil
    }

    var (
        op          OpCode        // current opcode
        mem         = NewMemory() // bound memory
        stack       = newstack()  // local stack
        callContext = &ScopeContext{
            Memory:   mem,
            Stack:    stack,
            Contract: contract,
        }
        // For optimisation reason we're using uint64 as the program counter.
        // It's theoretically possible to go above 2^64. The YP defines the PC
        // to be uint256. Practically much less so feasible.
        pc   = uint64(0) // program counter
        cost uint64
        // copies used by tracer
        pcCopy  uint64 // needed for the deferred Tracer
        gasCopy uint64 // for Tracer to log gas remaining before execution
        logged  bool   // deferred Tracer should ignore already logged steps
        res     []byte // result of the opcode execution function
    )
    // Don't move this deferrred function, it's placed before the capturestate-deferred method,
    // so that it get's executed _after_: the capturestate needs the stacks before
    // they are returned to the pools
    defer func() {
        returnStack(stack)
    }()
    contract.Input = input

    if in.cfg.Debug {
        defer func() {
            if err != nil {
                if !logged {
                    in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
                } else {
                    in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
                }
            }
        }()
    }
    // The Interpreter main run loop (contextual). This loop runs until either an
    // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
    // the execution of one of the operations or until the done flag is set by the
    // parent context.
    steps := 0
    for {
        steps++
        if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 {
            break
        }
        if in.cfg.Debug {
            // Capture pre-execution values for tracing.
            logged, pcCopy, gasCopy = false, pc, contract.Gas
        }

        // Get the operation from the jump table and validate the stack to ensure there are
        // enough stack items available to perform the operation.
        op = contract.GetOp(pc)
        operation := in.cfg.JumpTable[op]
        if operation == nil {
            return nil, &ErrInvalidOpCode{opcode: op}
        }
        // Validate stack
        if sLen := stack.len(); sLen < operation.minStack {
            return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
        } else if sLen > operation.maxStack {
            return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
        }
        // If the operation is valid, enforce and write restrictions
        if in.readOnly && in.evm.chainRules.IsByzantium {
            // If the interpreter is operating in readonly mode, make sure no
            // state-modifying operation is performed. The 3rd stack item
            // for a call operation is the value. Transferring value from one
            // account to the others means the state is modified and should also
            // return with an error.
            if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
                return nil, ErrWriteProtection
            }
        }
        // Static portion of gas
        cost = operation.constantGas // For tracing
        if !contract.UseGas(operation.constantGas) {
            return nil, ErrOutOfGas
        }

        var memorySize uint64
        // calculate the new memory size and expand the memory to fit
        // the operation
        // Memory check needs to be done prior to evaluating the dynamic gas portion,
        // to detect calculation overflows
        if operation.memorySize != nil {
            memSize, overflow := operation.memorySize(stack)
            if overflow {
                return nil, ErrGasUintOverflow
            }
            // memory is expanded in words of 32 bytes. Gas
            // is also calculated in words.
            if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
                return nil, ErrGasUintOverflow
            }
        }
        // Dynamic portion of gas
        // consume the gas and return an error if not enough gas is available.
        // cost is explicitly set so that the capture state defer method can get the proper cost
        if operation.dynamicGas != nil {
            var dynamicCost uint64
            dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
            cost += dynamicCost // total cost, for debug tracing
            if err != nil || !contract.UseGas(dynamicCost) {
                return nil, ErrOutOfGas
            }
        }
        if memorySize > 0 {
            mem.Resize(memorySize)
        }

        if in.cfg.Debug {
            in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
            logged = true
        }

        // execute the operation
        res, err = operation.execute(&pc, in, callContext)
        // if the operation clears the return data (e.g. it has returning data)
        // set the last return to the result of the operation.
        if operation.returns {
            in.returnData = common.CopyBytes(res)
        }

        switch {
        case err != nil:
            return nil, err
        case operation.reverts:
            return res, ErrExecutionReverted
        case operation.halts:
            return res, nil
        case !operation.jumps:
            pc++
        }
    }
    return nil, nil
}

如果出现错误则回退到创建之前的时刻,进行回滚操作:

// When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if err != nil {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != ErrExecutionReverted {
            gas = 0
        }
        // TODO: consider clearing up unused snapshots:
        //} else {
        //  evm.StateDB.DiscardSnapshot(snapshot)
    }
    return ret, gas, err

处于篇幅文字限制,后面再补充一篇进行完结~

0 条评论
某人
表情
可输入 255