Files
evm/plugin/evm/vm_database.go
T
zeekay ff486d672e refactor: externalize node dependencies to dedicated packages
Major refactoring to reduce EVM's dependency on node:

External packages now used:
- github.com/luxfi/p2p v1.0.2 (network/p2p, gossip, lp118)
- github.com/luxfi/upgrade v1.0.0 (upgrade configs)
- github.com/luxfi/vms v0.1.0 (VM interfaces)
- github.com/luxfi/codec (codec utilities)
- github.com/luxfi/units (unit constants)
- github.com/luxfi/constants (network constants)
- github.com/luxfi/utils (wrappers, json codec)

Remaining node dependencies (minimal, for plugin model):
- node/vms/rpcchainvm (RPC plugin server)
- node/api (API utilities)
- node/utils/rpc (RPC utilities)
- node/version (version info)

This follows the principle: ONE canonical package for each concern.
2025-12-04 08:36:14 -08:00

259 lines
7.7 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
consensuscontext "github.com/luxfi/consensus/context"
luxdatabase "github.com/luxfi/database"
"github.com/luxfi/database/factory"
"github.com/luxfi/database/prefixdb"
"github.com/luxfi/database/versiondb"
"github.com/luxfi/evm/plugin/evm/config"
evmdatabase "github.com/luxfi/evm/plugin/evm/database"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/luxfi/log"
metrics "github.com/luxfi/metric"
"github.com/luxfi/constants"
)
const (
dbMetricsPrefix = "db"
meterDBGatherer = "meterdb"
)
type DatabaseConfig struct {
// If true, all writes are to memory and are discarded at shutdown.
ReadOnly bool `json:"readOnly"`
// Path to database
Path string `json:"path"`
// Name of the database type to use
Name string `json:"name"`
// Config bytes (JSON) for the database
// See relevant (pebbledb, leveldb) config options
Config []byte `json:"-"`
}
// initializeDBs initializes the databases used by the VM.
// If [useStandaloneDB] is true, the chain will use a standalone database for its state.
// Otherwise, the chain will use the provided [avaDB] for its state.
func (vm *VM) initializeDBs(avaDB luxdatabase.Database) error {
db := avaDB
// Check if standalone database is explicitly enabled via config
// (this takes precedence over the UnitTestID check)
useStandAloneDB := false
if vm.config.UseStandaloneDatabase != nil && vm.config.UseStandaloneDatabase.Bool() {
useStandAloneDB = true
} else if consensuscontext.GetNetworkID(vm.ctx) != constants.UnitTestID {
// If not explicitly set, check if we should use standalone database
// based on the accepted block database state (skip for unit tests)
acceptedDB := prefixdb.New(acceptedPrefix, avaDB)
var err error
useStandAloneDB, err = vm.useStandaloneDatabase(acceptedDB)
if err != nil {
return err
}
}
if useStandAloneDB {
// If we are using a standalone database, we need to create a new database
// for the chain state.
// TODO: Fix GetChainDataDir with new consensus interface
// Use the root path for chaindata
chainDataDir := filepath.Join("/tmp", "chaindata")
dbConfig, err := getDatabaseConfig(vm.config, chainDataDir)
if err != nil {
return err
}
log.Info("Using standalone database for the chain state", "DatabaseConfig", dbConfig)
// Create a logger adapter for newStandaloneDatabase
logger := log.NoLog{}
// Pass nil for metrics as it's optional
db, err = newStandaloneDatabase(dbConfig, nil, logger)
if err != nil {
return err
}
vm.usingStandaloneDB = true
}
// Use NewNested rather than New so that the structure of the database
// remains the same regardless of the provided baseDB type.
vm.chaindb = rawdb.NewDatabase(evmdatabase.WrapDatabase(prefixdb.NewNested(ethDBPrefix, db)))
vm.versiondb = versiondb.New(db)
vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.versiondb)
vm.metadataDB = prefixdb.New(metadataPrefix, vm.versiondb)
vm.db = db
// Note warpDB and validatorsDB are not part of versiondb because it is not necessary
// that they are committed to the database atomically with
// the last accepted block.
// [warpDB] is used to store warp message signatures
// set to a prefixDB with the prefix [warpPrefix]
vm.warpDB = prefixdb.New(warpPrefix, db)
// [validatorsDB] is used to store the current validator set and uptimes
// set to a prefixDB with the prefix [validatorsDBPrefix]
vm.validatorsDB = prefixdb.New(validatorsDBPrefix, db)
return nil
}
func (vm *VM) inspectDatabases() error {
start := time.Now()
log.Info("Starting database inspection")
if err := rawdb.InspectDatabase(vm.chaindb, nil, nil); err != nil {
return err
}
if err := inspectDB(vm.acceptedBlockDB, "acceptedBlockDB"); err != nil {
return err
}
if err := inspectDB(vm.metadataDB, "metadataDB"); err != nil {
return err
}
if err := inspectDB(vm.warpDB, "warpDB"); err != nil {
return err
}
if err := inspectDB(vm.validatorsDB, "validatorsDB"); err != nil {
return err
}
log.Info("Completed database inspection", "elapsed", time.Since(start))
return nil
}
// useStandaloneDatabase returns true if the chain can and should use a standalone database
// other than given by [db] in Initialize()
func (vm *VM) useStandaloneDatabase(acceptedDB luxdatabase.Database) (bool, error) {
// no config provided, use default
standaloneDBFlag := vm.config.UseStandaloneDatabase
if standaloneDBFlag != nil {
return standaloneDBFlag.Bool(), nil
}
// check if the chain can use a standalone database
_, err := acceptedDB.Get(lastAcceptedKey)
if err == luxdatabase.ErrNotFound {
// If there is nothing in the database, we can use the standalone database
return true, nil
}
return false, err
}
// getDatabaseConfig returns the database configuration for the chain
// to be used by separate, standalone database.
func getDatabaseConfig(config config.Config, chainDataDir string) (DatabaseConfig, error) {
var (
configBytes []byte
err error
)
if len(config.DatabaseConfigContent) != 0 {
dbConfigContent := config.DatabaseConfigContent
configBytes, err = base64.StdEncoding.DecodeString(dbConfigContent)
if err != nil {
return DatabaseConfig{}, fmt.Errorf("unable to decode base64 content: %w", err)
}
} else if len(config.DatabaseConfigFile) != 0 {
configPath := config.DatabaseConfigFile
configBytes, err = os.ReadFile(configPath)
if err != nil {
return DatabaseConfig{}, err
}
}
dbPath := filepath.Join(chainDataDir, "db")
if len(config.DatabasePath) != 0 {
dbPath = config.DatabasePath
}
return DatabaseConfig{
Name: config.DatabaseType,
ReadOnly: config.DatabaseReadOnly,
Path: dbPath,
Config: configBytes,
}, nil
}
func inspectDB(db luxdatabase.Database, label string) error {
it := db.NewIterator()
defer it.Release()
var (
count int64
start = time.Now()
logged = time.Now()
// Totals
total common.StorageSize
)
// Inspect key-value database first.
for it.Next() {
var (
key = it.Key()
size = common.StorageSize(len(key) + len(it.Value()))
)
total += size
count++
if count%1000 == 0 && time.Since(logged) > 8*time.Second {
log.Info("Inspecting database", "label", label, "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
}
// Display the database statistic.
log.Info("Database statistics", "label", label, "total", total.String(), "count", count)
return nil
}
func newStandaloneDatabase(dbConfig DatabaseConfig, gatherer metrics.MultiGatherer, logger log.Logger) (luxdatabase.Database, error) {
dbPath := filepath.Join(dbConfig.Path, dbConfig.Name)
// Use log.Root() instead of trying to adapt the logger
logAdapter := log.Root()
dbConfigBytes := dbConfig.Config
// If the database is pebble, we need to set the config
// to use no sync. Sync mode in pebble has an issue with OSs like MacOS.
if dbConfig.Name == "pebbledb" {
// Create a default config with no sync
cfg := map[string]interface{}{
"sync": false,
}
// If config bytes provided, unmarshal and update
if len(dbConfigBytes) > 0 {
if err := json.Unmarshal(dbConfigBytes, &cfg); err != nil {
return nil, err
}
}
// Ensure sync is false
cfg["sync"] = false
var err error
// Marshal the config back to bytes to ensure that new defaults are applied
dbConfigBytes, err = json.Marshal(cfg)
if err != nil {
return nil, err
}
}
db, err := factory.New(
dbConfig.Name,
dbPath,
dbConfig.ReadOnly,
dbConfigBytes,
gatherer,
logAdapter,
dbMetricsPrefix,
meterDBGatherer,
)
if err != nil {
return nil, fmt.Errorf("couldn't create database: %w", err)
}
return db, nil
}