2ce00adb55
* focus on performance improvement in many aspects. 1. Do BlockBody verification concurrently; 2. Do calculation of intermediate root concurrently; 3. Preload accounts before processing blocks; 4. Make the snapshot layers configurable. 5. Reuse some object to reduce GC. add * rlp: improve decoder stream implementation (#22858) This commit makes various cleanup changes to rlp.Stream. * rlp: shrink Stream struct This removes a lot of unused padding space in Stream by reordering the fields. The size of Stream changes from 120 bytes to 88 bytes. Stream instances are internally cached and reused using sync.Pool, so this does not improve performance. * rlp: simplify list stack The list stack kept track of the size of the current list context as well as the current offset into it. The size had to be stored in the stack in order to subtract it from the remaining bytes of any enclosing list in ListEnd. It seems that this can be implemented in a simpler way: just subtract the size from the enclosing list context in List instead. * rlp: use atomic.Value for type cache (#22902) All encoding/decoding operations read the type cache to find the writer/decoder function responsible for a type. When analyzing CPU profiles of geth during sync, I found that the use of sync.RWMutex in cache lookups appears in the profiles. It seems we are running into CPU cache contention problems when package rlp is heavily used on all CPU cores during sync. This change makes it use atomic.Value + a writer lock instead of sync.RWMutex. In the common case where the typeinfo entry is present in the cache, we simply fetch the map and lookup the type. * rlp: optimize byte array handling (#22924) This change improves the performance of encoding/decoding [N]byte. name old time/op new time/op delta DecodeByteArrayStruct-8 336ns ± 0% 246ns ± 0% -26.98% (p=0.000 n=9+10) EncodeByteArrayStruct-8 225ns ± 1% 148ns ± 1% -34.12% (p=0.000 n=10+10) name old alloc/op new alloc/op delta DecodeByteArrayStruct-8 120B ± 0% 48B ± 0% -60.00% (p=0.000 n=10+10) EncodeByteArrayStruct-8 0.00B 0.00B ~ (all equal) * rlp: optimize big.Int decoding for size <= 32 bytes (#22927) This change grows the static integer buffer in Stream to 32 bytes, making it possible to decode 256bit integers without allocating a temporary buffer. In the recent commit 088da24, Stream struct size decreased from 120 bytes down to 88 bytes. This commit grows the struct to 112 bytes again, but the size change will not degrade performance because Stream instances are internally cached in sync.Pool. name old time/op new time/op delta DecodeBigInts-8 12.2µs ± 0% 8.6µs ± 4% -29.58% (p=0.000 n=9+10) name old speed new speed delta DecodeBigInts-8 230MB/s ± 0% 326MB/s ± 4% +42.04% (p=0.000 n=9+10) * eth/protocols/eth, les: avoid Raw() when decoding HashOrNumber (#22841) Getting the raw value is not necessary to decode this type, and decoding it directly from the stream is faster. * fix testcase * debug no lazy * fix can not repair * address comments Co-authored-by: Felix Lange <fjl@twurst.com>
318 lines
11 KiB
Go
318 lines
11 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"hash"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
)
|
|
|
|
var EVMInterpreterPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &EVMInterpreter{}
|
|
},
|
|
}
|
|
|
|
// Config are the configuration options for the Interpreter
|
|
type 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
|
|
}
|
|
|
|
// ScopeContext contains the things that are per-call, such as stack and memory,
|
|
// but not transients like pc and gas
|
|
type ScopeContext struct {
|
|
Memory *Memory
|
|
Stack *Stack
|
|
Contract *Contract
|
|
}
|
|
|
|
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
|
|
// Read to get a variable amount of data from the hash state. Read is faster than Sum
|
|
// because it doesn't copy the internal state, but also modifies the internal state.
|
|
type keccakState interface {
|
|
hash.Hash
|
|
Read([]byte) (int, error)
|
|
}
|
|
|
|
// EVMInterpreter represents an EVM interpreter
|
|
type EVMInterpreter struct {
|
|
evm *EVM
|
|
cfg Config
|
|
|
|
hasher keccakState // Keccak256 hasher instance shared across opcodes
|
|
hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes
|
|
|
|
readOnly bool // Whether to throw on stateful modifications
|
|
returnData []byte // Last CALL's return data for subsequent reuse
|
|
}
|
|
|
|
// 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
|
|
}
|
|
evmInterpreter := EVMInterpreterPool.Get().(*EVMInterpreter)
|
|
evmInterpreter.evm = evm
|
|
evmInterpreter.cfg = cfg
|
|
evmInterpreter.readOnly = false
|
|
evmInterpreter.returnData = nil
|
|
return evmInterpreter
|
|
}
|
|
|
|
// 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 also makes 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
|
|
|
|
// TODO temporary fix for issue
|
|
// 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 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 = 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
|
|
}
|
|
|
|
// CanRun tells if the contract, passed as an argument, can be
|
|
// run by the current interpreter.
|
|
func (in *EVMInterpreter) CanRun(code []byte) bool {
|
|
return true
|
|
}
|