以太坊虚拟机(下篇)

admin 2023年4月18日02:19:38评论21 views字数 45425阅读151分25秒阅读模式

文章前言

区块链上的虚拟机(Virtual Machine)是指建立在去中心化的区块链上的代码运行环境,目前市面上比较主流的便是以太坊虚拟机(Ethereum Virtual Machine,EVM)和类以太坊虚拟机,它基于Account账户模型将智能合约代码以对外完全隔离的方式在内部运行,实现了图灵完备的智能合约体系,本篇文章将从源码角度对其工作原理进行简要分析~

合约调用

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

EVM.Call

Call函数的完整代码如下所示:

// filedir:go-ethereum-1.10.2corevmevm.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}

在这里首先检查了递归调用的深度,之后检查合约调用者是否有足够的资产用于支付交易中的ether

// 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 Dbfunc 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.2corevmcontracts.go  L145// RunPrecompiledContract runs and evaluates the output of a precompiled contract.// It returns// - the returned bytes,// - the _remaining_ gas,// - any error that occurredfunc 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.2corevmcontracts.go  L167func (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
EVM.CallCode

EVM.CallCode函数代码如下所示:

// CallCode 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.//// CallCode differs from Call in the sense that it executes the given address'// code with the caller as context.func (evm *EVM) CallCode(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  // Note although it's noop to transfer X ether to caller itself. But  // if caller doesn't have enough balance, it would be an error to allow  // over-charging itself. So the check here is necessary.  if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {    return nil, gas, ErrInsufficientBalance  }  var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr // 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(caller.Address()), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = run(evm, contract, input, false) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { gas = 0 } } return ret, gas, err}

在这里首先检查了递归调用的深度,之后检查合约调用者是否有足够的资产用于支付交易中的ether,之后创建快照:

  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  // Note although it's noop to transfer X ether to caller itself. But  // if caller doesn't have enough balance, it would be an error to allow  // over-charging itself. So the check here is necessary.  if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {    return nil, gas, ErrInsufficientBalance  }  var snapshot = evm.StateDB.Snapshot()

之后检查是已编译的原生合约,如果是则直接调用RunPrecompiledContract:

  // It is allowed to call precompiles, even via delegatecall  if p, isPrecompile := evm.precompile(addr); isPrecompile {    ret, gas, err = RunPrecompiledContract(p, input, gas)  } else {    addrCopy := addr    // 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(caller.Address()), value, gas)    contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))    ret, err = run(evm, contract, input, false)    gas = contract.Gas  }  if err != nil {    evm.StateDB.RevertToSnapshot(snapshot)    if err != ErrExecutionReverted {      gas = 0    }  }  return ret, gas, err

RunPrecompiledContract函数如下,首先检查gas是否足够,之后调用run,和call一致:

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.// It returns// - the returned bytes,// - the _remaining_ gas,// - any error that occurredfunc 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函数执行合约,之后返回字节码信息:

// 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")}

如果有错误产生则回退到之前的快照:

  if err != nil {    evm.StateDB.RevertToSnapshot(snapshot)    if err != ErrExecutionReverted {      gas = 0    }  }  return ret, gas, err
EVM.DelegateCall

EVM.DelegateCall代码如下所示,与CallCode类似,这里不再赘述:

// DelegateCall executes the contract associated with the addr with the given input// as parameters. It reverses the state in case of an execution error.//// DelegateCall differs from CallCode in the sense that it executes the given address'// code with the caller as context and the caller is set to the caller of the caller.func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (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  }  var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = run(evm, contract, input, false) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { gas = 0 } } return ret, gas, err}
EVM.StaticCall

EVM.StaticCall函数代码如下所示,与CallCode大同小异,这里不再赘述:

// StaticCall executes the contract associated with the addr with the given input// as parameters while disallowing any modifications to the state during the call.// Opcodes that attempt to perform such modifications will result in exceptions// instead of performing the modifications.func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (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  }  // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.  // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced  // after all empty accounts were deleted, so this is not required. However, if we omit this,  // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.  // We could change this, but for now it's left for legacy reasons  var snapshot = evm.StateDB.Snapshot()
// We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios evm.StateDB.AddBalance(addr, big0)
if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. addrCopy := addr // 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(addrCopy), new(big.Int), gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) // 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. ret, err = run(evm, contract, input, true) gas = contract.Gas } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { gas = 0 } } return ret, gas, err}
原生合约

在上面的调用分析过程中我们曾涉及到一个概念——"原生合约",当地址为原生合约时直接进入RunPrecompiledContract执行原生合约,无需通过解释器进行解释,我们对其简单介绍一下下:

