From 80ccc0e3224610aeced01fa67bd0039032aeb0af Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Tue, 6 May 2025 14:02:08 +0200 Subject: [PATCH] crypto: all: create global hasher pool --- core/rawdb/accessors_trie.go | 28 ++-------------------------- core/rawdb/database.go | 1 + core/state/state_object.go | 1 + core/state/statedb.go | 1 + core/types/bloom9.go | 4 ++-- core/types/hashing.go | 14 ++++---------- core/vm/evm.go | 3 ++- core/vm/instructions.go | 7 +------ core/vm/interpreter.go | 2 +- crypto/crypto.go | 17 ++++++++++++++++- eth/handler.go | 1 + eth/protocols/snap/sync.go | 2 ++ trie/hasher.go | 2 +- trie/sync.go | 24 +----------------------- triedb/pathdb/disklayer.go | 26 ++------------------------ triedb/pathdb/execute.go | 17 +++++++++-------- triedb/pathdb/history.go | 1 + 17 files changed, 48 insertions(+), 103 deletions(-) diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index 8bd6b71eee..e154ab527b 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -18,7 +18,6 @@ package rawdb import ( "fmt" - "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -45,25 +44,6 @@ const HashScheme = "hash" // on extra state diffs to survive deep reorg. const PathScheme = "path" -// hasher is used to compute the sha256 hash of the provided data. -type hasher struct{ sha crypto.KeccakState } - -var hasherPool = sync.Pool{ - New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} }, -} - -func newHasher() *hasher { - return hasherPool.Get().(*hasher) -} - -func (h *hasher) hash(data []byte) common.Hash { - return crypto.HashData(h.sha, data) -} - -func (h *hasher) release() { - hasherPool.Put(h) -} - // ReadAccountTrieNode retrieves the account trie node with the specified node path. func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte { data, _ := db.Get(accountTrieNodeKey(path)) @@ -170,9 +150,7 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c if len(blob) == 0 { return false } - h := newHasher() - defer h.release() - return h.hash(blob) == hash // exists but not match + return crypto.Keccak256Hash(blob) == hash // exists but not match default: panic(fmt.Sprintf("Unknown scheme %v", scheme)) } @@ -194,9 +172,7 @@ func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash if len(blob) == 0 { return nil } - h := newHasher() - defer h.release() - if h.hash(blob) != hash { + if crypto.Keccak256Hash(blob) != hash { return nil // exists but not match } return blob diff --git a/core/rawdb/database.go b/core/rawdb/database.go index a03dbafb1f..7fb78773bf 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -645,6 +645,7 @@ func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool, buff = crypto.NewKeccakState() startTime = time.Now() ) + defer crypto.ReturnKeccakState(buff) batch := db.NewBatch() it := db.NewIterator(nil, start) diff --git a/core/state/state_object.go b/core/state/state_object.go index a6979bd361..76139766d9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -386,6 +386,7 @@ func (s *stateObject) commitStorage(op *accountUpdate) { return blob } ) + defer crypto.ReturnKeccakState(buf) for key, val := range s.pendingStorage { // Skip the noop storage changes, it might be possible the value // of tracked slot is same in originStorage and pendingStorage diff --git a/core/state/statedb.go b/core/state/statedb.go index 9378cae7de..511292820f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1067,6 +1067,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco buf = crypto.NewKeccakState() deletes = make(map[common.Hash]*accountDelete) ) + defer crypto.ReturnKeccakState(buf) for addr, prevObj := range s.stateObjectsDestruct { prev := prevObj.origin diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 962ba46d47..b8704d8fff 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -140,11 +140,11 @@ func Bloom9(data []byte) []byte { // bloomValues returns the bytes (index-value pairs) to set for the given data func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { - sha := hasherPool.Get().(crypto.KeccakState) + sha := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(sha) sha.Reset() sha.Write(data) sha.Read(hashbuf) - hasherPool.Put(sha) // The actual bits to flip v1 := byte(1 << (hashbuf[1] & 0x7)) v2 := byte(1 << (hashbuf[3] & 0x7)) diff --git a/core/types/hashing.go b/core/types/hashing.go index 224d7a87ea..540c5111fb 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -25,14 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) -// hasherPool holds LegacyKeccak256 hashers for rlpHash. -var hasherPool = sync.Pool{ - New: func() interface{} { return sha3.NewLegacyKeccak256() }, -} - // encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. var encodeBufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, @@ -56,8 +50,8 @@ func getPooledBuffer(size uint64) ([]byte, *bytes.Buffer, error) { // rlpHash encodes x and hashes the encoded bytes. func rlpHash(x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) - defer hasherPool.Put(sha) + sha := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(sha) sha.Reset() rlp.Encode(sha, x) sha.Read(h[:]) @@ -67,8 +61,8 @@ func rlpHash(x interface{}) (h common.Hash) { // prefixedRlpHash writes the prefix into the hasher before rlp-encoding x. // It's used for typed transactions. func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) - defer hasherPool.Put(sha) + sha := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(sha) sha.Reset() sha.Write([]byte{prefix}) rlp.Encode(sha, x) diff --git a/core/vm/evm.go b/core/vm/evm.go index c28dcb2554..ecb0f118ec 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -555,7 +555,8 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { - contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(code)) + inithash := crypto.HashData(evm.interpreter.hasher, code) + contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 0b3b1d1569..63bb6d2d51 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -234,11 +233,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( offset, size := scope.Stack.pop(), scope.Stack.peek() data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - if interpreter.hasher == nil { - interpreter.hasher = crypto.NewKeccakState() - } else { - interpreter.hasher.Reset() - } + interpreter.hasher.Reset() interpreter.hasher.Write(data) interpreter.hasher.Read(interpreter.hasherBuf[:]) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a0038d1aa8..a62c3c843d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -150,7 +150,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips - return &EVMInterpreter{evm: evm, table: table} + return &EVMInterpreter{evm: evm, table: table, hasher: crypto.NewKeccakState()} } // Run loops and evaluates the contract's code with the given input data and returns diff --git a/crypto/crypto.go b/crypto/crypto.go index 13e9b134f0..cd0af3f3e5 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -28,6 +28,7 @@ import ( "io" "math/big" "os" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -70,7 +71,19 @@ type KeccakState interface { // NewKeccakState creates a new KeccakState func NewKeccakState() KeccakState { - return sha3.NewLegacyKeccak256().(KeccakState) + state := hasherPool.Get().(KeccakState) + state.Reset() + return state +} + +var hasherPool = sync.Pool{ + New: func() any { + return sha3.NewLegacyKeccak256().(KeccakState) + }, +} + +func ReturnKeccakState(state KeccakState) { + hasherPool.Put(state) } // HashData hashes the provided data using the KeccakState and returns a 32 byte hash @@ -89,6 +102,7 @@ func Keccak256(data ...[]byte) []byte { d.Write(b) } d.Read(b) + ReturnKeccakState(d) return b } @@ -100,6 +114,7 @@ func Keccak256Hash(data ...[]byte) (h common.Hash) { d.Write(b) } d.Read(h[:]) + ReturnKeccakState(d) return h } diff --git a/eth/handler.go b/eth/handler.go index 8283d7d02f..44bac477cb 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -488,6 +488,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { hasher = crypto.NewKeccakState() hash = make([]byte, 32) ) + defer crypto.ReturnKeccakState(hasher) for _, tx := range txs { var maybeDirect bool switch { diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 9e079f540f..e1b5ed79b8 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2655,6 +2655,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error // Cross reference the requested bytecodes with the response to find gaps // that the serving node is missing hasher := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(hasher) hash := make([]byte, 32) codes := make([][]byte, len(req.hashes)) @@ -2907,6 +2908,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error nodes = make([][]byte, len(req.hashes)) fills uint64 ) + defer crypto.ReturnKeccakState(hasher) for i, j := 0, 0; i < len(trienodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() diff --git a/trie/hasher.go b/trie/hasher.go index 614640ae3a..393cb0bd4d 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -34,7 +34,7 @@ type hasher struct { // hasherPool holds pureHashers var hasherPool = sync.Pool{ - New: func() interface{} { + New: func() any { return &hasher{ tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. sha: crypto.NewKeccakState(), diff --git a/trie/sync.go b/trie/sync.go index 3b7caae5b1..8d0ce6901c 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -729,9 +729,7 @@ func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists } else { blob = rawdb.ReadStorageTrieNode(s.database, owner, path) } - h := newBlobHasher() - defer h.release() - exists = hash == h.hash(blob) + exists = hash == crypto.Keccak256Hash(blob) inconsistent = !exists && len(blob) != 0 return exists, inconsistent } @@ -746,23 +744,3 @@ func ResolvePath(path []byte) (common.Hash, []byte) { } return owner, path } - -// blobHasher is used to compute the sha256 hash of the provided data. -type blobHasher struct{ state crypto.KeccakState } - -// blobHasherPool is the pool for reusing pre-allocated hash state. -var blobHasherPool = sync.Pool{ - New: func() interface{} { return &blobHasher{state: crypto.NewKeccakState()} }, -} - -func newBlobHasher() *blobHasher { - return blobHasherPool.Get().(*blobHasher) -} - -func (h *blobHasher) hash(data []byte) common.Hash { - return crypto.HashData(h.state, data) -} - -func (h *blobHasher) release() { - blobHasherPool.Put(h) -} diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index f3a60a507d..b8869888d9 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -115,15 +115,12 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co dirtyNodeMissMeter.Mark(1) // Try to retrieve the trie node from the clean memory cache - h := newHasher() - defer h.release() - key := nodeCacheKey(owner, path) if dl.nodes != nil { if blob := dl.nodes.Get(nil, key); len(blob) > 0 { cleanNodeHitMeter.Mark(1) cleanNodeReadMeter.Mark(int64(len(blob))) - return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil + return blob, crypto.Keccak256Hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil } cleanNodeMissMeter.Mark(1) } @@ -138,7 +135,7 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co dl.nodes.Set(key, blob) cleanNodeWriteMeter.Mark(int64(len(blob))) } - return blob, h.hash(blob), &nodeLoc{loc: locDiskLayer, depth: depth}, nil + return blob, crypto.Keccak256Hash(blob), &nodeLoc{loc: locDiskLayer, depth: depth}, nil } // account directly retrieves the account RLP associated with a particular @@ -359,22 +356,3 @@ func (dl *diskLayer) resetCache() { dl.nodes.Reset() } } - -// hasher is used to compute the sha256 hash of the provided data. -type hasher struct{ sha crypto.KeccakState } - -var hasherPool = sync.Pool{ - New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} }, -} - -func newHasher() *hasher { - return hasherPool.Get().(*hasher) -} - -func (h *hasher) hash(data []byte) common.Hash { - return crypto.HashData(h.sha, data) -} - -func (h *hasher) release() { - hasherPool.Put(h) -} diff --git a/triedb/pathdb/execute.go b/triedb/pathdb/execute.go index 2400f280a3..f8cb484744 100644 --- a/triedb/pathdb/execute.go +++ b/triedb/pathdb/execute.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" @@ -85,10 +86,10 @@ func apply(db database.NodeDatabase, prevRoot common.Hash, postRoot common.Hash, func updateAccount(ctx *context, db database.NodeDatabase, addr common.Address) error { // The account was present in prev-state, decode it from the // 'slim-rlp' format bytes. - h := newHasher() - defer h.release() + h := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(h) - addrHash := h.hash(addr.Bytes()) + addrHash := crypto.HashData(h, addr.Bytes()) prev, err := types.FullAccount(ctx.accounts[addr]) if err != nil { return err @@ -113,7 +114,7 @@ func updateAccount(ctx *context, db database.NodeDatabase, addr common.Address) for key, val := range ctx.storages[addr] { tkey := key if ctx.rawStorageKey { - tkey = h.hash(key.Bytes()) + tkey = crypto.HashData(h, key.Bytes()) } var err error if len(val) == 0 { @@ -149,10 +150,10 @@ func updateAccount(ctx *context, db database.NodeDatabase, addr common.Address) // account and storage is wiped out correctly. func deleteAccount(ctx *context, db database.NodeDatabase, addr common.Address) error { // The account must be existent in post-state, load the account. - h := newHasher() - defer h.release() + h := crypto.NewKeccakState() + defer crypto.ReturnKeccakState(h) - addrHash := h.hash(addr.Bytes()) + addrHash := crypto.HashData(h, addr.Bytes()) blob, err := ctx.accountTrie.Get(addrHash.Bytes()) if err != nil { return err @@ -174,7 +175,7 @@ func deleteAccount(ctx *context, db database.NodeDatabase, addr common.Address) } tkey := key if ctx.rawStorageKey { - tkey = h.hash(key.Bytes()) + tkey = crypto.HashData(h, key.Bytes()) } if err := st.Delete(tkey.Bytes()); err != nil { return err diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index aed0296da5..9eb1a08fc7 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -282,6 +282,7 @@ func (h *history) stateSet() (map[common.Hash][]byte, map[common.Hash]map[common accounts = make(map[common.Hash][]byte) storages = make(map[common.Hash]map[common.Hash][]byte) ) + defer crypto.ReturnKeccakState(buff) for addr, blob := range h.accounts { addrHash := crypto.HashData(buff, addr.Bytes()) accounts[addrHash] = blob