trie: do not expect ordering in stacktrie during fuzzing (#31170)

This PR removes the assumption of the stacktrie and trie to have the
same ordering. This was hit by the fuzzers on oss-fuzz

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
Marius van der Wijden 2025-02-18 03:48:42 +01:00 committed by GitHub
parent aec1964410
commit 3adfa1fbeb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 24 deletions

View file

@ -37,16 +37,13 @@ func FuzzStackTrie(f *testing.F) {
}
func fuzz(data []byte, debugging bool) {
// This spongeDb is used to check the sequence of disk-db-writes
var (
input = bytes.NewReader(data)
spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
dbA = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
trieA = NewEmpty(dbA)
spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
memDB = rawdb.NewMemoryDatabase()
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
rawdb.WriteTrieNode(memDB, common.Hash{}, path, hash, blob, rawdb.HashScheme)
})
vals []*kv
maxElements = 10000
@ -55,13 +52,17 @@ func fuzz(data []byte, debugging bool) {
)
// Fill the trie with elements
for i := 0; input.Len() > 0 && i < maxElements; i++ {
// Build the key
k := make([]byte, 32)
input.Read(k)
// Build the val
var a uint16
binary.Read(input, binary.LittleEndian, &a)
a = 1 + a%100
v := make([]byte, a)
input.Read(v)
if input.Len() == 0 {
// If it was exhausted while reading, the value may be all zeroes,
// thus 'deletion' which is not supported on stacktrie
@ -73,6 +74,7 @@ func fuzz(data []byte, debugging bool) {
}
keys[string(k)] = struct{}{}
vals = append(vals, &kv{k: k, v: v})
trieA.MustUpdate(k, v)
}
if len(vals) == 0 {
@ -99,11 +101,6 @@ func fuzz(data []byte, debugging bool) {
if rootA != rootB {
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
}
sumA := spongeA.sponge.Sum(nil)
sumB := spongeB.sponge.Sum(nil)
if !bytes.Equal(sumA, sumB) {
panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
}
// Ensure all the nodes are persisted correctly
var (

View file

@ -779,6 +779,7 @@ func TestCommitAfterHash(t *testing.T) {
func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
// Make the random benchmark deterministic
random := rand.New(rand.NewSource(0))
// Create a realistic account trie to hash
addresses = make([][20]byte, size)
for i := 0; i < len(addresses); i++ {
@ -795,13 +796,18 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
)
// The big.Rand function is not deterministic with regards to 64 vs 32 bit systems,
// and will consume different amount of data from the rand source.
//balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
// balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
// Therefore, we instead just read via byte buffer
numBytes := random.Uint32() % 33 // [0, 32] bytes
balanceBytes := make([]byte, numBytes)
random.Read(balanceBytes)
balance := new(uint256.Int).SetBytes(balanceBytes)
data, _ := rlp.EncodeToBytes(&types.StateAccount{Nonce: nonce, Balance: balance, Root: root, CodeHash: code})
data, _ := rlp.EncodeToBytes(&types.StateAccount{
Nonce: nonce,
Balance: balance,
Root: root,
CodeHash: code,
})
accounts[i] = data
}
return addresses, accounts
@ -856,6 +862,7 @@ func (s *spongeDb) Flush() {
s.sponge.Write([]byte(key))
s.sponge.Write([]byte(s.values[key]))
}
fmt.Println(len(s.keys))
}
// spongeBatch is a dummy batch which immediately writes to the underlying spongedb
@ -873,10 +880,12 @@ func (b *spongeBatch) Write() error { return nil }
func (b *spongeBatch) Reset() {}
func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie
// in the expected order.
// The test data was based on the 'master' code, and is basically random. It can be used
// to check whether changes to the trie modifies the write order or data in any way.
// TestCommitSequence tests that the trie.Commit operation writes the elements
// of the trie in the expected order.
//
// The test data was based on the 'master' code, and is basically random.
// It can be used to check whether changes to the trie modifies the write order
// or data in any way.
func TestCommitSequence(t *testing.T) {
for i, tc := range []struct {
count int
@ -887,19 +896,23 @@ func TestCommitSequence(t *testing.T) {
{2000, common.FromHex("4574cd8e6b17f3fe8ad89140d1d0bf4f1bd7a87a8ac3fb623b33550544c77635")},
} {
addresses, accounts := makeAccounts(tc.count)
// This spongeDb is used to check the sequence of disk-db-writes
s := &spongeDb{sponge: crypto.NewKeccakState()}
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
trie := NewEmpty(db)
// Fill the trie with elements
trie := NewEmpty(db)
for i := 0; i < tc.count; i++ {
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Flush trie -> database
root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root)
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp)
}
@ -917,12 +930,13 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
{200, common.FromHex("dde92ca9812e068e6982d04b40846dc65a61a9fd4996fc0f55f2fde172a8e13c")},
{2000, common.FromHex("ab553a7f9aff82e3929c382908e30ef7dd17a332933e92ba3fe873fc661ef382")},
} {
prng := rand.New(rand.NewSource(int64(i)))
// This spongeDb is used to check the sequence of disk-db-writes
prng := rand.New(rand.NewSource(int64(i)))
s := &spongeDb{sponge: crypto.NewKeccakState()}
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
trie := NewEmpty(db)
// Fill the trie with elements
trie := NewEmpty(db)
for i := 0; i < tc.count; i++ {
key := make([]byte, 32)
var val []byte
@ -939,6 +953,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
// Flush trie -> database
root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root)
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
@ -974,19 +989,22 @@ func TestCommitSequenceStackTrie(t *testing.T) {
// For the stack trie, we need to do inserts in proper order
key := make([]byte, 32)
binary.BigEndian.PutUint64(key, uint64(i))
var val []byte
// 50% short elements, 50% large elements
var val []byte
if prng.Intn(2) == 0 {
val = make([]byte, 1+prng.Intn(32))
} else {
val = make([]byte, 1+prng.Intn(1024))
}
prng.Read(val)
trie.Update(key, val)
stTrie.Update(key, val)
}
// Flush trie -> database
root, nodes := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Commit(root)
@ -1045,6 +1063,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
// Flush trie -> database
root, nodes := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Commit(root)