// CallCode 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.//// CallCode differs from Call in the sense that it executes the given address'// code with the caller as context.func (evm *EVM) CallCode(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  // Note although it's noop to transfer X ether to caller itself. But  // if caller doesn't have enough balance, it would be an error to allow  // over-charging itself. So the check here is necessary.  if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {    return nil, gas, ErrInsufficientBalance  }  var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr // 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(caller.Address()), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = run(evm, contract, input, false) gas = contract.Gas } ......

首先跟进evm.precompile()函数中,在这里根据当前evm链规则选择不同的precompiles集合,之后从precompiles检索我们的地址是否在其中:

func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {  var precompiles map[common.Address]PrecompiledContract  switch {  case evm.chainRules.IsBerlin:    precompiles = PrecompiledContractsBerlin  case evm.chainRules.IsIstanbul:    precompiles = PrecompiledContractsIstanbul  case evm.chainRules.IsByzantium:    precompiles = PrecompiledContractsByzantium  default:    precompiles = PrecompiledContractsHomestead  }  p, ok := precompiles[addr]  return p, ok}

evm.chainRules定义结构如下:

// filedir:go-ethereum-1.10.2paramsconfig.go  L616// Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions// that do not have or require information about the block.//// Rules is a one time interface meaning that it shouldn't be used in between transition// phases.type Rules struct {  ChainID                                                 *big.Int  IsHomestead, IsEIP150, IsEIP155, IsEIP158               bool  IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool  IsBerlin                                                bool}

我们跟进PrecompiledContract可以看到这里的原生合约的地址时预先定义好了的,以PrecompiledContractsHomestead为例来说,从下面可以看到它有4个预定义合约地址序列,与地址对应的对象也都有一个Run方法,用于执行各自的内容:

// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum// contracts used in the Frontier and Homestead releases.var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{  common.BytesToAddress([]byte{1}): &ecrecover{},  common.BytesToAddress([]byte{2}): &sha256hash{},  common.BytesToAddress([]byte{3}): &ripemd160hash{},  common.BytesToAddress([]byte{4}): &dataCopy{},}
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum// contracts used in the Byzantium release.var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},}
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum// contracts used in the Istanbul release.var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, common.BytesToAddress([]byte{9}): &blake2F{},}
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum// contracts used in the Berlin release.var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, common.BytesToAddress([]byte{9}): &blake2F{},}
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum// contracts specified in EIP-2537. These are exported for testing purposes.var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{10}): &bls12381G1Add{}, common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, common.BytesToAddress([]byte{13}): &bls12381G2Add{}, common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, common.BytesToAddress([]byte{16}): &bls12381Pairing{}, common.BytesToAddress([]byte{17}): &bls12381MapG1{}, common.BytesToAddress([]byte{18}): &bls12381MapG2{},}
var ( PrecompiledAddressesBerlin []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address)
解释结构

解释器数据结构定义如下所示:

// filedir:go-ethereum-1.10.2corevminterpreter.go  L28// Config are the configuration options for the Interpretertype Config struct {  Debug                   bool   // Enables debugging  Tracer                  Tracer // Opcode logger  NoRecursion             bool   // Disables call, callcode, delegate call and create  EnablePreimageRecording bool   // Enables recording of SHA3/keccak preimages
JumpTable [256]*operation // EVM instruction table, automatically populated if unset
EWASMInterpreter string // External EWASM interpreter options EVMInterpreter string // External EVM interpreter options
ExtraEips []int // Additional EIPS that are to be enabled}
// Interpreter is used to run Ethereum based contracts and will utilise the// passed environment to query external sources for state information.// The Interpreter will run the byte code VM based on the passed// configuration.type Interpreter interface { // 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. Run(contract *Contract, input []byte, static bool) ([]byte, error) // CanRun tells if the contract, passed as an argument, can be // run by the current interpreter. This is meant so that the // caller can do something like: // // ```golang // for _, interpreter := range interpreters { // if interpreter.CanRun(contract.code) { // interpreter.Run(contract.code, input) // } // } // ``` CanRun([]byte) bool}
解释对象

NewEVMInterpreter用于创建一个解释器,首先填充cfg.JumpTable,在填充时会根据以太坊版本来填充相应的字段,同时通过一个for循环来开启EIP,最后返回一个EVMInterpreter对象:

