mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
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:
parent
aec1964410
commit
3adfa1fbeb
2 changed files with 40 additions and 24 deletions
|
|
@ -37,16 +37,13 @@ func FuzzStackTrie(f *testing.F) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fuzz(data []byte, debugging bool) {
|
func fuzz(data []byte, debugging bool) {
|
||||||
// This spongeDb is used to check the sequence of disk-db-writes
|
|
||||||
var (
|
var (
|
||||||
input = bytes.NewReader(data)
|
input = bytes.NewReader(data)
|
||||||
spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
|
dbA = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
|
||||||
dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
|
trieA = NewEmpty(dbA)
|
||||||
trieA = NewEmpty(dbA)
|
memDB = rawdb.NewMemoryDatabase()
|
||||||
spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
|
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||||
dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
|
rawdb.WriteTrieNode(memDB, common.Hash{}, path, hash, blob, rawdb.HashScheme)
|
||||||
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
|
||||||
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
|
|
||||||
})
|
})
|
||||||
vals []*kv
|
vals []*kv
|
||||||
maxElements = 10000
|
maxElements = 10000
|
||||||
|
|
@ -55,13 +52,17 @@ func fuzz(data []byte, debugging bool) {
|
||||||
)
|
)
|
||||||
// Fill the trie with elements
|
// Fill the trie with elements
|
||||||
for i := 0; input.Len() > 0 && i < maxElements; i++ {
|
for i := 0; input.Len() > 0 && i < maxElements; i++ {
|
||||||
|
// Build the key
|
||||||
k := make([]byte, 32)
|
k := make([]byte, 32)
|
||||||
input.Read(k)
|
input.Read(k)
|
||||||
|
|
||||||
|
// Build the val
|
||||||
var a uint16
|
var a uint16
|
||||||
binary.Read(input, binary.LittleEndian, &a)
|
binary.Read(input, binary.LittleEndian, &a)
|
||||||
a = 1 + a%100
|
a = 1 + a%100
|
||||||
v := make([]byte, a)
|
v := make([]byte, a)
|
||||||
input.Read(v)
|
input.Read(v)
|
||||||
|
|
||||||
if input.Len() == 0 {
|
if input.Len() == 0 {
|
||||||
// If it was exhausted while reading, the value may be all zeroes,
|
// If it was exhausted while reading, the value may be all zeroes,
|
||||||
// thus 'deletion' which is not supported on stacktrie
|
// thus 'deletion' which is not supported on stacktrie
|
||||||
|
|
@ -73,6 +74,7 @@ func fuzz(data []byte, debugging bool) {
|
||||||
}
|
}
|
||||||
keys[string(k)] = struct{}{}
|
keys[string(k)] = struct{}{}
|
||||||
vals = append(vals, &kv{k: k, v: v})
|
vals = append(vals, &kv{k: k, v: v})
|
||||||
|
|
||||||
trieA.MustUpdate(k, v)
|
trieA.MustUpdate(k, v)
|
||||||
}
|
}
|
||||||
if len(vals) == 0 {
|
if len(vals) == 0 {
|
||||||
|
|
@ -99,11 +101,6 @@ func fuzz(data []byte, debugging bool) {
|
||||||
if rootA != rootB {
|
if rootA != rootB {
|
||||||
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", 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
|
// Ensure all the nodes are persisted correctly
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -779,6 +779,7 @@ func TestCommitAfterHash(t *testing.T) {
|
||||||
func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
|
func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
|
||||||
// Make the random benchmark deterministic
|
// Make the random benchmark deterministic
|
||||||
random := rand.New(rand.NewSource(0))
|
random := rand.New(rand.NewSource(0))
|
||||||
|
|
||||||
// Create a realistic account trie to hash
|
// Create a realistic account trie to hash
|
||||||
addresses = make([][20]byte, size)
|
addresses = make([][20]byte, size)
|
||||||
for i := 0; i < len(addresses); i++ {
|
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,
|
// 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.
|
// 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
|
// Therefore, we instead just read via byte buffer
|
||||||
numBytes := random.Uint32() % 33 // [0, 32] bytes
|
numBytes := random.Uint32() % 33 // [0, 32] bytes
|
||||||
balanceBytes := make([]byte, numBytes)
|
balanceBytes := make([]byte, numBytes)
|
||||||
random.Read(balanceBytes)
|
random.Read(balanceBytes)
|
||||||
balance := new(uint256.Int).SetBytes(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
|
accounts[i] = data
|
||||||
}
|
}
|
||||||
return addresses, accounts
|
return addresses, accounts
|
||||||
|
|
@ -856,6 +862,7 @@ func (s *spongeDb) Flush() {
|
||||||
s.sponge.Write([]byte(key))
|
s.sponge.Write([]byte(key))
|
||||||
s.sponge.Write([]byte(s.values[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
|
// 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) Reset() {}
|
||||||
func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
|
func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
|
||||||
|
|
||||||
// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie
|
// TestCommitSequence tests that the trie.Commit operation writes the elements
|
||||||
// in the expected order.
|
// 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.
|
// 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) {
|
func TestCommitSequence(t *testing.T) {
|
||||||
for i, tc := range []struct {
|
for i, tc := range []struct {
|
||||||
count int
|
count int
|
||||||
|
|
@ -887,19 +896,23 @@ func TestCommitSequence(t *testing.T) {
|
||||||
{2000, common.FromHex("4574cd8e6b17f3fe8ad89140d1d0bf4f1bd7a87a8ac3fb623b33550544c77635")},
|
{2000, common.FromHex("4574cd8e6b17f3fe8ad89140d1d0bf4f1bd7a87a8ac3fb623b33550544c77635")},
|
||||||
} {
|
} {
|
||||||
addresses, accounts := makeAccounts(tc.count)
|
addresses, accounts := makeAccounts(tc.count)
|
||||||
|
|
||||||
// This spongeDb is used to check the sequence of disk-db-writes
|
// This spongeDb is used to check the sequence of disk-db-writes
|
||||||
s := &spongeDb{sponge: crypto.NewKeccakState()}
|
s := &spongeDb{sponge: crypto.NewKeccakState()}
|
||||||
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
|
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
|
||||||
trie := NewEmpty(db)
|
|
||||||
// Fill the trie with elements
|
// Fill the trie with elements
|
||||||
|
trie := NewEmpty(db)
|
||||||
for i := 0; i < tc.count; i++ {
|
for i := 0; i < tc.count; i++ {
|
||||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||||
}
|
}
|
||||||
// Flush trie -> database
|
// Flush trie -> database
|
||||||
root, nodes := trie.Commit(false)
|
root, nodes := trie.Commit(false)
|
||||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||||
|
|
||||||
// Flush memdb -> disk (sponge)
|
// Flush memdb -> disk (sponge)
|
||||||
db.Commit(root)
|
db.Commit(root)
|
||||||
|
|
||||||
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
|
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)
|
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")},
|
{200, common.FromHex("dde92ca9812e068e6982d04b40846dc65a61a9fd4996fc0f55f2fde172a8e13c")},
|
||||||
{2000, common.FromHex("ab553a7f9aff82e3929c382908e30ef7dd17a332933e92ba3fe873fc661ef382")},
|
{2000, common.FromHex("ab553a7f9aff82e3929c382908e30ef7dd17a332933e92ba3fe873fc661ef382")},
|
||||||
} {
|
} {
|
||||||
prng := rand.New(rand.NewSource(int64(i)))
|
|
||||||
// This spongeDb is used to check the sequence of disk-db-writes
|
// This spongeDb is used to check the sequence of disk-db-writes
|
||||||
|
prng := rand.New(rand.NewSource(int64(i)))
|
||||||
s := &spongeDb{sponge: crypto.NewKeccakState()}
|
s := &spongeDb{sponge: crypto.NewKeccakState()}
|
||||||
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
|
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
|
||||||
trie := NewEmpty(db)
|
|
||||||
// Fill the trie with elements
|
// Fill the trie with elements
|
||||||
|
trie := NewEmpty(db)
|
||||||
for i := 0; i < tc.count; i++ {
|
for i := 0; i < tc.count; i++ {
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
var val []byte
|
var val []byte
|
||||||
|
|
@ -939,6 +953,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
|
||||||
// Flush trie -> database
|
// Flush trie -> database
|
||||||
root, nodes := trie.Commit(false)
|
root, nodes := trie.Commit(false)
|
||||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||||
|
|
||||||
// Flush memdb -> disk (sponge)
|
// Flush memdb -> disk (sponge)
|
||||||
db.Commit(root)
|
db.Commit(root)
|
||||||
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
|
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
|
// For the stack trie, we need to do inserts in proper order
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
binary.BigEndian.PutUint64(key, uint64(i))
|
binary.BigEndian.PutUint64(key, uint64(i))
|
||||||
var val []byte
|
|
||||||
// 50% short elements, 50% large elements
|
// 50% short elements, 50% large elements
|
||||||
|
var val []byte
|
||||||
if prng.Intn(2) == 0 {
|
if prng.Intn(2) == 0 {
|
||||||
val = make([]byte, 1+prng.Intn(32))
|
val = make([]byte, 1+prng.Intn(32))
|
||||||
} else {
|
} else {
|
||||||
val = make([]byte, 1+prng.Intn(1024))
|
val = make([]byte, 1+prng.Intn(1024))
|
||||||
}
|
}
|
||||||
prng.Read(val)
|
prng.Read(val)
|
||||||
|
|
||||||
trie.Update(key, val)
|
trie.Update(key, val)
|
||||||
stTrie.Update(key, val)
|
stTrie.Update(key, val)
|
||||||
}
|
}
|
||||||
// Flush trie -> database
|
// Flush trie -> database
|
||||||
root, nodes := trie.Commit(false)
|
root, nodes := trie.Commit(false)
|
||||||
|
|
||||||
// Flush memdb -> disk (sponge)
|
// Flush memdb -> disk (sponge)
|
||||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||||
db.Commit(root)
|
db.Commit(root)
|
||||||
|
|
@ -1045,6 +1063,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
|
||||||
|
|
||||||
// Flush trie -> database
|
// Flush trie -> database
|
||||||
root, nodes := trie.Commit(false)
|
root, nodes := trie.Commit(false)
|
||||||
|
|
||||||
// Flush memdb -> disk (sponge)
|
// Flush memdb -> disk (sponge)
|
||||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||||
db.Commit(root)
|
db.Commit(root)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue