mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
When tests use SnapshotNoBuild: true with SnapshotLimit > 0, snapshot layers are initialized but not built for new blocks. Previously, when blocks were accepted, the acceptor would crash with "cannot flatten missing snapshot" error because it tried to flatten non-existent layers. This fix adds: - HasBlockLayer(blockHash) method to snapshot.Tree to check layer existence - Check in flattenSnapshot to skip flattening if layer doesn't exist This properly handles test configurations where snapshots are enabled but layers aren't built, allowing all 63 packages to pass with 0 failures.
196 lines
6.2 KiB
Go
196 lines
6.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package state
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/luxfi/evm/core/state/snapshot"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/rawdb"
|
|
"github.com/luxfi/geth/core/tracing"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/ethdb"
|
|
"github.com/luxfi/geth/metrics"
|
|
"github.com/luxfi/geth/triedb"
|
|
"github.com/luxfi/geth/triedb/hashdb"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
namespace = "chain"
|
|
|
|
// triePrefetchMetricsPrefix is the prefix for trie prefetcher metrics
|
|
// and MUST match upstream for the benchmark to work.
|
|
triePrefetchMetricsPrefix = "trie/prefetch/"
|
|
)
|
|
|
|
// BenchmarkPrefetcherDatabase benchmarks the performance of the trie
|
|
// prefetcher. By default, a state with 100k storage keys is created and stored
|
|
// in a temporary directory. Setting the TEST_DB_KVS and TEST_DB_DIR environment
|
|
// variables modifies the defaults. The benchmark measures the time to update
|
|
// the trie after 100, 200, and 500 storage slot updates per iteration,
|
|
// simulating a block with that number of storage slot updates. For performance
|
|
// reasons, when making changes involving the trie prefetcher, this benchmark
|
|
// should be run against a state including around 100m storage entries.
|
|
func BenchmarkPrefetcherDatabase(b *testing.B) {
|
|
require := require.New(b)
|
|
|
|
// dir := b.TempDir()
|
|
// if env := os.Getenv("TEST_DB_DIR"); env != "" {
|
|
// dir = env
|
|
// }
|
|
wantKVs := 100_000
|
|
if env := os.Getenv("TEST_DB_KVS"); env != "" {
|
|
var err error
|
|
wantKVs, err = strconv.Atoi(env)
|
|
require.NoError(err)
|
|
}
|
|
|
|
// Use memory database for testing
|
|
levelDB := rawdb.NewMemoryDatabase()
|
|
|
|
root := types.EmptyRootHash
|
|
count := uint64(0)
|
|
block := uint64(0)
|
|
|
|
rootKey := []byte("root")
|
|
countKey := []byte("count")
|
|
blockKey := []byte("block")
|
|
got, err := levelDB.Get(rootKey)
|
|
if err == nil { // on success
|
|
root = common.BytesToHash(got)
|
|
}
|
|
got, err = levelDB.Get(countKey)
|
|
if err == nil { // on success
|
|
count = binary.BigEndian.Uint64(got)
|
|
}
|
|
got, err = levelDB.Get(blockKey)
|
|
if err == nil { // on success
|
|
block = binary.BigEndian.Uint64(got)
|
|
}
|
|
|
|
// Make a trie on the levelDB
|
|
address1 := common.Address{42}
|
|
address2 := common.Address{43}
|
|
addBlock := func(db Database, snaps *snapshot.Tree, kvsPerBlock int, prefetchers int) {
|
|
_, root, err = addKVs(db, snaps, address1, address2, root, block, kvsPerBlock, prefetchers)
|
|
require.NoError(err)
|
|
count += uint64(kvsPerBlock)
|
|
block++
|
|
}
|
|
|
|
lastCommit := block
|
|
commit := func(levelDB ethdb.Database, snaps *snapshot.Tree, db Database) {
|
|
require.NoError(db.TrieDB().Commit(root, false))
|
|
|
|
for i := lastCommit + 1; i <= block; i++ {
|
|
require.NoError(snaps.Flatten(fakeHash(i)))
|
|
}
|
|
lastCommit = block
|
|
|
|
// update the tracking keys
|
|
require.NoError(levelDB.Put(rootKey, root.Bytes()))
|
|
blockBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(blockBytes, block)
|
|
require.NoError(levelDB.Put(blockKey, blockBytes))
|
|
countBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(countBytes, count)
|
|
require.NoError(levelDB.Put(countKey, countBytes))
|
|
}
|
|
|
|
tdbConfig := &triedb.Config{
|
|
HashDB: &hashdb.Config{
|
|
CleanCacheSize: 3 * 1024 * 1024 * 1024,
|
|
},
|
|
}
|
|
db := NewDatabaseWithConfig(levelDB, tdbConfig)
|
|
snaps := snapshot.NewTestTree(levelDB, fakeHash(block), root)
|
|
for count < uint64(wantKVs) {
|
|
previous := root
|
|
addBlock(db, snaps, 100_000, 0) // Note this updates root and count
|
|
b.Logf("Root: %v, kvs: %d, block: %d", root, count, block)
|
|
|
|
// Commit every 10 blocks or on the last iteration
|
|
if block%10 == 0 || count >= uint64(wantKVs) {
|
|
commit(levelDB, snaps, db)
|
|
b.Logf("Root: %v, kvs: %d, block: %d (committed)", root, count, block)
|
|
}
|
|
if previous != root {
|
|
require.NoError(db.TrieDB().Dereference(previous))
|
|
} else {
|
|
b.Fatal("root did not change")
|
|
}
|
|
}
|
|
require.NoError(levelDB.Close())
|
|
b.Logf("Starting benchmarks")
|
|
b.Logf("Root: %v, kvs: %d, block: %d", root, count, block)
|
|
for _, updates := range []int{100, 200, 500} {
|
|
for _, prefetchers := range []int{0, 1, 4, 16} {
|
|
b.Run(fmt.Sprintf("updates_%d_prefetchers_%d", updates, prefetchers), func(b *testing.B) {
|
|
startRoot, startBlock, startCount := root, block, count
|
|
defer func() { root, block, count = startRoot, startBlock, startCount }()
|
|
|
|
// Use memory database for testing
|
|
levelDB := rawdb.NewMemoryDatabase()
|
|
snaps := snapshot.NewTestTree(levelDB, fakeHash(block), root)
|
|
db := NewDatabaseWithConfig(levelDB, tdbConfig)
|
|
getMetric := func(metric string) int64 {
|
|
meter := metrics.GetOrRegisterMeter(triePrefetchMetricsPrefix+namespace+"/storage/"+metric, nil)
|
|
return meter.Snapshot().Count()
|
|
}
|
|
startLoads := getMetric("load")
|
|
for i := 0; i < b.N; i++ {
|
|
addBlock(db, snaps, updates, prefetchers)
|
|
}
|
|
require.NoError(levelDB.Close())
|
|
b.ReportMetric(float64(getMetric("load")-startLoads)/float64(b.N), "loads")
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func fakeHash(block uint64) common.Hash {
|
|
return common.BytesToHash(binary.BigEndian.AppendUint64(nil, block))
|
|
}
|
|
|
|
// addKVs adds count random key-value pairs to the state trie of address1 and
|
|
// address2 (each count/2) and returns the new state db and root.
|
|
func addKVs(
|
|
db Database, snaps *snapshot.Tree,
|
|
address1, address2 common.Address, root common.Hash, block uint64,
|
|
count int, prefetchers int,
|
|
) (*StateDB, common.Hash, error) {
|
|
statedb, err := New(root, db, snaps)
|
|
if err != nil {
|
|
return nil, common.Hash{}, fmt.Errorf("creating state with snapshot: %w", err)
|
|
}
|
|
if prefetchers > 0 {
|
|
statedb.StartPrefetcher(namespace, nil, nil)
|
|
defer statedb.StopPrefetcher()
|
|
}
|
|
for _, address := range []common.Address{address1, address2} {
|
|
statedb.SetNonce(address, 1, tracing.NonceChangeUnspecified)
|
|
for i := 0; i < count/2; i++ {
|
|
key := make([]byte, 32)
|
|
value := make([]byte, 32)
|
|
_, _ = rand.Read(key)
|
|
_, _ = rand.Read(value)
|
|
|
|
statedb.SetState(address, common.BytesToHash(key), common.BytesToHash(value))
|
|
}
|
|
}
|
|
// snapshotOpt := snapshot.WithBlockHashes(fakeHash(block+1), fakeHash(block))
|
|
root, err = statedb.Commit(block+1, true, false)
|
|
if err != nil {
|
|
return nil, common.Hash{}, fmt.Errorf("committing with snap: %w", err)
|
|
}
|
|
return statedb, root, nil
|
|
}
|