// filedir:go-ethereum-1.10.2corevminterpreter.go  L92// NewEVMInterpreter returns a new instance of the Interpreter.func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {  // We use the STOP instruction whether to see  // the jump table was initialised. If it was not  // we'll set the default jump table.  if cfg.JumpTable[STOP] == nil {    var jt JumpTable    switch {    case evm.chainRules.IsBerlin:      jt = berlinInstructionSet    case evm.chainRules.IsIstanbul:      jt = istanbulInstructionSet    case evm.chainRules.IsConstantinople:      jt = constantinopleInstructionSet    case evm.chainRules.IsByzantium:      jt = byzantiumInstructionSet    case evm.chainRules.IsEIP158:      jt = spuriousDragonInstructionSet    case evm.chainRules.IsEIP150:      jt = tangerineWhistleInstructionSet    case evm.chainRules.IsHomestead:      jt = homesteadInstructionSet    default:      jt = frontierInstructionSet    }    for i, eip := range cfg.ExtraEips {      if err := EnableEIP(eip, &jt); err != nil {        // Disable it, so caller can check if it's activated or not        cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...)        log.Error("EIP activation failed", "eip", eip, "error", err)      }    }    cfg.JumpTable = jt  }
return &EVMInterpreter{ evm: evm, cfg: cfg, }}
指令结构

go-ethereum-1.10.2corevmjump_table.go文件中定义了指令操作的数据结构:

type (  executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)  gasFunc       func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64  // memorySizeFunc returns the required size, and whether the operation overflowed a uint64  memorySizeFunc func(*Stack) (size uint64, overflow bool))
type operation struct { // execute is the operation function execute executionFunc constantGas uint64 dynamicGas gasFunc // minStack tells how many stack items are required minStack int // maxStack specifies the max length the stack can have for this operation // to not overflow the stack. maxStack int
// memorySize returns the memory size required for the operation memorySize memorySizeFunc
halts bool // indicates whether the operation should halt further execution jumps bool // indicates whether the program counter should not increment writes bool // determines whether this a state modifying operation reverts bool // determines whether the operation reverts state (implicitly halts) returns bool // determines whether the operations sets the return data content}

同时根据不同的以太坊版本订制不同的指令集:

// JumpTable contains the EVM opcodes supported at a given fork.type JumpTable [256]*operation
// newBerlinInstructionSet returns the frontier, homestead, byzantium,// contantinople, istanbul, petersburg and berlin instructions.func newBerlinInstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 return instructionSet}
// newIstanbulInstructionSet returns the frontier, homestead, byzantium,// contantinople, istanbul and petersburg instructions.func newIstanbulInstructionSet() JumpTable { instructionSet := newConstantinopleInstructionSet()
enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344 enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884 enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200
return instructionSet}
// newConstantinopleInstructionSet returns the frontier, homestead,// byzantium and contantinople instructions.func newConstantinopleInstructionSet() JumpTable {  instructionSet := newByzantiumInstructionSet()  instructionSet[SHL] = &operation{    execute:     opSHL,    constantGas: GasFastestStep,    minStack:    minStack(2, 1),    maxStack:    maxStack(2, 1),  }  instructionSet[SHR] = &operation{    execute:     opSHR,    constantGas: GasFastestStep,    minStack:    minStack(2, 1),    maxStack:    maxStack(2, 1),  }  instructionSet[SAR] = &operation{    execute:     opSAR,    constantGas: GasFastestStep,    minStack:    minStack(2, 1),    maxStack:    maxStack(2, 1),  }  instructionSet[EXTCODEHASH] = &operation{    execute:     opExtCodeHash,    constantGas: params.ExtcodeHashGasConstantinople,    minStack:    minStack(1, 1),    maxStack:    maxStack(1, 1),  }  instructionSet[CREATE2] = &operation{    execute:     opCreate2,    constantGas: params.Create2Gas,    dynamicGas:  gasCreate2,    minStack:    minStack(4, 1),    maxStack:    maxStack(4, 1),    memorySize:  memoryCreate2,    writes:      true,    returns:     true,  }  return instructionSet}......
指令相关

go-ethereum-1.10.2corevminstructions.go文件中包含了常见的操作指令,例如:

func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {    x, y := scope.Stack.pop(), scope.Stack.peek()    y.Add(&x, y)    return nil, nil}
func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Sub(&x, y) return nil, nil}
func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Mul(&x, y) return nil, nil}
指令解释

解释器的关键方法为Run方法,其代码如下所示:

// filedir: go-ethereum-1.10.2corevminterpreter.go L133// 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}

在Run函数的开头之处,首先增加了当前的调用深度(有多个defer是会进行压栈操作,所以函数执行完之后会恢复到最初的深度)

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

之后检查readOnly是否设置,之后将上一个调用的返回数据清空,之后检查合约代码是否为空,如果为空则直接返回,之后定义一些局部变量(操作码、内存块、栈等),之后检查是否开启debug模式,如果是则跟踪调用执行记录:

  // 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) } } }() }

之后解释器的主循环,它通过一个for循环来来实现,该循环会一直执行,直到执行显示结束(STOP)、停止(RETURN)、自毁(SELFDESTRUCT)、在执行过程中发生错误,在这里首先检查执行的次数是否为1000的整数倍同时evm是否终止,如果满足条件则直接break,否则继续向下执行来检查debug是否开启:

  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    }

