diff --git a/core/types/block.go b/core/types/block.go index da9614793a..b5b6468a13 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -240,7 +240,7 @@ type extblock struct { // // The receipt's bloom must already calculated for the block's bloom to be // correctly calculated. -func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher) *Block { if body == nil { body = &Body{} } diff --git a/core/types/hashing.go b/core/types/hashing.go index 183c8b835b..8337f656bc 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -75,14 +75,17 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { return h } -// TrieHasher is the tool used to calculate the hash of derivable list. -// This is internal, do not use. -type TrieHasher interface { +// ListHasher defines the interface for computing the hash of a derivable list. +type ListHasher interface { + // Reset clears the internal state of the hasher, preparing it for reuse. Reset() - Update([]byte, []byte) error - // UpdateSafe is identical to Update, except that this method will copy the - // value slice. The caller is free to modify the value bytes after this method returns. - UpdateSafe([]byte, []byte) error + + // Update inserts the given key-value pair into the hasher. + // The implementation must copy the provided slices, allowing the caller + // to safely modify them after the call returns. + Update(key []byte, value []byte) error + + // Hash computes and returns the final hash of all inserted key-value pairs. Hash() common.Hash } @@ -94,6 +97,7 @@ type DerivableList interface { EncodeIndex(int, *bytes.Buffer) } +// encodeForDerive encodes the element in the list at the position i into the buffer. func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { buf.Reset() list.EncodeIndex(i, buf) @@ -101,9 +105,12 @@ func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { } // DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. -func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { +func DeriveSha(list DerivableList, hasher ListHasher) common.Hash { hasher.Reset() + // Allocate a buffer for value encoding. As the hasher is claimed that all + // supplied key value pairs will be copied by hasher and safe to reuse the + // encoding buffer. valueBuf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(valueBuf) @@ -117,17 +124,17 @@ func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { for i := 1; i < list.Len() && i <= 0x7f; i++ { indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) value := encodeForDerive(list, i, valueBuf) - hasher.UpdateSafe(indexBuf, value) + hasher.Update(indexBuf, value) } if list.Len() > 0 { indexBuf = rlp.AppendUint64(indexBuf[:0], 0) value := encodeForDerive(list, 0, valueBuf) - hasher.UpdateSafe(indexBuf, value) + hasher.Update(indexBuf, value) } for i := 0x80; i < list.Len(); i++ { indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) value := encodeForDerive(list, i, valueBuf) - hasher.UpdateSafe(indexBuf, value) + hasher.Update(indexBuf, value) } return hasher.Hash() } diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index 5978bd44d0..8f51088179 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -26,12 +26,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/rawdb" "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/triedb" ) func TestDeriveSha(t *testing.T) { @@ -40,7 +38,7 @@ func TestDeriveSha(t *testing.T) { t.Fatal(err) } for len(txs) < 1000 { - exp := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(txs, trie.NewListHasher()) got := types.DeriveSha(txs, trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) @@ -81,12 +79,12 @@ func BenchmarkDeriveSha200(b *testing.B) { if err != nil { b.Fatal(err) } - want := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + want := types.DeriveSha(txs, trie.NewListHasher()) var have common.Hash b.Run("std_trie", func(b *testing.B) { b.ReportAllocs() for b.Loop() { - have = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + have = types.DeriveSha(txs, trie.NewListHasher()) } if have != want { b.Errorf("have %x want %x", have, want) @@ -111,7 +109,7 @@ func TestFuzzDeriveSha(t *testing.T) { rndSeed := mrand.Int() for i := 0; i < 10; i++ { seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(newDummy(i), trie.NewListHasher()) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { printList(t, newDummy(seed)) @@ -139,7 +137,7 @@ func TestDerivableList(t *testing.T) { }, } for i, tc := range tcs[1:] { - exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(flatList(tc), trie.NewListHasher()) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) @@ -235,12 +233,6 @@ func (d *hashToHumanReadable) Update(i []byte, i2 []byte) error { return nil } -// UpdateSafe is identical to Update, except that this method will copy the -// value slice. The caller is free to modify the value bytes after this method returns. -func (d *hashToHumanReadable) UpdateSafe(key, value []byte) error { - return d.Update(key, common.CopyBytes(value)) -} - func (d *hashToHumanReadable) Hash() common.Hash { return common.Hash{} } diff --git a/internal/blocktest/test_hash.go b/internal/blocktest/test_hash.go index 05608e5721..b3e7098e2b 100644 --- a/internal/blocktest/test_hash.go +++ b/internal/blocktest/test_hash.go @@ -23,6 +23,7 @@ package blocktest import ( + "bytes" "hash" "github.com/ethereum/go-ethereum/common" @@ -48,17 +49,11 @@ func (h *testHasher) Reset() { // Update updates the hash state with the given key and value. func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) + h.hasher.Write(bytes.Clone(key)) + h.hasher.Write(bytes.Clone(val)) return nil } -// UpdateSafe is identical to Update, except that this method will copy the -// value slice. The caller is free to modify the value bytes after this method returns. -func (h *testHasher) UpdateSafe(key, value []byte) error { - return h.Update(key, common.CopyBytes(value)) -} - // Hash returns the hash value. func (h *testHasher) Hash() common.Hash { return common.BytesToHash(h.hasher.Sum(nil)) diff --git a/trie/list_hasher.go b/trie/list_hasher.go new file mode 100644 index 0000000000..173ded5553 --- /dev/null +++ b/trie/list_hasher.go @@ -0,0 +1,59 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// ListHasher is the wrapper of Merkle-Patricia-Trie, implementing the +// types.ListHasher by always deep-copying the supplied slices. +// +// This implementation is very inefficient in terms of memory allocation +// compared with StackTrie, only for correctness comparison purpose. +type ListHasher struct { + tr *Trie +} + +// NewListHasher initializes the list hasher. +func NewListHasher() *ListHasher { + return &ListHasher{ + tr: NewEmpty(nil), + } +} + +// Reset implements types.ListHasher, clearing the internal state and prepare +// it for reuse. +func (h *ListHasher) Reset() { + h.tr.reset() +} + +// Update implements types.ListHasher, inserting the given key-value pair into +// the trie. The supplied key-value pair should be deep-copied to satisfy the +// interface restriction. +func (h *ListHasher) Update(key []byte, value []byte) error { + key, value = bytes.Clone(key), bytes.Clone(value) + return h.tr.Update(key, value) +} + +// Hash implements types.ListHasher, computing the final hash of all inserted +// key-value pairs. +func (h *ListHasher) Hash() common.Hash { + return h.tr.Hash() +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 1c91a77b81..ed7c7635ba 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -28,7 +28,7 @@ import ( var ( stPool = sync.Pool{New: func() any { return new(stNode) }} bPool = newBytesPool(32, 100) - _ = types.TrieHasher((*StackTrie)(nil)) + _ = types.ListHasher((*StackTrie)(nil)) ) // OnTrieNode is a callback method invoked when a trie node is committed @@ -75,16 +75,10 @@ func (t *StackTrie) grow(key []byte) { } } -// UpdateSafe is identical to Update, except that this method will copy the -// value slice. The caller is free to modify the value bytes after this method returns. -func (t *StackTrie) UpdateSafe(key, value []byte) error { - // The stacktrie always copies the value (is already safe). - return t.Update(key, value) -} - // Update inserts a (key, value) pair into the stack trie. -// The value is copied, and the caller is free to modify the value after this -// method returns. +// +// Note the supplied key value pair is copied and managed internally, they are +// safe to be modified after this method returns. func (t *StackTrie) Update(key, value []byte) error { if len(value) == 0 { return errors.New("trying to insert empty (deletion)") @@ -114,7 +108,6 @@ func (t *StackTrie) Update(key, value []byte) error { func (t *StackTrie) Reset() { t.root = stPool.Get().(*stNode) t.last = nil - t.onTrieNode = nil } // TrieKey returns the internal key representation for the given user key. diff --git a/trie/trie.go b/trie/trie.go index 43d4f06285..cbe8f186e0 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -790,8 +790,8 @@ func (t *Trie) Witness() map[string][]byte { return t.prevalueTracer.Values() } -// Reset drops the referenced root node and cleans all internal state. -func (t *Trie) Reset() { +// reset drops the referenced root node and cleans all internal state. +func (t *Trie) reset() { t.root = nil t.owner = common.Hash{} t.unhashed = 0