mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
189 lines
6.0 KiB
Go
189 lines
6.0 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package statesynctest
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/luxfi/node/v2/utils/wrappers"
|
|
"github.com/luxfi/evm/utils/utilstest"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/ethdb"
|
|
"github.com/luxfi/geth/rlp"
|
|
"github.com/luxfi/geth/trie"
|
|
"github.com/luxfi/geth/trie/trienode"
|
|
"github.com/luxfi/geth/triedb"
|
|
"github.com/holiman/uint256"
|
|
"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 *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)")
|
|
}
|
|
return FillTrie(t, 0, numKeys, keySize, trieDB, types.EmptyRootHash)
|
|
}
|
|
|
|
// 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, start, numKeys int, keySize int, trieDB *triedb.Database, root common.Hash) (common.Hash, [][]byte, [][]byte) {
|
|
testTrie, err := trie.New(trie.TrieID(root), trieDB)
|
|
if err != nil {
|
|
t.Fatalf("error creating trie: %v", err)
|
|
}
|
|
|
|
keys := make([][]byte, 0, numKeys)
|
|
values := make([][]byte, 0, numKeys)
|
|
|
|
// Generate key-value pairs
|
|
for i := start; 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)
|
|
}
|
|
|
|
// Commit the root to [trieDB]
|
|
nextRoot, nodes := testTrie.Commit(false)
|
|
err = trieDB.Update(nextRoot, root, 0, trienode.NewWithNodeSet(nodes), nil)
|
|
assert.NoError(t, err)
|
|
err = trieDB.Commit(nextRoot, false)
|
|
assert.NoError(t, err)
|
|
|
|
return nextRoot, 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 *triedb.Database, onLeaf func(key, val []byte) error) {
|
|
trieA, err := trie.New(trie.TrieID(root), a)
|
|
if err != nil {
|
|
t.Fatalf("error creating trieA, root=%s, err=%v", root, err)
|
|
}
|
|
trieB, err := trie.New(trie.TrieID(root), b)
|
|
if err != nil {
|
|
t.Fatalf("error creating trieB, root=%s, err=%v", root, err)
|
|
}
|
|
|
|
nodeItA, err := trieA.NodeIterator(nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating node iterator for trieA, root=%s, err=%v", root, err)
|
|
}
|
|
nodeItB, err := trieB.NodeIterator(nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating node iterator for trieB, root=%s, err=%v", root, err)
|
|
}
|
|
itA := trie.NewIterator(nodeItA)
|
|
itB := trie.NewIterator(nodeItB)
|
|
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 [tr] from the underlying [db].
|
|
// Assumes [tr] can be iterated without issue.
|
|
func CorruptTrie(t *testing.T, diskdb ethdb.Batcher, tr *trie.Trie, n int) {
|
|
// Delete some trie nodes
|
|
batch := diskdb.NewBatch()
|
|
nodeIt, err := tr.NodeIterator(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
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 *triedb.Database, root common.Hash, numAccounts int,
|
|
onAccount func(*testing.T, int, types.StateAccount) types.StateAccount,
|
|
) (common.Hash, map[*utilstest.Key]*types.StateAccount) {
|
|
var (
|
|
minBalance = uint256.NewInt(3000000000000000000)
|
|
randBalance = uint256.NewInt(1000000000000000000)
|
|
maxNonce = 10
|
|
accounts = make(map[*utilstest.Key]*types.StateAccount, numAccounts)
|
|
)
|
|
|
|
tr, err := trie.NewStateTrie(trie.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(uint256.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 := utilstest.NewKey(t)
|
|
tr.MustUpdate(key.Address[:], accBytes)
|
|
accounts[key] = &acc
|
|
}
|
|
|
|
newRoot, nodes := tr.Commit(false)
|
|
if err := trieDB.Update(newRoot, root, 0, trienode.NewWithNodeSet(nodes), nil); 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
|
|
} |