From e5f1d94c70ca2837fcc151e70f7a555cf67faf9d Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 26 Mar 2024 16:16:16 +0800 Subject: [PATCH] feat: EVM execution opcode level optimization several optimizations included: - opcode fusion - jumpdest calculation optimization - SHA caching in interpreter Co-authored-by: redhdx --- cmd/evm/main.go | 5 + cmd/evm/runner.go | 8 +- cmd/geth/main.go | 1 + cmd/utils/flags.go | 20 +- core/opcodeCompiler/compiler/OpCodeCache.go | 51 +++ core/opcodeCompiler/compiler/evmByteCode.go | 225 ++++++++++ .../compiler/opcodeProcessor.go | 387 ++++++++++++++++++ core/state/state_object.go | 2 + core/vm/analysis.go | 35 ++ core/vm/contract.go | 27 +- core/vm/errors.go | 1 - core/vm/evm.go | 59 ++- core/vm/gas_table_test.go | 1 - core/vm/instructions.go | 351 ++++++++++++++-- core/vm/interpreter.go | 12 +- core/vm/jump_table.go | 129 ++++++ core/vm/opcodes.go | 77 +++- core/vm/runtime/runtime_example_test.go | 3 +- core/vm/runtime/runtime_test.go | 194 ++++++++- core/vm/stack.go | 14 + crypto/crypto.go | 3 +- eth/backend.go | 3 +- eth/ethconfig/config.go | 43 +- .../internal/tracetest/calltrace_test.go | 1 + eth/tracers/js/tracer_test.go | 93 ++++- go.mod | 4 +- go.sum | 27 ++ 27 files changed, 1682 insertions(+), 94 deletions(-) create mode 100644 core/opcodeCompiler/compiler/OpCodeCache.go create mode 100644 core/opcodeCompiler/compiler/evmByteCode.go create mode 100644 core/opcodeCompiler/compiler/opcodeProcessor.go diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 1f6500b78c..30e3109c31 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -134,6 +134,11 @@ var ( Usage: "enable return data output", Category: flags.VMCategory, } + VMOpcodeOptimizeFlag = &cli.BoolFlag{ + Name: "vm.opcode.optimize", + Usage: "enable opcode optimization", + Value: true, + } ) var stateTransitionCommand = &cli.Command{ diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 45fc985351..d28bd910e7 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "fmt" + compiler2 "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "io" "math/big" "os" @@ -224,7 +225,8 @@ func runCmd(ctx *cli.Context) error { BlobHashes: blobHashes, BlobBaseFee: blobBaseFee, EVMConfig: vm.Config{ - Tracer: tracer, + Tracer: tracer, + EnableOpcodeOptimizations: ctx.Bool(VMOpcodeOptimizeFlag.Name), }, } @@ -234,6 +236,10 @@ func runCmd(ctx *cli.Context) error { runtimeConfig.ChainConfig = params.AllEthashProtocolChanges } + if runtimeConfig.EVMConfig.EnableOpcodeOptimizations { + compiler2.EnableOptimization() + } + var hexInput []byte if inputFileFlag := ctx.String(InputFileFlag.Name); inputFileFlag != "" { var err error diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 05b34344a0..fe34b746f6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -156,6 +156,7 @@ var ( utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, configFileFlag, + utils.VMOpcodeOptimizeFlag, }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8ed5a37318..d9d00a3003 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,6 +23,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "math" "math/big" "net" @@ -1050,6 +1051,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Value: metrics.DefaultConfig.InfluxDBOrganization, Category: flags.MetricsCategory, } + + VMOpcodeOptimizeFlag = &cli.BoolFlag{ + Name: "vm.opcode.optimize", + Usage: "enable opcode optimization", + Value: true, + Category: flags.VMCategory, + } ) var ( @@ -1901,6 +1909,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { + // TODO(fjl): force-enable this in --dev mode + cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) + compiler.EnableOptimization() + } + if ctx.IsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) } @@ -2387,8 +2401,12 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 } - vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)} + vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), + EnableOpcodeOptimizations: ctx.Bool(VMOpcodeOptimizeFlag.Name)} + if vmcfg.EnableOpcodeOptimizations { + compiler.EnableOptimization() + } // Disable transaction indexing/unindexing by default. chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil) if err != nil { diff --git a/core/opcodeCompiler/compiler/OpCodeCache.go b/core/opcodeCompiler/compiler/OpCodeCache.go new file mode 100644 index 0000000000..0fdccc358a --- /dev/null +++ b/core/opcodeCompiler/compiler/OpCodeCache.go @@ -0,0 +1,51 @@ +package compiler + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" +) + +type OpCodeCache struct { + optimizedCodeCache *lru.Cache[common.Hash, []byte] + bitvecCache *lru.Cache[common.Hash, []byte] +} + +func (c *OpCodeCache) GetCachedBitvec(codeHash common.Hash) []byte { + bitvec, _ := c.bitvecCache.Get(codeHash) + return bitvec +} + +func (c *OpCodeCache) AddBitvecCache(codeHash common.Hash, bitvec []byte) { + c.bitvecCache.Add(codeHash, bitvec) +} + +func (c *OpCodeCache) RemoveCachedCode(hash common.Hash) { + c.optimizedCodeCache.Remove(hash) +} + +func (c *OpCodeCache) GetCachedCode(hash common.Hash) []byte { + processedCode, _ := c.optimizedCodeCache.Get(hash) + return processedCode +} + +func (c *OpCodeCache) AddCodeCache(hash common.Hash, optimizedCode []byte) { + c.optimizedCodeCache.Add(hash, optimizedCode) +} + +var opcodeCache *OpCodeCache + +const ( + optimizedCodeCacheCap = 1024 + bitvecCacheCap = 1024 +) + +func init() { + opcodeCache = &OpCodeCache{ + optimizedCodeCache: lru.NewCache[common.Hash, []byte](optimizedCodeCacheCap), + bitvecCache: lru.NewCache[common.Hash, []byte](bitvecCacheCap), + } +} + +func getOpCodeCacheInstance() *OpCodeCache { + return opcodeCache +} diff --git a/core/opcodeCompiler/compiler/evmByteCode.go b/core/opcodeCompiler/compiler/evmByteCode.go new file mode 100644 index 0000000000..f4a335cf1d --- /dev/null +++ b/core/opcodeCompiler/compiler/evmByteCode.go @@ -0,0 +1,225 @@ +package compiler + +// This is copied from vm/opcodes.go. + +// ByteCode is an EVM ByteCode +type ByteCode byte + +// 0x0 range - arithmetic ops. +const ( + STOP ByteCode = 0x0 + ADD ByteCode = 0x1 + MUL ByteCode = 0x2 + SUB ByteCode = 0x3 + DIV ByteCode = 0x4 + SDIV ByteCode = 0x5 + MOD ByteCode = 0x6 + SMOD ByteCode = 0x7 + ADDMOD ByteCode = 0x8 + MULMOD ByteCode = 0x9 + EXP ByteCode = 0xa + SIGNEXTEND ByteCode = 0xb +) + +// 0x10 range - comparison ops. +const ( + LT ByteCode = 0x10 + GT ByteCode = 0x11 + SLT ByteCode = 0x12 + SGT ByteCode = 0x13 + EQ ByteCode = 0x14 + ISZERO ByteCode = 0x15 + AND ByteCode = 0x16 + OR ByteCode = 0x17 + XOR ByteCode = 0x18 + NOT ByteCode = 0x19 + BYTE ByteCode = 0x1a + SHL ByteCode = 0x1b + SHR ByteCode = 0x1c + SAR ByteCode = 0x1d +) + +// 0x20 range - crypto. +const ( + KECCAK256 ByteCode = 0x20 +) + +// 0x30 range - closure state. +const ( + ADDRESS ByteCode = 0x30 + BALANCE ByteCode = 0x31 + ORIGIN ByteCode = 0x32 + CALLER ByteCode = 0x33 + CALLVALUE ByteCode = 0x34 + CALLDATALOAD ByteCode = 0x35 + CALLDATASIZE ByteCode = 0x36 + CALLDATACOPY ByteCode = 0x37 + CODESIZE ByteCode = 0x38 + CODECOPY ByteCode = 0x39 + GASPRICE ByteCode = 0x3a + EXTCODESIZE ByteCode = 0x3b + EXTCODECOPY ByteCode = 0x3c + RETURNDATASIZE ByteCode = 0x3d + RETURNDATACOPY ByteCode = 0x3e + EXTCODEHASH ByteCode = 0x3f +) + +// 0x40 range - block operations. +const ( + BLOCKHASH ByteCode = 0x40 + COINBASE ByteCode = 0x41 + TIMESTAMP ByteCode = 0x42 + NUMBER ByteCode = 0x43 + DIFFICULTY ByteCode = 0x44 + RANDOM ByteCode = 0x44 // Same as DIFFICULTY + PREVRANDAO ByteCode = 0x44 // Same as DIFFICULTY + GASLIMIT ByteCode = 0x45 + CHAINID ByteCode = 0x46 + SELFBALANCE ByteCode = 0x47 + BASEFEE ByteCode = 0x48 +) + +// 0x50 range - 'storage' and execution. +const ( + POP ByteCode = 0x50 + MLOAD ByteCode = 0x51 + MSTORE ByteCode = 0x52 + MSTORE8 ByteCode = 0x53 + SLOAD ByteCode = 0x54 + SSTORE ByteCode = 0x55 + JUMP ByteCode = 0x56 + JUMPI ByteCode = 0x57 + PC ByteCode = 0x58 + MSIZE ByteCode = 0x59 + GAS ByteCode = 0x5a + JUMPDEST ByteCode = 0x5b + PUSH0 ByteCode = 0x5f +) + +// 0x60 range - pushes. +const ( + PUSH1 ByteCode = 0x60 + iota + 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 +) + +// 0x80 range - dups. +const ( + DUP1 = 0x80 + iota + DUP2 + DUP3 + DUP4 + DUP5 + DUP6 + DUP7 + DUP8 + DUP9 + DUP10 + DUP11 + DUP12 + DUP13 + DUP14 + DUP15 + DUP16 +) + +// 0x90 range - swaps. +const ( + SWAP1 = 0x90 + iota + SWAP2 + SWAP3 + SWAP4 + SWAP5 + SWAP6 + SWAP7 + SWAP8 + SWAP9 + SWAP10 + SWAP11 + SWAP12 + SWAP13 + SWAP14 + SWAP15 + SWAP16 +) + +// 0xa0 range - logging ops. +const ( + LOG0 ByteCode = 0xa0 + iota + LOG1 + LOG2 + LOG3 + LOG4 +) + +// 0xd0 range - customized instructions. +const ( + Nop ByteCode = 0xd0 + iota + AndSwap1PopSwap2Swap1 + Swap2Swap1PopJump + Swap1PopSwap2Swap1 + PopSwap2Swap1Pop + Push2Jump + Push2JumpI + Push1Push1 + Push1Add + Push1Shl + Push1Dup1 + Swap1Pop + PopJump + Pop2 + Swap2Swap1 + Swap2Pop + Dup2LT + JumpIfZero // 0xe2 +) + +// 0xf0 range - closures. +const ( + CREATE ByteCode = 0xf0 + CALL ByteCode = 0xf1 + CALLCODE ByteCode = 0xf2 + RETURN ByteCode = 0xf3 + DELEGATECALL ByteCode = 0xf4 + CREATE2 ByteCode = 0xf5 + + STATICCALL ByteCode = 0xfa + REVERT ByteCode = 0xfd + INVALID ByteCode = 0xfe + SELFDESTRUCT ByteCode = 0xff +) + +// 0xb0 range. +const ( + TLOAD ByteCode = 0xb3 + TSTORE ByteCode = 0xb4 +) diff --git a/core/opcodeCompiler/compiler/opcodeProcessor.go b/core/opcodeCompiler/compiler/opcodeProcessor.go new file mode 100644 index 0000000000..511eddaa95 --- /dev/null +++ b/core/opcodeCompiler/compiler/opcodeProcessor.go @@ -0,0 +1,387 @@ +package compiler + +import ( + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" + "runtime" +) + +var ( + enabled bool + codeCache *OpCodeCache + taskChannel chan optimizeTask +) + +var ( + ErrFailPreprocessing = errors.New("fail to do preprocessing") + ErrOptimizedDisabled = errors.New("opcode optimization is disabled") +) + +const taskChannelSize = 1024 * 1024 + +const ( + generate optimizeTaskType = 1 + flush optimizeTaskType = 2 +) + +type OpCodeProcessorConfig struct { + DoOpcodeFusion bool +} + +type optimizeTaskType byte + +type CodeType uint8 + +type optimizeTask struct { + taskType optimizeTaskType + hash common.Hash + rawCode []byte +} + +func init() { + taskChannel = make(chan optimizeTask, taskChannelSize) + taskNumber := runtime.NumCPU() * 3 / 8 + if taskNumber < 1 { + taskNumber = 1 + } + codeCache = getOpCodeCacheInstance() + + for i := 0; i < taskNumber; i++ { + go taskProcessor() + } +} + +func EnableOptimization() { + if enabled { + return + } + enabled = true +} + +func DisableOptimization() { + enabled = false +} + +func LoadOptimizedCode(hash common.Hash) []byte { + if !enabled { + return nil + } + processedCode := codeCache.GetCachedCode(hash) + return processedCode + +} + +func LoadBitvec(codeHash common.Hash) []byte { + if !enabled { + return nil + } + bitvec := codeCache.GetCachedBitvec(codeHash) + return bitvec +} + +func StoreBitvec(codeHash common.Hash, bitvec []byte) { + if !enabled { + return + } + codeCache.AddBitvecCache(codeHash, bitvec) +} + +func GenOrLoadOptimizedCode(hash common.Hash, code []byte) { + if !enabled { + return + } + task := optimizeTask{generate, hash, code} + taskChannel <- task +} + +func taskProcessor() { + for { + task := <-taskChannel + // Process the message here + handleOptimizationTask(task) + } +} + +func handleOptimizationTask(task optimizeTask) { + switch task.taskType { + case generate: + _, err := TryGenerateOptimizedCode(task.hash, task.rawCode) + if err != nil { + log.Error("Can not generate optimized code", "error", err) + } + case flush: + DeleteCodeCache(task.hash) + } +} + +// GenOrRewriteOptimizedCode generate the optimized code and refresh the code cache. +func GenOrRewriteOptimizedCode(hash common.Hash, code []byte) ([]byte, error) { + if !enabled { + return nil, ErrOptimizedDisabled + } + processedCode, err := processByteCodes(code) + if err != nil { + return nil, err + } + codeCache.AddCodeCache(hash, processedCode) + return processedCode, err +} + +func TryGenerateOptimizedCode(hash common.Hash, code []byte) ([]byte, error) { + if !enabled { + return nil, ErrOptimizedDisabled + } + processedCode := codeCache.GetCachedCode(hash) + var err error = nil + if processedCode == nil || len(processedCode) == 0 { + processedCode, err = GenOrRewriteOptimizedCode(hash, code) + } + return processedCode, err +} + +func DeleteCodeCache(hash common.Hash) { + if !enabled { + return + } + // flush in case there are invalid cached code + codeCache.RemoveCachedCode(hash) +} + +func processByteCodes(code []byte) ([]byte, error) { + return doOpcodesProcess(code) +} + +func doOpcodesProcess(code []byte) ([]byte, error) { + code, err := doCodeFusion(code) + if err != nil { + return nil, ErrFailPreprocessing + } + return code, nil +} + +func doCodeFusion(code []byte) ([]byte, error) { + fusedCode := make([]byte, len(code)) + length := copy(fusedCode, code) + skipToNext := false + for i := 0; i < length; i++ { + cur := i + skipToNext = false + + if length > cur+4 { + code0 := ByteCode(fusedCode[cur+0]) + code1 := ByteCode(fusedCode[cur+1]) + code2 := ByteCode(fusedCode[cur+2]) + code3 := ByteCode(fusedCode[cur+3]) + code4 := ByteCode(fusedCode[cur+4]) + if code0 == AND && code1 == SWAP1 && code2 == POP && code3 == SWAP2 && code4 == SWAP1 { + op := AndSwap1PopSwap2Swap1 + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + fusedCode[cur+2] = byte(Nop) + fusedCode[cur+3] = byte(Nop) + fusedCode[cur+4] = byte(Nop) + skipToNext = true + } + + // Test zero and Jump. target offset at code[2-3] + if code0 == ISZERO && code1 == PUSH2 && code4 == JUMPI { + op := JumpIfZero + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + fusedCode[cur+4] = byte(Nop) + + startMin := cur + 2 + endMin := cur + 4 + integer := new(uint256.Int) + integer.SetBytes(common.RightPadBytes( + fusedCode[startMin:endMin], 2)) + + skipToNext = true + } + + if skipToNext { + i += 4 + continue + } + } + + if length > cur+3 { + code0 := ByteCode(fusedCode[cur+0]) + code1 := ByteCode(fusedCode[cur+1]) + code2 := ByteCode(fusedCode[cur+2]) + code3 := ByteCode(fusedCode[cur+3]) + if code0 == SWAP2 && code1 == SWAP1 && code2 == POP && code3 == JUMP { + op := Swap2Swap1PopJump + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + fusedCode[cur+2] = byte(Nop) + fusedCode[cur+3] = byte(Nop) + skipToNext = true + } + + if code0 == SWAP1 && code1 == POP && code2 == SWAP2 && code3 == SWAP1 { + op := Swap1PopSwap2Swap1 + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + fusedCode[cur+2] = byte(Nop) + fusedCode[cur+3] = byte(Nop) + skipToNext = true + } + + if code0 == POP && code1 == SWAP2 && code2 == SWAP1 && code3 == POP { + op := PopSwap2Swap1Pop + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + fusedCode[cur+2] = byte(Nop) + fusedCode[cur+3] = byte(Nop) + skipToNext = true + } + // push and jump + if code0 == PUSH2 && code3 == JUMP { + op := Push2Jump + fusedCode[cur] = byte(op) + fusedCode[cur+3] = byte(Nop) + skipToNext = true + } + + if code0 == PUSH2 && code3 == JUMPI { + op := Push2JumpI + fusedCode[cur] = byte(op) + fusedCode[cur+3] = byte(Nop) + skipToNext = true + } + + if code0 == PUSH1 && code2 == PUSH1 { + op := Push1Push1 + fusedCode[cur] = byte(op) + fusedCode[cur+2] = byte(Nop) + skipToNext = true + } + + if skipToNext { + i += 3 + continue + } + } + + if length > cur+2 { + code0 := ByteCode(fusedCode[cur+0]) + _ = ByteCode(fusedCode[cur+1]) + code2 := ByteCode(fusedCode[cur+2]) + if code0 == PUSH1 { + if code2 == ADD { + op := Push1Add + fusedCode[cur] = byte(op) + fusedCode[cur+2] = byte(Nop) + skipToNext = true + } + if code2 == SHL { + op := Push1Shl + fusedCode[cur] = byte(op) + fusedCode[cur+2] = byte(Nop) + skipToNext = true + } + + if code2 == DUP1 { + op := Push1Dup1 + fusedCode[cur] = byte(op) + fusedCode[cur+2] = byte(Nop) + skipToNext = true + } + + } + if skipToNext { + i += 2 + continue + } + } + + if length > cur+1 { + code0 := ByteCode(fusedCode[cur+0]) + code1 := ByteCode(fusedCode[cur+1]) + + if code0 == SWAP1 && code1 == POP { + op := Swap1Pop + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + if code0 == POP && code1 == JUMP { + op := PopJump + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + + if code0 == POP && code1 == POP { + op := Pop2 + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + + if code0 == SWAP2 && code1 == SWAP1 { + op := Swap2Swap1 + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + + if code0 == SWAP2 && code1 == POP { + op := Swap2Pop + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + + if code0 == DUP2 && code1 == LT { + op := Dup2LT + fusedCode[cur] = byte(op) + fusedCode[cur+1] = byte(Nop) + skipToNext = true + } + + if skipToNext { + i++ + continue + } + } + + skip, steps := calculateSkipSteps(fusedCode, cur) + if skip { + i += steps + continue + } + } + return fusedCode, nil +} + +func calculateSkipSteps(code []byte, cur int) (skip bool, steps int) { + inst := ByteCode(code[cur]) + if inst >= PUSH1 && inst <= PUSH32 { + // skip the data. + steps = int(inst - PUSH1 + 1) + skip = true + return skip, steps + } + + switch inst { + case Push2Jump, Push2JumpI: + steps = 3 + skip = true + case Push1Push1: + steps = 3 + skip = true + case Push1Add, Push1Shl, Push1Dup1: + steps = 2 + skip = true + case JumpIfZero: + steps = 4 + skip = true + default: + return false, 0 + } + return skip, steps +} diff --git a/core/state/state_object.go b/core/state/state_object.go index d42d2c34d8..11fcb01871 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -19,6 +19,7 @@ package state import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "io" "math/big" "time" @@ -511,6 +512,7 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) { s.code = code s.data.CodeHash = codeHash[:] s.dirtyCode = true + compiler.GenOrLoadOptimizedCode(codeHash, s.code) } func (s *stateObject) SetNonce(nonce uint64) { diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 38af9084ac..7677601c95 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -76,6 +76,14 @@ func codeBitmapInternal(code, bits bitvec) bitvec { for pc := uint64(0); pc < uint64(len(code)); { op := OpCode(code[pc]) pc++ + + // handle super instruction. + step, processed := codeBitmapForSI(code, pc, op, &bits) + if processed { + pc += step + continue + } + if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). continue } @@ -116,3 +124,30 @@ func codeBitmapInternal(code, bits bitvec) bitvec { } return bits } + +func codeBitmapForSI(code []byte, pc uint64, op OpCode, bits *bitvec) (step uint64, processed bool) { + // pc points to the data pointer for push, or the next op for opcode + // bits marks the data bytes pointed by [pc] + switch op { + case Push2Jump, Push2JumpI: + bits.setN(set2BitsMask, pc) + step = 3 + processed = true + case Push1Push1: + bits.set1(pc) + bits.set1(pc + 2) + step = 3 + processed = true + case Push1Add, Push1Shl, Push1Dup1: + bits.set1(pc) + step = 2 + processed = true + case JumpIfZero: + bits.setN(set2BitsMask, pc+1) + step = 4 + processed = true + default: + return 0, false + } + return step, processed +} diff --git a/core/vm/contract.go b/core/vm/contract.go index e4b03bd74f..18c81c09de 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -17,10 +17,10 @@ package vm import ( - "math/big" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "github.com/holiman/uint256" + "math/big" ) // ContractRef is a reference to the contract's backing object @@ -58,8 +58,9 @@ type Contract struct { CodeAddr *common.Address Input []byte - Gas uint64 - value *big.Int + Gas uint64 + value *big.Int + optimized bool } // NewContract returns a new contract environment for the execution of EVM. @@ -93,7 +94,10 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool { if OpCode(c.Code[udest]) != JUMPDEST { return false } - return c.isCode(udest) + if c.isCode(udest) { + return true + } + return false } // isCode returns true if the provided PC location is an actual opcode, as @@ -112,8 +116,17 @@ func (c *Contract) isCode(udest uint64) bool { if !exist { // Do the analysis and save in parent context // We do not need to store it in c.analysis - analysis = codeBitmap(c.Code) - c.jumpdests[c.CodeHash] = analysis + if c.optimized { + analysis = compiler.LoadBitvec(c.CodeHash) + if analysis == nil { + analysis = codeBitmap(c.Code) + compiler.StoreBitvec(c.CodeHash, analysis) + } + c.jumpdests[c.CodeHash] = analysis + } else { + analysis = codeBitmap(c.Code) + c.jumpdests[c.CodeHash] = analysis + } } // Also stash it in current contract for faster access c.analysis = analysis diff --git a/core/vm/errors.go b/core/vm/errors.go index fbbf19e178..ac9a296300 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,7 +37,6 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") - // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. errStopToken = errors.New("stop token") diff --git a/core/vm/evm.go b/core/vm/evm.go index 2e69a3349d..a7c3062613 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "math/big" "sync/atomic" @@ -139,6 +140,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } evm.interpreter = NewEVMInterpreter(evm) + if config.EnableOpcodeOptimizations { + compiler.EnableOptimization() + } return evm } @@ -186,6 +190,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas 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) debug := evm.Config.Tracer != nil @@ -229,15 +234,20 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, 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. + // try get from cache code := evm.StateDB.GetCode(addr) if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { addrCopy := addr + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + + codeHash := evm.StateDB.GetCodeHash(addrCopy) + contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) // 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) + contract.SetCallCode(&addrCopy, codeHash, code) + ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -294,7 +304,11 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // 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)) + // try get from cache + code := evm.StateDB.GetCode(addr) + codeHash := evm.StateDB.GetCodeHash(addrCopy) + contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) + contract.SetCallCode(&addrCopy, codeHash, code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -338,7 +352,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by 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)) + code := evm.StateDB.GetCode(addr) + codeHash := evm.StateDB.GetCodeHash(addrCopy) + contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) + contract.SetCallCode(&addrCopy, codeHash, code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -356,6 +373,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // 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) { + // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -391,7 +409,11 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // 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)) + // Try get optimized code + code := evm.StateDB.GetCode(addr) + codeHash := evm.StateDB.GetCodeHash(addrCopy) + contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) + contract.SetCallCode(&addrCopy, codeHash, code) // 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. @@ -407,6 +429,22 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } +func tryGetOptimizedCode(evm *EVM, codeHash common.Hash, rawCode []byte) (bool, []byte) { + var code []byte + optimized := false + code = rawCode + if evm.Config.EnableOpcodeOptimizations { + optCode := compiler.LoadOptimizedCode(codeHash) + if len(optCode) != 0 { + code = optCode + optimized = true + } else { + compiler.GenOrLoadOptimizedCode(codeHash, rawCode) + } + } + return optimized, code +} + type codeAndHash struct { code []byte hash common.Hash @@ -464,9 +502,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) } } - + // We don't optimize creation code as it run only once and may cause code cache issue. + contract.optimized = false + if evm.Config.EnableOpcodeOptimizations { + compiler.DisableOptimization() + } ret, err := evm.interpreter.Run(contract, nil, false) + // After creation, retrieve to optimization + if evm.Config.EnableOpcodeOptimizations { + compiler.EnableOptimization() + } + // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { err = ErrMaxCodeSizeExceeded diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 4a5259a262..b7ada331bd 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -149,7 +149,6 @@ func TestCreateGas(t *testing.T) { if tt.eip3860 { config.ExtraEips = []int{3860} } - vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, config) var startGas = uint64(testGas) ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int)) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 56ff350201..93b895e2c8 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -169,7 +169,8 @@ func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() + x, y := scope.Stack.pop2() + z := scope.Stack.peek() if z.IsZero() { z.Clear() } else { @@ -179,7 +180,8 @@ func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() + x, y := scope.Stack.pop2() + z := scope.Stack.peek() z.MulMod(&x, &y, z) return nil, nil } @@ -190,11 +192,13 @@ func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.LtUint64(256) { value.Lsh(value, uint(shift.Uint64())) } else { value.Clear() } + return nil, nil } @@ -237,11 +241,13 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if interpreter.hasher == nil { interpreter.hasher = crypto.NewKeccakState() - } else { + } /* else { interpreter.hasher.Reset() - } - interpreter.hasher.Write(data) - interpreter.hasher.Read(interpreter.hasherBuf[:]) + }*/ + interpreter.hasherBuf = crypto.HashData(interpreter.hasher, data) + + //interpreter.hasher.Write(data) + // interpreter.hasher.Read(interpreter.hasherBuf[:]) evm := interpreter.evm if evm.Config.EnablePreimageRecording { @@ -298,9 +304,8 @@ func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = scope.Stack.pop() - dataOffset = scope.Stack.pop() - length = scope.Stack.pop() + memOffset, dataOffset = scope.Stack.pop2() + length = scope.Stack.pop() ) dataOffset64, overflow := dataOffset.Uint64WithOverflow() if overflow { @@ -321,9 +326,8 @@ func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = scope.Stack.pop() - dataOffset = scope.Stack.pop() - length = scope.Stack.pop() + memOffset, dataOffset = scope.Stack.pop2() + length = scope.Stack.pop() ) offset64, overflow := dataOffset.Uint64WithOverflow() @@ -356,15 +360,20 @@ func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = scope.Stack.pop() - codeOffset = scope.Stack.pop() - length = scope.Stack.pop() + memOffset, codeOffset = scope.Stack.pop2() + length = scope.Stack.pop() ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } - codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + + contractRawCode := scope.Contract.Code + + if interpreter.evm.Config.EnableOpcodeOptimizations && scope.Contract.optimized { + contractRawCode = interpreter.evm.StateDB.GetCode(*scope.Contract.CodeAddr) + } + codeCopy := getData(contractRawCode, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil @@ -501,13 +510,13 @@ func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // pop value of the stack - mStart, val := scope.Stack.pop(), scope.Stack.pop() + mStart, val := scope.Stack.pop2() scope.Memory.Set32(mStart.Uint64(), &val) return nil, nil } func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - off, val := scope.Stack.pop(), scope.Stack.pop() + off, val := scope.Stack.pop2() scope.Memory.store[off.Uint64()] = byte(val.Uint64()) return nil, nil } @@ -524,8 +533,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.readOnly { return nil, ErrWriteProtection } - loc := scope.Stack.pop() - val := scope.Stack.pop() + loc, val := scope.Stack.pop2() interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) return nil, nil } @@ -546,7 +554,8 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by if interpreter.evm.abort.Load() { return nil, errStopToken } - pos, cond := scope.Stack.pop(), scope.Stack.pop() + pos, cond := scope.Stack.pop2() + if !cond.IsZero() { if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump @@ -580,10 +589,10 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b return nil, ErrWriteProtection } var ( - value = scope.Stack.pop() - offset, size = scope.Stack.pop(), scope.Stack.pop() - input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = scope.Contract.Gas + value, offset = scope.Stack.pop2() + size = scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas ) if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 @@ -626,11 +635,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, ErrWriteProtection } var ( - endowment = scope.Stack.pop() - offset, size = scope.Stack.pop(), scope.Stack.pop() - salt = scope.Stack.pop() - input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = scope.Contract.Gas + endowment, offset = scope.Stack.pop2() + size, salt = scope.Stack.pop2() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas ) // Apply EIP150 gas -= gas / 64 @@ -750,6 +758,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + if err != nil { temp.Clear() } else { @@ -794,14 +803,14 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - offset, size := scope.Stack.pop(), scope.Stack.pop() + offset, size := scope.Stack.pop2() ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) return ret, errStopToken } func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - offset, size := scope.Stack.pop(), scope.Stack.pop() + offset, size := scope.Stack.pop2() ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) interpreter.returnData = ret @@ -889,6 +898,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } else { scope.Stack.push(integer.Clear()) } + return nil, nil } @@ -933,3 +943,280 @@ func makeSwap(size int64) executionFunc { return nil, nil } } + +// fused instructions +// opAndSwap1PopSwap2Swap1 implements the fused instruction of And and `move to the stack bottom`. +func opAndSwap1PopSwap2Swap1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + a, b := scope.Stack.pop2() + c, d, e := scope.Stack.peek(), scope.Stack.Back(1), scope.Stack.Back(2) + r := a.And(&a, &b) + tmp := *e + *e = *r + *c = *d + *d = tmp + *pc += 4 + return nil, nil +} + +// opSwap2Swap1PopJump +func opSwap2Swap1PopJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + a, _ := scope.Stack.pop2() + c := scope.Stack.peek() + dest := *c + *c = a + if !scope.Contract.validJumpdest(&dest) { + return nil, ErrInvalidJump + } + *pc = dest.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opSwap1PopSwap2Swap1 +func opSwap1PopSwap2Swap1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + a, _ := scope.Stack.pop2() + c, d := scope.Stack.pop(), scope.Stack.peek() + scope.Stack.push(d) + *d = a + scope.Stack.push(&c) + *pc += 3 // pc will be increased by the interpreter loop + return nil, nil +} + +// opPopSwap2Swap1Pop +func opPopSwap2Swap1Pop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + _, b := scope.Stack.pop2() + _, d := scope.Stack.pop(), scope.Stack.peek() + scope.Stack.push(d) + *d = b + *pc += 3 // pc will be increased by the interpreter loop + return nil, nil +} + +// opPush2Jump +func opPush2Jump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = len(scope.Contract.Code) + integer = new(uint256.Int) + pos = integer.Clear() + ) + + startMin := codeLen + if int(*pc+1) < startMin { + startMin = int(*pc + 1) + } + + endMin := codeLen + if startMin+2 < endMin { + endMin = startMin + 2 + } + + pos = integer.SetBytes(common.RightPadBytes( + scope.Contract.Code[startMin:endMin], 2)) + + if !scope.Contract.validJumpdest(pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opPush2JumpI +func opPush2JumpI(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = len(scope.Contract.Code) + integer = new(uint256.Int) + pos = integer.Clear() + ) + + startMin := codeLen + if int(*pc+1) < startMin { + startMin = int(*pc + 1) + } + + endMin := codeLen + if startMin+2 < endMin { + endMin = startMin + 2 + } + + pos = integer.SetBytes(common.RightPadBytes( + scope.Contract.Code[startMin:endMin], 2)) + + cond := scope.Stack.pop() + if !cond.IsZero() { + if !scope.Contract.validJumpdest(pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + } else { + *pc += 3 + } + return nil, nil +} + +// opPush1Push1 +func opPush1Push1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + a, b = new(uint256.Int), new(uint256.Int) + ) + *pc += 3 + if *pc < codeLen { + a = a.SetUint64(uint64(scope.Contract.Code[*pc-2])) + b = b.SetUint64(uint64(scope.Contract.Code[*pc])) + } + scope.Stack.push2(a, b) + //scope.Stack.push(b) + return nil, nil +} + +// opPush1Add +func opPush1Add(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + a = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + a = a.SetUint64(uint64(scope.Contract.Code[*pc])) + } + b := scope.Stack.pop() + scope.Stack.push(b.Add(a, &b)) + *pc += 1 + return nil, nil +} + +// opPush1Shl +func opPush1Shl(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + shift = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + shift = shift.SetUint64(uint64(scope.Contract.Code[*pc])) + } else { + shift = shift.Clear() + } + value := scope.Stack.peek() + + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() + } + *pc += 1 + return nil, nil +} + +// opPush1Dup1 +func opPush1Dup1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + value = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + value = value.SetUint64(uint64(scope.Contract.Code[*pc])) + } + + scope.Stack.push2(value, value) + //scope.Stack.push(value) + *pc += 1 + return nil, nil +} + +// opSwap1Pop +func opSwap1Pop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + a, b := scope.Stack.pop(), scope.Stack.peek() + *b = a + *pc += 1 + return nil, nil +} + +// opPopJump +func opPopJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + _, pos := scope.Stack.pop2() + if !scope.Contract.validJumpdest(&pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opPop2 +func opPop2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + _, _ = scope.Stack.pop2() + *pc += 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opSwap2Swap1 +func opSwap2Swap1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + a, b, c := *scope.Stack.peek(), *scope.Stack.Back(1), *scope.Stack.Back(2) + // to b, c, a + *scope.Stack.peek() = b + *scope.Stack.Back(1) = c + *scope.Stack.Back(2) = a + *pc += 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opSwap2Pop +func opSwap2Pop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // a,b,c -> b,a + a := scope.Stack.peek() + *scope.Stack.Back(2) = *a + scope.Stack.pop() + *pc += 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opDup2Lt +func opDup2LT(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + y := scope.Stack.Back(1) + if y.Lt(x) { + x.SetOne() + } else { + x.Clear() + } + *pc += 1 // pc will be increased by the interpreter loop + return nil, nil +} + +// opJumpIfZero +func opJumpIfZero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + value := scope.Stack.pop() + integer := new(uint256.Int) + pos := new(uint256.Int) + + if value.IsZero() { + codeLen := len(scope.Contract.Code) + *pc += 2 + startMin := codeLen + if int(*pc) < startMin { + startMin = int(*pc) + } + + endMin := codeLen + if startMin+2 < endMin { + endMin = startMin + 2 + } + + pos = integer.SetBytes(common.RightPadBytes( + scope.Contract.Code[startMin:endMin], 2)) + + if !scope.Contract.validJumpdest(pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + } else { + *pc += 4 + } + return nil, nil +} + +// opNop has no behavior. +func opNop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, nil +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 28da2e80e6..8612e2c6a4 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -25,10 +25,11 @@ import ( // Config are the configuration options for the Interpreter type Config struct { - Tracer EVMLogger // Opcode logger - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - ExtraEips []int // Additional EIPS that are to be enabled + Tracer EVMLogger // Opcode logger + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled + EnableOpcodeOptimizations bool // Disable optimization of opcode. } // ScopeContext contains the things that are per-call, such as stack and memory, @@ -108,7 +109,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // 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 { @@ -165,6 +165,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } }() } + // 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 @@ -226,6 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) logged = true } + // execute the operation res, err = operation.execute(&pc, in, callContext) if err != nil { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index fb87258326..1068510089 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -174,6 +174,21 @@ func newConstantinopleInstructionSet() JumpTable { maxStack: maxStack(4, 1), memorySize: memoryCreate2, } + + instructionSet[Nop] = &operation{ + execute: opNop, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + instructionSet[Push1Shl] = &operation{ + execute: opPush1Shl, + constantGas: 2 * GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + return validate(instructionSet) } @@ -1054,6 +1069,120 @@ func newFrontierInstructionSet() JumpTable { }, } + // TODO-dav: lower the gas fee. + // super instructions + tbl[AndSwap1PopSwap2Swap1] = &operation{ + execute: opAndSwap1PopSwap2Swap1, + constantGas: 4*GasFastestStep + GasQuickStep, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } + + tbl[Swap2Swap1PopJump] = &operation{ + execute: opSwap2Swap1PopJump, + constantGas: 2*GasFastestStep + GasQuickStep + GasMidStep, + minStack: minStack(3, 3), + maxStack: maxStack(3, 3), + } + + tbl[Swap1PopSwap2Swap1] = &operation{ + execute: opSwap1PopSwap2Swap1, + constantGas: 3*GasFastestStep + GasQuickStep, + minStack: minStack(4, 4), + maxStack: maxStack(4, 4), + } + + tbl[PopSwap2Swap1Pop] = &operation{ + execute: opPopSwap2Swap1Pop, + constantGas: 2*GasFastestStep + 2*GasQuickStep, + minStack: minStack(4, 4), + maxStack: maxStack(4, 4), + } + + tbl[Push2Jump] = &operation{ + execute: opPush2Jump, + constantGas: GasFastestStep + GasMidStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + tbl[Push2JumpI] = &operation{ + execute: opPush2JumpI, + constantGas: GasFastestStep + GasSlowStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + + tbl[Push1Push1] = &operation{ + execute: opPush1Push1, + constantGas: 2 * GasFastestStep, + minStack: minStack(0, 2), + maxStack: maxStack(0, 2), + } + + tbl[Push1Add] = &operation{ + execute: opPush1Add, + constantGas: 2 * GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + tbl[Push1Dup1] = &operation{ + execute: opPush1Dup1, + constantGas: 2 * GasFastestStep, + minStack: minStack(0, 2), + maxStack: maxStack(0, 2), + } + + tbl[Swap1Pop] = &operation{ + execute: opSwap1Pop, + constantGas: GasFastestStep + GasQuickStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + + tbl[PopJump] = &operation{ + execute: opPopJump, + constantGas: GasQuickStep + GasMidStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + + tbl[Pop2] = &operation{ + execute: opPop2, + constantGas: 2 * GasQuickStep, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } + + tbl[Swap2Swap1] = &operation{ + execute: opSwap2Swap1, + constantGas: 2 * GasFastestStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + tbl[Swap2Pop] = &operation{ + execute: opSwap2Pop, + constantGas: GasFastestStep + GasQuickStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + tbl[Dup2LT] = &operation{ + execute: opDup2LT, + constantGas: 2 * GasFastestStep, + minStack: minStack(2, 2), + maxStack: maxStack(2, 2), + } + + tbl[JumpIfZero] = &operation{ + execute: opJumpIfZero, + constantGas: 2*GasFastestStep + GasSlowStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + // Fill all unassigned slots with opUndefined. for i, entry := range tbl { if entry == nil { diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index a11cf05a15..567884b0bf 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -209,6 +209,28 @@ const ( LOG4 ) +// 0xd0 range - customized instructions. +const ( + Nop OpCode = 0xd0 + iota + AndSwap1PopSwap2Swap1 + Swap2Swap1PopJump + Swap1PopSwap2Swap1 + PopSwap2Swap1Pop + Push2Jump + Push2JumpI + Push1Push1 + Push1Add + Push1Shl + Push1Dup1 + Swap1Pop + PopJump + Pop2 + Swap2Swap1 + Swap2Pop + Dup2LT + JumpIfZero // 0xe2 +) + // 0xf0 range - closures. const ( CREATE OpCode = 0xf0 @@ -385,6 +407,26 @@ var opCodeToString = map[OpCode]string{ LOG3: "LOG3", LOG4: "LOG4", + // 0xd0 range. + Nop: "NOP", + AndSwap1PopSwap2Swap1: "ANDSWAP1POPSWAP2SWAP1", + Swap2Swap1PopJump: "SWAP2SWAP1POPJUMP", + Swap1PopSwap2Swap1: "SWAP1POPSWAP2SWAP1", + PopSwap2Swap1Pop: "POPSWAP2SWAP1POP", + Push2Jump: "PUSH2JUMP", + Push2JumpI: "PUSH2JUMPI", + Push1Push1: "PUSH1PUSH1", + Push1Add: "PUSH1ADD", + Push1Shl: "PUSH1SHL", + Push1Dup1: "PUSH1DUP1", + Swap1Pop: "SWAP1POP", + PopJump: "POPJUMP", + Pop2: "POP2", + Swap2Swap1: "SWAP2SWAP1", + Swap2Pop: "SWAP2POP", + Dup2LT: "DUP2LT", + JumpIfZero: "JUMPIFZERO", + // 0xf0 range - closures. CREATE: "CREATE", CALL: "CALL", @@ -549,14 +591,33 @@ var stringToOp = map[string]OpCode{ "LOG2": LOG2, "LOG3": LOG3, "LOG4": LOG4, - "CREATE": CREATE, - "CREATE2": CREATE2, - "CALL": CALL, - "RETURN": RETURN, - "CALLCODE": CALLCODE, - "REVERT": REVERT, - "INVALID": INVALID, - "SELFDESTRUCT": SELFDESTRUCT, + + "NOP": Nop, + "ANDSWAP1POPSWAP2SWAP1": AndSwap1PopSwap2Swap1, + "SWAP2SWAP1POPJUMP": Swap2Swap1PopJump, + "SWAP1POPSWAP2SWAP1": Swap1PopSwap2Swap1, + "POPSWAP2SWAP1POP": PopSwap2Swap1Pop, + "PUSH2JUMP": Push2Jump, + "PUSH2JUMPI": Push2JumpI, + "PUSH1PUSH1": Push1Push1, + "PUSH1ADD": Push1Add, + "PUSH1SHL": Push1Shl, + "PUSH1DUP1": Push1Dup1, + "SWAP1POP": Swap1Pop, + "POPJUMP": PopJump, + "POP2": Pop2, + "SWAP2SWAP1": Swap2Swap1, + "SWAP2POP": Swap2Pop, + "DUP2LT": Dup2LT, + "JUMPIFZERO": JumpIfZero, + "CREATE": CREATE, + "CREATE2": CREATE2, + "CALL": CALL, + "RETURN": RETURN, + "CALLCODE": CALLCODE, + "REVERT": REVERT, + "INVALID": INVALID, + "SELFDESTRUCT": SELFDESTRUCT, } // StringToOp finds the opcode whose name is stored in `str`. diff --git a/core/vm/runtime/runtime_example_test.go b/core/vm/runtime/runtime_example_test.go index b7d0ddc384..88f48f4551 100644 --- a/core/vm/runtime/runtime_example_test.go +++ b/core/vm/runtime/runtime_example_test.go @@ -18,13 +18,14 @@ package runtime_test import ( "fmt" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm/runtime" ) func ExampleExecute() { - ret, _, err := runtime.Execute(common.Hex2Bytes("6060604052600a8060106000396000f360606040526008565b00"), nil, nil) + ret, _, err := runtime.Execute(common.Hex2Bytes("6060604052600a8060106000396000f360606040526008565b00"), nil, &runtime.Config{EVMConfig: vm.Config{EnableOpcodeOptimizations: false}}) if err != nil { fmt.Println(err) } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 796d3b4434..f017b5e095 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -18,16 +18,12 @@ package runtime import ( "fmt" - "math/big" - "os" - "strings" - "testing" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/asm" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -35,6 +31,11 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/params" + "math/big" + "os" + "strings" + "testing" + "time" // force-load js tracers to trigger registration _ "github.com/ethereum/go-ethereum/eth/tracers/js" @@ -663,7 +664,8 @@ func TestColdAccountAccessCost(t *testing.T) { tracer := logger.NewStructLogger(nil) Execute(tc.code, nil, &Config{ EVMConfig: vm.Config{ - Tracer: tracer, + Tracer: tracer, + EnableOpcodeOptimizations: false, }, }) have := tracer.StructLogs()[tc.step].GasCost @@ -834,7 +836,185 @@ func TestRuntimeJSTracer(t *testing.T) { GasLimit: 1000000, State: statedb, EVMConfig: vm.Config{ - Tracer: tracer, + Tracer: tracer, + EnableOpcodeOptimizations: false, + }}) + if err != nil { + t.Fatal("didn't expect error", err) + } + res, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + if have, want := string(res), tc.results[i]; have != want { + t.Errorf("wrong result for tracer %d testcase %d, have \n%v\nwant\n%v\n", i, j, have, want) + } + } + } +} + +func TestRuntimeJSTracerWithOpcodeOptimizer(t *testing.T) { + jsTracers := []string{ + `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, + step: function() { this.steps}, + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters; + this.enterGas = frame.getGas(); + }, + exit: function(res) { + this.exits; + this.gasUsed = res.getGasUsed(); + }}`, + `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters; + this.enterGas = frame.getGas(); + }, + exit: function(res) { + this.exits; + this.gasUsed = res.getGasUsed(); + }}`} + tests := []struct { + code []byte + // One result per tracer + results []string + }{ + { + // CREATE + code: []byte{ + // Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) + byte(vm.PUSH5), + // Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + // length, offset, value + byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0, + byte(vm.CREATE), + byte(vm.POP), + }, + results: []string{`"1,1,952855,6,11"`, `"1,1,952855,6,0"`}, + }, + { + // CREATE2 + code: []byte{ + // Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) + byte(vm.PUSH5), + // Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + // salt, length, offset, value + byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0, + byte(vm.CREATE2), + byte(vm.POP), + }, + results: []string{`"1,1,952846,6,11"`, `"1,1,952846,6,0"`}, + }, + { + // CALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xbb, //address + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), + }, + results: []string{`"1,1,981796,6,9"`, `"1,1,981796,6,0"`}, + }, + { + // CALLCODE + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xcc, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + }, + results: []string{`"1,1,981796,6,9"`, `"1,1,981796,6,0"`}, + }, + { + // STATICCALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0xdd, //address + byte(vm.GAS), // gas + byte(vm.STATICCALL), + byte(vm.POP), + }, + results: []string{`"1,1,981799,6,9"`, `"1,1,981799,6,0"`}, + }, + { + // DELEGATECALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0xee, //address + byte(vm.GAS), // gas + byte(vm.DELEGATECALL), + byte(vm.POP), + }, + results: []string{`"1,1,981799,6,9"`, `"1,1,981799,6,0"`}, + }, + { + // CALL self-destructing contract + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xff, //address + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), + }, + results: []string{`"2,2,0,5003,9"`, `"2,2,0,5003,0"`}, + }, + } + calleeCode := []byte{ + byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, + byte(vm.RETURN), + } + depressedCode := []byte{ + byte(vm.PUSH1), 0xaa, + byte(vm.SELFDESTRUCT), + } + main := common.HexToAddress("0xaa") + compiler.EnableOptimization() + for i, jsTracer := range jsTracers { + for j, tc := range tests { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.SetCode(main, tc.code) + statedb.SetCode(common.HexToAddress("0xbb"), calleeCode) + statedb.SetCode(common.HexToAddress("0xcc"), calleeCode) + statedb.SetCode(common.HexToAddress("0xdd"), calleeCode) + statedb.SetCode(common.HexToAddress("0xee"), calleeCode) + statedb.SetCode(common.HexToAddress("0xff"), depressedCode) + /* wait for optimized code to be generated */ + time.Sleep(time.Second) + tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) + if err != nil { + t.Fatal(err) + } + _, _, err = Call(main, nil, &Config{ + GasLimit: 1000000, + State: statedb, + EVMConfig: vm.Config{ + Tracer: tracer, + EnableOpcodeOptimizations: true, }}) if err != nil { t.Fatal("didn't expect error", err) diff --git a/core/vm/stack.go b/core/vm/stack.go index e1a957e244..6c94eeb47f 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -54,12 +54,26 @@ func (st *Stack) push(d *uint256.Int) { st.data = append(st.data, *d) } +// push push to tos +func (st *Stack) push2(a *uint256.Int, b *uint256.Int) { + // NOTE push limit (1024) is checked in baseCheck + st.data = append(st.data, *a, *b) +} + +// pop the top most elem in stack or cache func (st *Stack) pop() (ret uint256.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return } +// pop the top most elem in stack or cache +func (st *Stack) pop2() (ret uint256.Int, ret1 uint256.Int) { + ret, ret1 = st.data[len(st.data)-1], st.data[len(st.data)-2] + st.data = st.data[:len(st.data)-2] + return +} + func (st *Stack) len() int { return len(st.data) } diff --git a/crypto/crypto.go b/crypto/crypto.go index 2492165d38..78d9fb985e 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -112,7 +112,8 @@ func CreateAddress(b common.Address, nonce uint64) common.Address { // CreateAddress2 creates an ethereum address given the address bytes, initial // contract code hash and a salt. func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address { - return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:]) + addr := common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:]) + return addr } // ToECDSA creates a private key with the given D value. diff --git a/eth/backend.go b/eth/backend.go index 51fa757be5..b989803f17 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -193,7 +193,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } var ( vmConfig = vm.Config{ - EnablePreimageRecording: config.EnablePreimageRecording, + EnablePreimageRecording: config.EnablePreimageRecording, + EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 7f31961a9c..077ffd6e1b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -59,26 +59,27 @@ var LightClientGPO = gasprice.Config{ // Defaults contains default settings for use on the Ethereum main net. var Defaults = Config{ - SyncMode: downloader.SnapSync, - NetworkId: 1, - TxLookupLimit: 2350000, - TransactionHistory: 2350000, - StateHistory: params.FullImmutabilityThreshold, - LightPeers: 100, - DatabaseCache: 512, - TrieCleanCache: 154, - TrieDirtyCache: 256, - TrieTimeout: 60 * time.Minute, - TrieCommitInterval: 0, - SnapshotCache: 102, - FilterLogCacheSize: 32, - Miner: miner.DefaultConfig, - TxPool: legacypool.DefaultConfig, - BlobPool: blobpool.DefaultConfig, - RPCGasCap: 50000000, - RPCEVMTimeout: 5 * time.Second, - GPO: FullNodeGPO, - RPCTxFeeCap: 1, // 1 ether + SyncMode: downloader.SnapSync, + NetworkId: 1, + TxLookupLimit: 2350000, + TransactionHistory: 2350000, + StateHistory: params.FullImmutabilityThreshold, + LightPeers: 100, + DatabaseCache: 512, + TrieCleanCache: 154, + TrieDirtyCache: 256, + TrieTimeout: 60 * time.Minute, + TrieCommitInterval: 0, + SnapshotCache: 102, + FilterLogCacheSize: 32, + Miner: miner.DefaultConfig, + TxPool: legacypool.DefaultConfig, + BlobPool: blobpool.DefaultConfig, + RPCGasCap: 50000000, + RPCEVMTimeout: 5 * time.Second, + GPO: FullNodeGPO, + RPCTxFeeCap: 1, // 1 ether + EnableOpcodeOptimizing: false, } //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go @@ -184,6 +185,8 @@ type Config struct { RollupDisableTxPoolGossip bool RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + + EnableOpcodeOptimizing bool } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 6df49a90c1..9d6d40c442 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -102,6 +102,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if !strings.HasSuffix(file.Name(), ".json") { continue } + file := file // capture range variable t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index bf6427faf6..eaa759ba71 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -19,6 +19,7 @@ package js import ( "encoding/json" "errors" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "math/big" "strings" "testing" @@ -61,8 +62,16 @@ func testCtx() *vmContext { } func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { + return runTraceWithOption(tracer, vmctx, chaincfg, contractCode, false) +} + +func runTraceWithOptiEnabled(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { + return runTraceWithOption(tracer, vmctx, chaincfg, contractCode, true) +} + +func runTraceWithOption(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte, enableOpti bool) (json.RawMessage, error) { var ( - env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer}) + env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer, EnableOpcodeOptimizations: enableOpti}) gasLimit uint64 = 31000 startGas uint64 = 10000 value = big.NewInt(0) @@ -73,6 +82,13 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon contract.Code = contractCode } + if enableOpti { + // reset the code also require flush code cache. + compiler.DeleteCodeCache(contract.CodeHash) + optimized, _ := compiler.GenOrRewriteOptimizedCode(contract.CodeHash, contract.Code) + contract.Code = optimized + } + tracer.CaptureTxStart(gasLimit) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) ret, err := env.Interpreter().Run(contract, []byte{}, false) @@ -160,6 +176,81 @@ func TestTracer(t *testing.T) { } } +func TestTracerWithOptimization(t *testing.T) { + execTracer := func(code string, contract []byte) ([]byte, string) { + t.Helper() + tracer, err := newJsTracer(code, nil, nil) + if err != nil { + t.Fatal(err) + } + ret, err := runTraceWithOptiEnabled(tracer, testCtx(), params.TestChainConfig, contract) + if err != nil { + return nil, err.Error() // Stringify to allow comparison without nil checks + } + return ret, "" + } + for i, tt := range []struct { + code string + want string + fail string + contract []byte + }{ + { // tests that we don't panic on bad arguments to memory access + code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound memory: offset -1, end -2 at step (:1:53(13)) in server-side tracer function 'step'", + }, { // tests that we don't panic on bad arguments to stack peeks + code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound stack: size 0, index -1 at step (:1:53(11)) in server-side tracer function 'step'", + }, { // tests that we don't panic on bad arguments to memory getUint + code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound memory: available 0, offset -64, size 32 at step (:1:58(11)) in server-side tracer function 'step'", + }, { // tests some general counting + code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", + want: `2`, + }, { // tests that depth is reported correctly + code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[0,2]`, + }, { // tests memory length + code: "{lengths: [], step: function(log) { this.lengths.push(log.memory.length()); }, fault: function() {}, result: function() { return this.lengths; }}", + want: `[0,0]`, + }, { // tests to-string of opcodes + code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", + want: `["PUSH1PUSH1","STOP"]`, + }, { // tests gasUsed + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed; }}", + want: `"100000.21006"`, + }, { + code: "{res: null, step: function(log) {}, fault: function() {}, result: function() { return toWord('0xffaa') }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":255,"31":170}`, + }, { // test feeding a buffer back into go + code: "{res: null, step: function(log) { var address = log.contract.getAddress(); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: null, step: function(log) { var address = '0x0000000000000000000000000000000000000000'; this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: null, step: function(log) { var address = Array.prototype.slice.call(log.contract.getAddress()); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: [], step: function(log) { var op = log.op.toString(); if (op === 'MSTORE8' || op === 'STOP') { this.res.push(log.memory.slice(0, 2)) } }, fault: function() {}, result: function() { return this.res }}", + want: `[{"0":0,"1":0},{"0":255,"1":0}]`, + contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)}, + }, { + code: "{res: [], step: function(log) { if (log.op.toString() === 'STOP') { this.res.push(log.memory.slice(5, 1025 * 1024)) } }, fault: function() {}, result: function() { return this.res }}", + want: "", + fail: "tracer reached limit for padding memory slice: end 1049600, memorySize 32 at step (:1:83(20)) in server-side tracer function 'step'", + contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)}, + }, + } { + if have, err := execTracer(tt.code, tt.contract); tt.want != string(have) || tt.fail != err { + t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code) + } + } +} + func TestHalt(t *testing.T) { timeout := errors.New("stahp") tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil) diff --git a/go.mod b/go.mod index 0b7be66d06..6e032192ca 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/ethereum/go-ethereum -go 1.20 +go 1.21 + +toolchain go1.21.4 require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 diff --git a/go.sum b/go.sum index ada56d58cf..aee9afc2d4 100644 --- a/go.sum +++ b/go.sum @@ -51,9 +51,11 @@ github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuI github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= @@ -68,10 +70,13 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -189,6 +194,7 @@ github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= +github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -234,6 +240,7 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= @@ -248,6 +255,7 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= +github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= @@ -263,6 +271,7 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -274,6 +283,7 @@ github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80/go.mod h1:gzbV github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= +github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -292,6 +302,7 @@ github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14y github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= @@ -303,6 +314,7 @@ github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlN github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018/go.mod h1:MIonLggsKgZLUSt414ExgwNtlOL5MuEoAJP514mwGe8= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= @@ -317,6 +329,7 @@ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwu github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -410,6 +423,7 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclK github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -522,6 +536,7 @@ github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85q github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -552,6 +567,7 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -607,6 +623,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1/go.mod h1:oVMjMN64nzEcepv1kdZKg github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -619,6 +636,7 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -740,6 +758,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joonix/log v0.0.0-20200409080653-9c1d2ceb5f1d/go.mod h1:fS54ONkjDV71zS9CDx3V9K21gJg7byKSvI4ajuWFNJw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -823,6 +842,7 @@ github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= @@ -1005,6 +1025,7 @@ github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjK github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= @@ -1132,6 +1153,7 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= @@ -1170,6 +1192,7 @@ github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1182,6 +1205,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -1453,6 +1477,7 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -1961,7 +1986,9 @@ google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=