之后通过GetOp获取操作码,从JumpTable获取操作指令,并检查操作指令是否为空以及栈空间是否足够,同时检查readOnly是否开启并确保在开启模式下不存在数据状态修改操作,调用Usegas来消耗执行操作所需要的gas并返回一个布尔值来标识当前的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    }

首先检查操作memorySize是否需要用到内存存储空间,如果会用到则计算所需要的空间大小并将空间大小进行适当扩展:

    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      }    }

之后检查dynamicGas是否为空,如果不为空则检查gas费用是否足够,之后重置memorySize以保证足够的内存存储空间,如果开启debug模式则捕获所有的操作码以及storage信息、地址信息等:

    // 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 }

之后通过operation.execute来执行操作,并通过switch方法来检查返回结果的错误值或者先前移动pc:

    // 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
操作指令

go-ethereum-1.10.2corevmopcodes.go文件中定义了常见的操作指令:

// OpCode is an EVM opcodetype OpCode byte
// IsPush specifies if an opcode is a PUSH opcode.func (op OpCode) IsPush() bool { switch op { case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: return true } return false}
// IsStaticJump specifies if an opcode is JUMP.func (op OpCode) IsStaticJump() bool { return op == JUMP}
// 0x0 range - arithmetic ops.const ( STOP OpCode = iota ADD MUL SUB DIV SDIV MOD SMOD ADDMOD MULMOD EXP SIGNEXTEND)
// 0x10 range - comparison ops.const ( LT OpCode = iota + 0x10 GT SLT SGT EQ ISZERO AND OR XOR NOT BYTE SHL SHR SAR
SHA3 OpCode = 0x20)
// 0x30 range - closure state.const ( ADDRESS OpCode = 0x30 + iota BALANCE ORIGIN CALLER CALLVALUE CALLDATALOAD CALLDATASIZE CALLDATACOPY CODESIZE CODECOPY GASPRICE EXTCODESIZE EXTCODECOPY RETURNDATASIZE RETURNDATACOPY EXTCODEHASH)......
栈操作类

go-ethereum-1.10.2corevmstack.go文件中定义与栈操作相关的指令,其数据结构如下:

// Stack is an object for basic stack operations. Items popped to the stack are// expected to be changed and modified. stack does not take care of adding newly// initialised objects.type Stack struct {  data []uint256.Int}func newstack() *Stack {  return stackPool.Get().(*Stack)}

PUSH——压栈操作

func (st *Stack) push(d *uint256.Int) {  // NOTE push limit (1024) is checked in baseCheck  st.data = append(st.data, *d)}func (st *Stack) pushN(ds ...uint256.Int) {  // FIXME: Is there a way to pass args by pointers.  st.data = append(st.data, ds...)}

POP——出栈操作

func (st *Stack) pop() (ret uint256.Int) {  ret = st.data[len(st.data)-1]  st.data = st.data[:len(st.data)-1]  return}

SWAP——元素交换

func (st *Stack) swap(n int) {  st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]}

DUP——复制数据到栈顶

func (st *Stack) dup(n int) {  st.push(&st.data[st.len()-n])}

PEEK——检索栈顶数据

func (st *Stack) peek() *uint256.Int {  return &st.data[st.len()-1]}

BACK——查看指定位置的数据

// Back returns the n'th item in stackfunc (st *Stack) Back(n int) *uint256.Int {  return &st.data[st.len()-n-1]}
费用计算

go-ethereum-1.10.2corevmgas_table.go文件包含了各种操作所需要消耗的Gas费用:

func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {  var (    gas            uint64    transfersValue = !stack.Back(2).IsZero()    address        = common.Address(stack.Back(1).Bytes20())  )  if evm.chainRules.IsEIP158 {    if transfersValue && evm.StateDB.Empty(address) {      gas += params.CallNewAccountGas    }  } else if !evm.StateDB.Exist(address) {    gas += params.CallNewAccountGas  }  if transfersValue {    gas += params.CallValueTransferGas  }  memoryGas, err := memoryGasCost(mem, memorySize)  if err != nil {    return 0, err  }  var overflow bool  if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {    return 0, ErrGasUintOverflow  }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } return gas, nil}
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } var ( gas uint64 overflow bool ) if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } return gas, nil}
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } return gas, nil}
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } return gas, nil}

参考链接

https://www.jianshu.com/p/a6789f97743f

https://www.jianshu.com/p/f319c78e9714

原文始发于微信公众号(七芒星实验室):以太坊虚拟机(下篇)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月18日02:19:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   以太坊虚拟机(下篇)https://cn-sec.com/archives/1676193.html

发表评论

匿名网友 填写信息