-
Notifications
You must be signed in to change notification settings - Fork 232
/
test_trie.go
183 lines (159 loc) · 5.62 KB
/
test_trie.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// (c) 2021-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package trie
import (
cryptoRand "crypto/rand"
"encoding/binary"
"math/big"
"math/rand"
"testing"
"github.com/ava-labs/avalanchego/utils/wrappers"
"github.com/ava-labs/subnet-evm/accounts/keystore"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/trie/trienode"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
)
// GenerateTrie creates a trie with [numKeys] key-value pairs inside of [trieDB].
// Returns the root of the generated trie, the slice of keys inserted into the trie in lexicographical
// order, and the slice of corresponding values.
// GenerateTrie reads from [rand] and the caller should call rand.Seed(n) for deterministic results
func GenerateTrie(t *testing.T, trieDB *Database, numKeys int, keySize int) (common.Hash, [][]byte, [][]byte) {
if keySize < wrappers.LongLen+1 {
t.Fatal("key size must be at least 9 bytes (8 bytes for uint64 and 1 random byte)")
}
testTrie := NewEmpty(trieDB)
keys, values := FillTrie(t, numKeys, keySize, testTrie)
// Commit the root to [trieDB]
root, nodes := testTrie.Commit(false)
err := trieDB.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
assert.NoError(t, err)
err = trieDB.Commit(root, false)
assert.NoError(t, err)
return root, keys, values
}
// FillTrie fills a given trie with [numKeys] number of keys, each of size [keySize]
// returns inserted keys and values
// FillTrie reads from [rand] and the caller should call rand.Seed(n) for deterministic results
func FillTrie(t *testing.T, numKeys int, keySize int, testTrie *Trie) ([][]byte, [][]byte) {
keys := make([][]byte, 0, numKeys)
values := make([][]byte, 0, numKeys)
// Generate key-value pairs
for i := 0; i < numKeys; i++ {
key := make([]byte, keySize)
binary.BigEndian.PutUint64(key[:wrappers.LongLen], uint64(i+1))
_, err := rand.Read(key[wrappers.LongLen:])
assert.NoError(t, err)
value := make([]byte, rand.Intn(128)+128) // min 128 bytes, max 256 bytes
_, err = rand.Read(value)
assert.NoError(t, err)
testTrie.MustUpdate(key, value)
keys = append(keys, key)
values = append(values, value)
}
return keys, values
}
// AssertTrieConsistency ensures given trieDB [a] and [b] both have the same
// non-empty trie at [root]. (all key/value pairs must be equal)
func AssertTrieConsistency(t testing.TB, root common.Hash, a, b *Database, onLeaf func(key, val []byte) error) {
trieA, err := New(TrieID(root), a)
if err != nil {
t.Fatalf("error creating trieA, root=%s, err=%v", root, err)
}
trieB, err := New(TrieID(root), b)
if err != nil {
t.Fatalf("error creating trieB, root=%s, err=%v", root, err)
}
itA := NewIterator(trieA.NodeIterator(nil))
itB := NewIterator(trieB.NodeIterator(nil))
count := 0
for itA.Next() && itB.Next() {
count++
assert.Equal(t, itA.Key, itB.Key)
assert.Equal(t, itA.Value, itB.Value)
if onLeaf != nil {
if err := onLeaf(itA.Key, itA.Value); err != nil {
t.Fatalf("error in onLeaf callback: %v", err)
}
}
}
assert.NoError(t, itA.Err)
assert.NoError(t, itB.Err)
assert.False(t, itA.Next())
assert.False(t, itB.Next())
assert.Greater(t, count, 0)
}
// CorruptTrie deletes every [n]th trie node from the trie given by [root] from the trieDB.
// Assumes that the trie given by root can be iterated without issue.
func CorruptTrie(t *testing.T, trieDB *Database, root common.Hash, n int) {
batch := trieDB.diskdb.NewBatch()
// next delete some trie nodes
tr, err := New(TrieID(root), trieDB)
if err != nil {
t.Fatal(err)
}
nodeIt := tr.NodeIterator(nil)
count := 0
for nodeIt.Next(true) {
count++
if count%n == 0 && nodeIt.Hash() != (common.Hash{}) {
if err := batch.Delete(nodeIt.Hash().Bytes()); err != nil {
t.Fatal(err)
}
}
}
if err := nodeIt.Error(); err != nil {
t.Fatal(err)
}
if err := batch.Write(); err != nil {
t.Fatal(err)
}
}
// FillAccounts adds [numAccounts] randomly generated accounts to the secure trie at [root] and commits it to [trieDB].
// [onAccount] is called if non-nil (so the caller can modify the account before it is stored in the secure trie).
// returns the new trie root and a map of funded keys to StateAccount structs.
func FillAccounts(
t *testing.T, trieDB *Database, root common.Hash, numAccounts int,
onAccount func(*testing.T, int, types.StateAccount) types.StateAccount,
) (common.Hash, map[*keystore.Key]*types.StateAccount) {
var (
minBalance = big.NewInt(3000000000000000000)
randBalance = big.NewInt(1000000000000000000)
maxNonce = 10
accounts = make(map[*keystore.Key]*types.StateAccount, numAccounts)
)
tr, err := NewStateTrie(TrieID(root), trieDB)
if err != nil {
t.Fatalf("error opening trie: %v", err)
}
for i := 0; i < numAccounts; i++ {
acc := types.StateAccount{
Nonce: uint64(rand.Intn(maxNonce)),
Balance: new(big.Int).Add(minBalance, randBalance),
CodeHash: types.EmptyCodeHash[:],
Root: types.EmptyRootHash,
}
if onAccount != nil {
acc = onAccount(t, i, acc)
}
accBytes, err := rlp.EncodeToBytes(&acc)
if err != nil {
t.Fatalf("failed to rlp encode account: %v", err)
}
key, err := keystore.NewKey(cryptoRand.Reader)
if err != nil {
t.Fatal(err)
}
tr.MustUpdate(key.Address[:], accBytes)
accounts[key] = &acc
}
newRoot, nodes := tr.Commit(false)
if err := trieDB.Update(newRoot, root, trienode.NewWithNodeSet(nodes)); err != nil {
t.Fatalf("error updating trieDB: %v", err)
}
if err := trieDB.Commit(newRoot, false); err != nil {
t.Fatalf("error committing trieDB: %v", err)
}
return newRoot, accounts
}