core/vm: track 63/64 call gas off stack (#15563)

* core/vm: track 63/64 call gas off stack

Gas calculations in gasCall* relayed the available gas for calls by
replacing it on the stack. This lead to inconsistent traces, which we
papered over by copying the pre-execution stack in trace mode.

This change relays available gas using a temporary variable, off the
stack, and allows removing the weird copy.

* core/vm: remove stackCopy

* core/vm: pop call gas into pool

* core/vm: to -> addr
release/1.8
Felix Lange 7 years ago committed by Péter Szilágyi
parent 8f35e3086c
commit be12392fba
  1. 4
      core/vm/evm.go
  2. 46
      core/vm/gas_table.go
  3. 76
      core/vm/instructions.go
  4. 28
      core/vm/interpreter.go

@ -104,6 +104,10 @@ type EVM struct {
// abort is used to abort the EVM calling operations // abort is used to abort the EVM calling operations
// NOTE: must be set atomically // NOTE: must be set atomically
abort int32 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
} }
// NewEVM retutrns a new EVM . The returned EVM is not thread safe and should // NewEVM retutrns a new EVM . The returned EVM is not thread safe and should

@ -342,19 +342,11 @@ func gasCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
cg, err := callGas(gt, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(gt, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Replace the stack item with the new gas calculation. This means that if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
// either the original item is left on the stack or the item is replaced by:
// (availableGas - gas) * 63 / 64
// We replace the stack item so that it's available when the opCall instruction is
// called. This information is otherwise lost due to the dependency on *current*
// available gas.
stack.data[stack.len()-1] = new(big.Int).SetUint64(cg)
if gas, overflow = math.SafeAdd(gas, cg); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil
@ -374,19 +366,11 @@ func gasCallCode(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack,
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
cg, err := callGas(gt, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(gt, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Replace the stack item with the new gas calculation. This means that if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
// either the original item is left on the stack or the item is replaced by:
// (availableGas - gas) * 63 / 64
// We replace the stack item so that it's available when the opCall instruction is
// called. This information is otherwise lost due to the dependency on *current*
// available gas.
stack.data[stack.len()-1] = new(big.Int).SetUint64(cg)
if gas, overflow = math.SafeAdd(gas, cg); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil
@ -436,18 +420,11 @@ func gasDelegateCall(gt params.GasTable, evm *EVM, contract *Contract, stack *St
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
cg, err := callGas(gt, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(gt, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Replace the stack item with the new gas calculation. This means that if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
// either the original item is left on the stack or the item is replaced by:
// (availableGas - gas) * 63 / 64
// We replace the stack item so that it's available when the opCall instruction is
// called.
stack.data[stack.len()-1] = new(big.Int).SetUint64(cg)
if gas, overflow = math.SafeAdd(gas, cg); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil
@ -463,18 +440,11 @@ func gasStaticCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stac
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
cg, err := callGas(gt, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(gt, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Replace the stack item with the new gas calculation. This means that if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
// either the original item is left on the stack or the item is replaced by:
// (availableGas - gas) * 63 / 64
// We replace the stack item so that it's available when the opCall instruction is
// called.
stack.data[stack.len()-1] = new(big.Int).SetUint64(cg)
if gas, overflow = math.SafeAdd(gas, cg); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil

@ -603,24 +603,20 @@ func opCreate(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *S
} }
func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
gas := stack.pop().Uint64() // Pop gas. The actual gas in in evm.callGasTemp.
// pop gas and value of the stack. evm.interpreter.intPool.put(stack.pop())
addr, value := stack.pop(), stack.pop() gas := evm.callGasTemp
// Pop other call parameters.
addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.BigToAddress(addr)
value = math.U256(value) value = math.U256(value)
// pop input size and offset // Get the arguments from the memory.
inOffset, inSize := stack.pop(), stack.pop()
// pop return size and offset
retOffset, retSize := stack.pop(), stack.pop()
address := common.BigToAddress(addr)
// Get the arguments from the memory
args := memory.Get(inOffset.Int64(), inSize.Int64()) args := memory.Get(inOffset.Int64(), inSize.Int64())
if value.Sign() != 0 { if value.Sign() != 0 {
gas += params.CallStipend gas += params.CallStipend
} }
ret, returnGas, err := evm.Call(contract, address, args, gas, value) ret, returnGas, err := evm.Call(contract, toAddr, args, gas, value)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
} else { } else {
@ -636,25 +632,20 @@ func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Sta
} }
func opCallCode(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opCallCode(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
gas := stack.pop().Uint64() // Pop gas. The actual gas is in evm.callGasTemp.
// pop gas and value of the stack. evm.interpreter.intPool.put(stack.pop())
addr, value := stack.pop(), stack.pop() gas := evm.callGasTemp
// Pop other call parameters.
addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.BigToAddress(addr)
value = math.U256(value) value = math.U256(value)
// pop input size and offset // Get arguments from the memory.
inOffset, inSize := stack.pop(), stack.pop()
// pop return size and offset
retOffset, retSize := stack.pop(), stack.pop()
address := common.BigToAddress(addr)
// Get the arguments from the memory
args := memory.Get(inOffset.Int64(), inSize.Int64()) args := memory.Get(inOffset.Int64(), inSize.Int64())
if value.Sign() != 0 { if value.Sign() != 0 {
gas += params.CallStipend gas += params.CallStipend
} }
ret, returnGas, err := evm.CallCode(contract, toAddr, args, gas, value)
ret, returnGas, err := evm.CallCode(contract, address, args, gas, value)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
} else { } else {
@ -670,9 +661,13 @@ func opCallCode(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack
} }
func opDelegateCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opDelegateCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
gas, to, inOffset, inSize, outOffset, outSize := stack.pop().Uint64(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() // Pop gas. The actual gas is in evm.callGasTemp.
evm.interpreter.intPool.put(stack.pop())
toAddr := common.BigToAddress(to) gas := evm.callGasTemp
// Pop other call parameters.
addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.BigToAddress(addr)
// Get arguments from the memory.
args := memory.Get(inOffset.Int64(), inSize.Int64()) args := memory.Get(inOffset.Int64(), inSize.Int64())
ret, returnGas, err := evm.DelegateCall(contract, toAddr, args, gas) ret, returnGas, err := evm.DelegateCall(contract, toAddr, args, gas)
@ -682,30 +677,25 @@ func opDelegateCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
} }
if err == nil || err == errExecutionReverted { if err == nil || err == errExecutionReverted {
memory.Set(outOffset.Uint64(), outSize.Uint64(), ret) memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
contract.Gas += returnGas contract.Gas += returnGas
evm.interpreter.intPool.put(to, inOffset, inSize, outOffset, outSize) evm.interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize)
return ret, nil return ret, nil
} }
func opStaticCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opStaticCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// pop gas // Pop gas. The actual gas is in evm.callGasTemp.
gas := stack.pop().Uint64() evm.interpreter.intPool.put(stack.pop())
// pop address gas := evm.callGasTemp
addr := stack.pop() // Pop other call parameters.
// pop input size and offset addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
inOffset, inSize := stack.pop(), stack.pop() toAddr := common.BigToAddress(addr)
// pop return size and offset // Get arguments from the memory.
retOffset, retSize := stack.pop(), stack.pop()
address := common.BigToAddress(addr)
// Get the arguments from the memory
args := memory.Get(inOffset.Int64(), inSize.Int64()) args := memory.Get(inOffset.Int64(), inSize.Int64())
ret, returnGas, err := evm.StaticCall(contract, address, args, gas) ret, returnGas, err := evm.StaticCall(contract, toAddr, args, gas)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
} else { } else {

@ -138,16 +138,15 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
pc = uint64(0) // program counter pc = uint64(0) // program counter
cost uint64 cost uint64
// copies used by tracer // copies used by tracer
stackCopy = newstack() // stackCopy needed for Tracer since stack is mutated by 63/64 gas rule pcCopy uint64 // needed for the deferred Tracer
pcCopy uint64 // needed for the deferred Tracer gasCopy uint64 // for Tracer to log gas remaining before execution
gasCopy uint64 // for Tracer to log gas remaining before execution logged bool // deferred Tracer should ignore already logged steps
logged bool // deferred Tracer should ignore already logged steps
) )
contract.Input = input contract.Input = input
defer func() { defer func() {
if err != nil && !logged && in.cfg.Debug { if err != nil && !logged && in.cfg.Debug {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stackCopy, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
} }
}() }()
@ -156,21 +155,14 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
// the execution of one of the operations or until the done flag is set by the // the execution of one of the operations or until the done flag is set by the
// parent context. // parent context.
for atomic.LoadInt32(&in.evm.abort) == 0 { for atomic.LoadInt32(&in.evm.abort) == 0 {
// Get the memory location of pc
op = contract.GetOp(pc)
if in.cfg.Debug { if in.cfg.Debug {
logged = false // Capture pre-execution values for tracing.
pcCopy = pc logged, pcCopy, gasCopy = false, pc, contract.Gas
gasCopy = contract.Gas
stackCopy = newstack()
for _, val := range stack.data {
stackCopy.push(val)
}
} }
// Get the operation from the jump table matching the opcode and validate the // Get the operation from the jump table and validate the stack to ensure there are
// stack and make sure there enough stack items available to perform the operation // enough stack items available to perform the operation.
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op] operation := in.cfg.JumpTable[op]
if !operation.valid { if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
@ -211,7 +203,7 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
} }
if in.cfg.Debug { if in.cfg.Debug {
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stackCopy, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
logged = true logged = true
} }

Loading…
Cancel
Save