From 1d3d4a4d578825b040a3cac9eb163e7f3c16ce4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Oct 2018 14:14:29 +0300 Subject: [PATCH] core/vm: reuse Keccak-256 hashes across opcode executions (#17863) --- core/vm/instructions.go | 19 +++++++++++++------ core/vm/instructions_test.go | 21 +++++++++++++++++++++ core/vm/interpreter.go | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9623fb8de..e94a2777b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/params" ) @@ -373,13 +373,20 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { offset, size := stack.pop(), stack.pop() data := memory.Get(offset.Int64(), size.Int64()) - hash := crypto.Keccak256(data) - evm := interpreter.evm - if evm.vmConfig.EnablePreimageRecording { - evm.StateDB.AddPreimage(common.BytesToHash(hash), data) + if interpreter.hasher == nil { + interpreter.hasher = sha3.NewKeccak256().(keccakState) + } else { + interpreter.hasher.Reset() } - stack.push(interpreter.intPool.get().SetBytes(hash)) + interpreter.hasher.Write(data) + interpreter.hasher.Read(interpreter.hasherBuf[:]) + + evm := interpreter.evm + if evm.vmConfig.EnablePreimageRecording { + evm.StateDB.AddPreimage(interpreter.hasherBuf, data) + } + stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) interpreter.intPool.put(offset, size) return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 73c5befaa..8a48d765d 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -492,6 +492,27 @@ func BenchmarkOpMstore(bench *testing.B) { poolOfIntPools.put(evmInterpreter.intPool) } +func BenchmarkOpSHA3(bench *testing.B) { + var ( + env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env, env.vmConfig) + ) + env.interpreter = evmInterpreter + evmInterpreter.intPool = poolOfIntPools.get() + mem.Resize(32) + pc := uint64(0) + start := big.NewInt(0) + + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + stack.pushN(big.NewInt(32), start) + opSha3(&pc, evmInterpreter, nil, mem, stack) + } + poolOfIntPools.put(evmInterpreter.intPool) +} + func TestCreate2Addreses(t *testing.T) { type testcase struct { origin string diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 8e934f60e..952d96dd4 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -18,8 +18,10 @@ package vm import ( "fmt" + "hash" "sync/atomic" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" ) @@ -68,12 +70,24 @@ type Interpreter interface { CanRun([]byte) bool } +// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type keccakState interface { + hash.Hash + Read([]byte) (int, error) +} + // EVMInterpreter represents an EVM interpreter type EVMInterpreter struct { evm *EVM cfg Config gasTable params.GasTable - intPool *intPool + + intPool *intPool + + hasher keccakState // Keccak256 hasher instance shared across opcodes + hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes readOnly bool // Whether to throw on stateful modifications returnData []byte // Last CALL's return data for subsequent reuse