Files
2024-01-03 09:44:09 +01:00

139 lines
5.1 KiB
Go

// (c) 2021-2024, Lux Partners Limited. All rights reserved.
// See the file LICENSE for licensing terms.
package statesync
import (
"bytes"
"math/rand"
"testing"
"github.com/luxdefi/evm/accounts/keystore"
"github.com/luxdefi/evm/core/rawdb"
"github.com/luxdefi/evm/core/state/snapshot"
"github.com/luxdefi/evm/core/types"
"github.com/luxdefi/evm/ethdb"
"github.com/luxdefi/evm/trie"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
)
// assertDBConsistency checks [serverTrieDB] and [clientTrieDB] have the same EVM state trie at [root],
// and that [clientTrieDB.DiskDB] has corresponding account & snapshot values.
// Also verifies any code referenced by the EVM state is present in [clientTrieDB] and the hash is correct.
func assertDBConsistency(t testing.TB, root common.Hash, clientDB ethdb.Database, serverTrieDB, clientTrieDB *trie.Database) {
numSnapshotAccounts := 0
accountIt := rawdb.IterateAccountSnapshots(clientDB)
defer accountIt.Release()
for accountIt.Next() {
if !bytes.HasPrefix(accountIt.Key(), rawdb.SnapshotAccountPrefix) || len(accountIt.Key()) != len(rawdb.SnapshotAccountPrefix)+common.HashLength {
continue
}
numSnapshotAccounts++
}
if err := accountIt.Error(); err != nil {
t.Fatal(err)
}
trieAccountLeaves := 0
trie.AssertTrieConsistency(t, root, serverTrieDB, clientTrieDB, func(key, val []byte) error {
trieAccountLeaves++
accHash := common.BytesToHash(key)
var acc types.StateAccount
if err := rlp.DecodeBytes(val, &acc); err != nil {
return err
}
// check snapshot consistency
snapshotVal := rawdb.ReadAccountSnapshot(clientDB, accHash)
expectedSnapshotVal := snapshot.SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
assert.Equal(t, expectedSnapshotVal, snapshotVal)
// check code consistency
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) {
codeHash := common.BytesToHash(acc.CodeHash)
code := rawdb.ReadCode(clientDB, codeHash)
actualHash := crypto.Keccak256Hash(code)
assert.NotZero(t, len(code))
assert.Equal(t, codeHash, actualHash)
}
if acc.Root == types.EmptyRootHash {
return nil
}
storageIt := rawdb.IterateStorageSnapshots(clientDB, accHash)
defer storageIt.Release()
snapshotStorageKeysCount := 0
for storageIt.Next() {
snapshotStorageKeysCount++
}
storageTrieLeavesCount := 0
// check storage trie and storage snapshot consistency
trie.AssertTrieConsistency(t, acc.Root, serverTrieDB, clientTrieDB, func(key, val []byte) error {
storageTrieLeavesCount++
snapshotVal := rawdb.ReadStorageSnapshot(clientDB, accHash, common.BytesToHash(key))
assert.Equal(t, val, snapshotVal)
return nil
})
assert.Equal(t, storageTrieLeavesCount, snapshotStorageKeysCount)
return nil
})
// Check that the number of accounts in the snapshot matches the number of leaves in the accounts trie
assert.Equal(t, trieAccountLeaves, numSnapshotAccounts)
}
func fillAccountsWithStorage(t *testing.T, serverDB ethdb.Database, serverTrieDB *trie.Database, root common.Hash, numAccounts int) common.Hash {
newRoot, _ := trie.FillAccounts(t, serverTrieDB, root, numAccounts, func(t *testing.T, index int, account types.StateAccount) types.StateAccount {
codeBytes := make([]byte, 256)
_, err := rand.Read(codeBytes)
if err != nil {
t.Fatalf("error reading random code bytes: %v", err)
}
codeHash := crypto.Keccak256Hash(codeBytes)
rawdb.WriteCode(serverDB, codeHash, codeBytes)
account.CodeHash = codeHash[:]
// now create state trie
numKeys := 16
account.Root, _, _ = trie.GenerateTrie(t, serverTrieDB, numKeys, common.HashLength)
return account
})
return newRoot
}
// FillAccountsWithOverlappingStorage adds [numAccounts] randomly generated accounts to the secure trie at [root]
// and commits it to [trieDB]. For each 3 accounts created:
// - One does not have a storage trie,
// - One has a storage trie shared with other accounts (total number of shared storage tries [numOverlappingStorageRoots]),
// - One has a uniquely generated storage trie,
// returns the new trie root and a map of funded keys to StateAccount structs.
func FillAccountsWithOverlappingStorage(
t *testing.T, trieDB *trie.Database, root common.Hash, numAccounts int, numOverlappingStorageRoots int,
) (common.Hash, map[*keystore.Key]*types.StateAccount) {
storageRoots := make([]common.Hash, 0, numOverlappingStorageRoots)
for i := 0; i < numOverlappingStorageRoots; i++ {
storageRoot, _, _ := trie.GenerateTrie(t, trieDB, 16, common.HashLength)
storageRoots = append(storageRoots, storageRoot)
}
storageRootIndex := 0
return trie.FillAccounts(t, trieDB, root, numAccounts, func(t *testing.T, i int, account types.StateAccount) types.StateAccount {
switch i % 3 {
case 0: // unmodified account
case 1: // account with overlapping storage root
account.Root = storageRoots[storageRootIndex%numOverlappingStorageRoots]
storageRootIndex++
case 2: // account with unique storage root
account.Root, _, _ = trie.GenerateTrie(t, trieDB, 16, common.HashLength)
}
return account
})
}