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..159017a490 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -224,7 +224,8 @@ func runCmd(ctx *cli.Context) error { BlobHashes: blobHashes, BlobBaseFee: blobBaseFee, EVMConfig: vm.Config{ - Tracer: tracer, + Tracer: tracer, + EnableOpcodeOptimizations: ctx.Bool(VMOpcodeOptimizeFlag.Name), }, } 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..aecd4492b4 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,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { + cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) + compiler.EnableOptimization() + } + if ctx.IsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) } @@ -2387,8 +2400,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..1e13862d48 --- /dev/null +++ b/core/opcodeCompiler/compiler/opcodeProcessor.go @@ -0,0 +1,384 @@ +package compiler + +import ( + "errors" + "github.com/ethereum/go-ethereum/common" + "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 IsEnabled() bool { + return enabled +} + +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: + TryGenerateOptimizedCode(task.hash, task.rawCode) + 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) { + 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..9399c7be3f 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. + 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..a06fbb1390 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 // Enable opcode optimization } // ScopeContext contains the things that are per-call, such as stack and memory, @@ -41,9 +42,8 @@ type ScopeContext struct { // EVMInterpreter represents an EVM interpreter type EVMInterpreter struct { - evm *EVM - table *JumpTable - + evm *EVM + table *JumpTable hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes @@ -94,7 +94,11 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { extraEips = append(extraEips, eip) } } + evm.Config.ExtraEips = extraEips + if evm.Config.EnableOpcodeOptimizations { + table = createOptimizedOpcodeTable(table) + } return &EVMInterpreter{evm: evm, table: table} } @@ -108,7 +112,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 +168,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 +230,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..d8f30eb880 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -174,6 +174,7 @@ func newConstantinopleInstructionSet() JumpTable { maxStack: maxStack(4, 1), memorySize: memoryCreate2, } + return validate(instructionSet) } @@ -1074,3 +1075,134 @@ func copyJumpTable(source *JumpTable) *JumpTable { } return &dest } + +func createOptimizedOpcodeTable(tbl *JumpTable) *JumpTable { + // super instructions + tbl[Nop] = &operation{ + execute: opNop, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + 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[Push1Shl] = &operation{ + execute: opPush1Shl, + 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), + } + + return tbl +} 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..282b5aed53 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..20906e07f8 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: "reached limit for padding memory slice: 1049568 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..afb29570d3 100644 --- a/go.mod +++ b/go.mod @@ -93,7 +93,6 @@ require ( github.com/aws/smithy-go v1.15.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.3 // indirect diff --git a/go.sum b/go.sum index ada56d58cf..413bdc1848 100644 --- a/go.sum +++ b/go.sum @@ -190,7 +190,6 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf 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/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= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=