1
0
Fork 0
forked from forks/go-ethereum

all: create global hasher pool (#31769)

This PR creates a global hasher pool that can be used by all packages.
It also removes a bunch of the package local pools.

It also updates a few locations to use available hashers or the global
hashing pool to reduce allocations all over the codebase.
This change should reduce global allocation count by ~1%

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
Marius van der Wijden 2025-05-09 07:52:40 +02:00 committed by GitHub
parent 485ff4bbff
commit 0eb2eeea90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 34 additions and 121 deletions

View file

@ -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

View file

@ -33,24 +33,6 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
// bufferPool holds the buffers for keccak calculation.
var bufferPool = sync.Pool{
New: func() interface{} {
return crypto.NewKeccakState()
},
}
// allocBuff allocates the keccak buffer from the pool
func allocBuff() crypto.KeccakState {
return bufferPool.Get().(crypto.KeccakState)
}
// releaseBuff returns the provided keccak buffer to the pool. It's unnecessary
// to clear the buffer, as it will be cleared before the calculation.
func releaseBuff(buff crypto.KeccakState) {
bufferPool.Put(buff)
}
// ContractCodeReader defines the interface for accessing contract code.
type ContractCodeReader interface {
// Code retrieves a particular contract's code.
@ -167,10 +149,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
//
// The returned account might be nil if it's not existent.
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
buff := allocBuff()
defer releaseBuff(buff)
account, err := r.reader.Account(crypto.HashData(buff, addr.Bytes()))
account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes()))
if err != nil {
return nil, err
}
@ -200,11 +179,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
//
// The returned storage slot might be empty if it's not existent.
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
buff := allocBuff()
defer releaseBuff(buff)
addrHash := crypto.HashData(buff, addr.Bytes())
slotHash := crypto.HashData(buff, key.Bytes())
addrHash := crypto.Keccak256Hash(addr.Bytes())
slotHash := crypto.Keccak256Hash(key.Bytes())
ret, err := r.reader.Storage(addrHash, slotHash)
if err != nil {
return common.Hash{}, err

View file

@ -25,12 +25,11 @@ 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() },
New: func() interface{} { return crypto.NewKeccakState() },
}
// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.

View file

@ -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)
}

View file

@ -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[:])

View file

@ -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

View file

@ -28,6 +28,7 @@ import (
"io"
"math/big"
"os"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
@ -73,6 +74,12 @@ func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState)
}
var hasherPool = sync.Pool{
New: func() any {
return sha3.NewLegacyKeccak256().(KeccakState)
},
}
// HashData hashes the provided data using the KeccakState and returns a 32 byte hash
func HashData(kh KeccakState, data []byte) (h common.Hash) {
kh.Reset()
@ -84,22 +91,26 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) {
// Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte {
b := make([]byte, 32)
d := NewKeccakState()
d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data {
d.Write(b)
}
d.Read(b)
hasherPool.Put(d)
return b
}
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure.
func Keccak256Hash(data ...[]byte) (h common.Hash) {
d := NewKeccakState()
d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data {
d.Write(b)
}
d.Read(h[:])
hasherPool.Put(d)
return h
}

View file

@ -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(),

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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,9 @@ 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()
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 +113,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 +149,9 @@ 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()
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 +173,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