Skip to content

Commit

Permalink
add standalone dbs and configs
Browse files Browse the repository at this point in the history
  • Loading branch information
ceyonur committed Sep 27, 2024
1 parent e8add1b commit 6e6b96f
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 31 deletions.
11 changes: 11 additions & 0 deletions plugin/evm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"time"

"github.com/ava-labs/avalanchego/database/pebbledb"
"github.com/ava-labs/subnet-evm/core/txpool/legacypool"
"github.com/ava-labs/subnet-evm/eth"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -60,6 +61,7 @@ const (
// - state sync time: ~6 hrs.
defaultStateSyncMinBlocks = 300_000
defaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request
defaultDBType = pebbledb.Name
)

var (
Expand Down Expand Up @@ -225,6 +227,14 @@ type Config struct {

// RPC settings
HttpBodyLimit uint64 `json:"http-body-limit"`

// Database settings
UseStandaloneDatabase *bool `json:"use-standalone-database"`
DatabaseConfigContent string `json:"database-config"`
DatabaseConfigFile string `json:"database-config-file"`
DatabaseType string `json:"database-type"`
DatabasePath string `json:"database-path"`
DatabaseReadOnly bool `json:"database-read-only"`
}

// EthAPIs returns an array of strings representing the Eth APIs that should be enabled
Expand Down Expand Up @@ -284,6 +294,7 @@ func (c *Config) SetDefaults() {
c.StateSyncRequestSize = defaultStateSyncRequestSize
c.AllowUnprotectedTxHashes = defaultAllowUnprotectedTxHashes
c.AcceptedCacheSize = defaultAcceptedCacheSize
c.DatabaseType = defaultDBType
}

func (d *Duration) UnmarshalJSON(data []byte) (err error) {
Expand Down
78 changes: 78 additions & 0 deletions plugin/evm/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package database

import (
"fmt"
"path/filepath"

"github.com/ava-labs/avalanchego/api/metrics"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/leveldb"
"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/database/meterdb"
"github.com/ava-labs/avalanchego/database/pebbledb"
"github.com/ava-labs/avalanchego/database/versiondb"
"github.com/ava-labs/avalanchego/node"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/metric"
"github.com/ethereum/go-ethereum/log"
)

var dbNamespace = "subnet_evm" + metric.NamespaceSeparator + "db"

// New returns a new database instance with the provided configuration
func New(gatherer metrics.MultiGatherer, dbConfig node.DatabaseConfig, logger logging.Logger) (database.Database, error) {
log.Info("initializing database")
dbRegisterer, err := metrics.MakeAndRegister(
gatherer,
dbNamespace,
)
if err != nil {
return nil, err
}

var db database.Database
// start the db
switch dbConfig.Name {
case leveldb.Name:
dbPath := filepath.Join(dbConfig.Path, leveldb.Name)
db, err = leveldb.New(dbPath, dbConfig.Config, logger, dbRegisterer)
if err != nil {
return nil, fmt.Errorf("couldn't create %s at %s: %w", leveldb.Name, dbPath, err)
}
case memdb.Name:
db = memdb.New()
case pebbledb.Name:
dbPath := filepath.Join(dbConfig.Path, pebbledb.Name)
db, err = pebbledb.New(dbPath, dbConfig.Config, logger, dbRegisterer)
if err != nil {
return nil, fmt.Errorf("couldn't create %s at %s: %w", pebbledb.Name, dbPath, err)
}
default:
return nil, fmt.Errorf(
"db-type was %q but should have been one of {%s, %s, %s}",
dbConfig.Name,
leveldb.Name,
memdb.Name,
pebbledb.Name,
)
}

if dbConfig.ReadOnly && dbConfig.Name != memdb.Name {
db = versiondb.New(db)
}

meterDBReg, err := metrics.MakeAndRegister(
gatherer,
"all",
)
if err != nil {
return nil, err
}

db, err = meterdb.New(meterDBReg, db)
if err != nil {
return nil, err
}

return db, nil
}
147 changes: 118 additions & 29 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package evm

import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand All @@ -20,6 +21,7 @@ import (
"github.com/ava-labs/avalanchego/network/p2p/gossip"
"github.com/prometheus/client_golang/prometheus"

avalancheNode "github.com/ava-labs/avalanchego/node"
"github.com/ava-labs/subnet-evm/commontype"
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/constants"
Expand All @@ -35,6 +37,7 @@ import (
"github.com/ava-labs/subnet-evm/node"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/peer"
"github.com/ava-labs/subnet-evm/plugin/evm/database"
"github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ava-labs/subnet-evm/triedb/hashdb"
Expand Down Expand Up @@ -66,7 +69,6 @@ import (
avalancheRPC "github.com/gorilla/rpc/v2"

"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/prefixdb"
"github.com/ava-labs/avalanchego/database/versiondb"
"github.com/ava-labs/avalanchego/ids"
Expand All @@ -81,6 +83,7 @@ import (

commonEng "github.com/ava-labs/avalanchego/snow/engine/common"

avalanchedatabase "github.com/ava-labs/avalanchego/database"
avalancheUtils "github.com/ava-labs/avalanchego/utils"
avalancheJSON "github.com/ava-labs/avalanchego/utils/json"
)
Expand Down Expand Up @@ -193,18 +196,17 @@ type VM struct {
db *versiondb.Database

// metadataDB is used to store one off keys.
metadataDB database.Database
metadataDB avalanchedatabase.Database

// [chaindb] is the database supplied to the Ethereum backend
chaindb ethdb.Database

// [acceptedBlockDB] is the database to store the last accepted
// block.
acceptedBlockDB database.Database

acceptedBlockDB avalanchedatabase.Database
// [warpDB] is used to store warp message signatures
// set to a prefixDB with the prefix [warpPrefix]
warpDB database.Database
warpDB avalanchedatabase.Database

toEngine chan<- commonEng.Message

Expand Down Expand Up @@ -251,7 +253,7 @@ type VM struct {
func (vm *VM) Initialize(
_ context.Context,
chainCtx *snow.Context,
db database.Database,
db avalanchedatabase.Database,
genesisBytes []byte,
upgradeBytes []byte,
configBytes []byte,
Expand Down Expand Up @@ -303,16 +305,15 @@ func (vm *VM) Initialize(

vm.toEngine = toEngine
vm.shutdownChan = make(chan struct{}, 1)
// 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(Database{prefixdb.NewNested(ethDBPrefix, db)})
vm.db = versiondb.New(db)
vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db)
vm.metadataDB = prefixdb.New(metadataPrefix, vm.db)
// Note warpDB is not part of versiondb because it is not necessary
// that warp signatures are committed to the database atomically with
// the last accepted block.
vm.warpDB = prefixdb.New(warpPrefix, db)

if err := vm.initializeMetrics(); err != nil {
return fmt.Errorf("failed to initialize metrics: %w", err)
}

// Initialize the database
if err := vm.initializeDBs(db); err != nil {
return fmt.Errorf("failed to initialize databases: %w", err)
}

if vm.config.InspectDatabase {
start := time.Now()
Expand Down Expand Up @@ -463,10 +464,6 @@ func (vm *VM) Initialize(
}
log.Info(fmt.Sprintf("lastAccepted = %s", lastAcceptedHash))

if err := vm.initializeMetrics(); err != nil {
return err
}

// initialize peer network
if vm.p2pSender == nil {
vm.p2pSender = appSender
Expand Down Expand Up @@ -797,8 +794,8 @@ func (vm *VM) initBlockBuilding() error {
// setAppRequestHandlers sets the request handlers for the VM to serve state sync
// requests.
func (vm *VM) setAppRequestHandlers() {
// Create separate EVM TrieDB (read only) for serving leafs requests.
// We create a separate TrieDB here, so that it has a separate cache from the one
// Create standalone EVM TrieDB (read only) for serving leafs requests.
// We create a standalone TrieDB here, so that it has a standalone cache from the one
// used by the node when processing blocks.
evmTrieDB := triedb.NewDatabase(
vm.chaindb,
Expand Down Expand Up @@ -910,10 +907,10 @@ func (vm *VM) ParseEthBlock(b []byte) (*types.Block, error) {
// by ChainState.
func (vm *VM) getBlock(_ context.Context, id ids.ID) (snowman.Block, error) {
ethBlock := vm.blockChain.GetBlockByHash(common.Hash(id))
// If [ethBlock] is nil, return [database.ErrNotFound] here
// If [ethBlock] is nil, return [avalanchedatabase.ErrNotFound] here
// so that the miss is considered cacheable.
if ethBlock == nil {
return nil, database.ErrNotFound
return nil, avalanchedatabase.ErrNotFound
}
// Note: the status of block is set by ChainState
return vm.newBlock(ethBlock), nil
Expand All @@ -935,7 +932,7 @@ func (vm *VM) GetAcceptedBlock(ctx context.Context, blkID ids.ID) (snowman.Block

if acceptedBlkID != blkID {
// The provided block is not accepted.
return nil, database.ErrNotFound
return nil, avalanchedatabase.ErrNotFound
}
return blk, nil
}
Expand All @@ -961,17 +958,17 @@ func (vm *VM) VerifyHeightIndex(context.Context) error {

// GetBlockIDAtHeight returns the canonical block at [height].
// Note: the engine assumes that if a block is not found at [height], then
// [database.ErrNotFound] will be returned. This indicates that the VM has state
// [avalanchedatabase.ErrNotFound] will be returned. This indicates that the VM has state
// synced and does not have all historical blocks available.
func (vm *VM) GetBlockIDAtHeight(_ context.Context, height uint64) (ids.ID, error) {
lastAcceptedBlock := vm.LastAcceptedBlock()
if lastAcceptedBlock.Height() < height {
return ids.ID{}, database.ErrNotFound
return ids.ID{}, avalanchedatabase.ErrNotFound
}

hash := vm.blockChain.GetCanonicalHash(height)
if hash == (common.Hash{}) {
return ids.ID{}, database.ErrNotFound
return ids.ID{}, avalanchedatabase.ErrNotFound
}
return ids.ID(hash), nil
}
Expand Down Expand Up @@ -1128,7 +1125,7 @@ func (vm *VM) readLastAccepted() (common.Hash, uint64, error) {
// initialize state with the genesis block.
lastAcceptedBytes, lastAcceptedErr := vm.acceptedBlockDB.Get(lastAcceptedKey)
switch {
case lastAcceptedErr == database.ErrNotFound:
case lastAcceptedErr == avalanchedatabase.ErrNotFound:
// If there is nothing in the database, return the genesis block hash and height
return vm.genesisHash, 0, nil
case lastAcceptedErr != nil:
Expand Down Expand Up @@ -1181,3 +1178,95 @@ func attachEthService(handler *rpc.Server, apis []rpc.API, names []string) error

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 avalanchedatabase.Database) (bool, error) {
// no config provided, use default
standaloneDBFlag := vm.config.UseStandaloneDatabase
if standaloneDBFlag == nil {
// check if the chain can use a standalone database
_, lastAcceptedErr := acceptedDB.Get(lastAcceptedKey)
if lastAcceptedErr == avalanchedatabase.ErrNotFound {
// If there is nothing in the database, we can use the standalone database
return true, nil
} else {
return false, lastAcceptedErr
}
}
return *standaloneDBFlag, nil
}

// getDatabaseConfig returns the database configuration for the chain
// to be used by sepearate, standalone database.
func getDatabaseConfig(config Config, chainDataDir string) (avalancheNode.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 avalancheNode.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 avalancheNode.DatabaseConfig{}, err
}
}

dbPath := filepath.Join(chainDataDir, "db")
if len(config.DatabasePath) != 0 {
dbPath = config.DatabasePath
}

return avalancheNode.DatabaseConfig{
Name: config.DatabaseType,
ReadOnly: config.DatabaseReadOnly,
Path: dbPath,
Config: configBytes,
}, nil
}

// 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 avalanchedatabase.Database) error {
// first initialize the accepted block database to check if we need to use a standalone database
verDB := versiondb.New(avaDB)
acceptedDB := prefixdb.New(acceptedPrefix, verDB)
useStandAloneDB, err := vm.useStandaloneDatabase(acceptedDB)
if err != nil {
return err
}
db := avaDB
if useStandAloneDB {
// If we are using a standalone database, we need to create a new database
// for the chain state.
dbConfig, err := getDatabaseConfig(vm.config, vm.ctx.ChainDataDir)
if err != nil {
return err
}
log.Info("Using standalone database for the chain state", "DatabaseConfig", dbConfig)
db, err = database.New(vm.ctx.Metrics, dbConfig, vm.ctx.Log)
if err != nil {
return err
}
}
// 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(Database{prefixdb.NewNested(ethDBPrefix, db)})
vm.db = versiondb.New(db)
vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, db)
vm.metadataDB = prefixdb.New(metadataPrefix, db)
// Note warpDB is not part of versiondb because it is not necessary
// that warp signatures 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)
return nil
}
2 changes: 1 addition & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ fi

# Build Subnet EVM, which is run as a subprocess
echo "Building Subnet EVM @ GitCommit: $SUBNET_EVM_COMMIT at $BINARY_PATH"
go build -ldflags "-X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$SUBNET_EVM_COMMIT $STATIC_LD_FLAGS" -o "$BINARY_PATH" "plugin/"*.go
go build -ldflags "-s -w -X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$SUBNET_EVM_COMMIT $STATIC_LD_FLAGS" -o "$BINARY_PATH" "plugin/"*.go
2 changes: 1 addition & 1 deletion scripts/run_simulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ run_simulator() {
#################################
echo "building simulator"
pushd ./cmd/simulator
go build -o ./simulator main/*.go
go build -ldflags "-s -w" -o ./simulator main/*.go
echo

popd
Expand Down

0 comments on commit 6e6b96f

Please sign in to comment.