mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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.
259 lines
7.7 KiB
Go
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
|
|
}
|