Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ethdb: pebble backend (64bit platforms only) #26517

Merged
merged 21 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions cmd/geth/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
)

Expand Down Expand Up @@ -70,6 +72,7 @@ var customGenesisTests = []struct {
// Tests that initializing Geth with a custom genesis block and chain definitions
// work properly.
func TestCustomGenesis(t *testing.T) {
t.Parallel()
for i, tt := range customGenesisTests {
// Create a temporary data directory to use and inspect later
datadir := t.TempDir()
Expand All @@ -90,3 +93,101 @@ func TestCustomGenesis(t *testing.T) {
geth.ExpectExit()
}
}

// TestCustomBackend that the backend selection and detection (leveldb vs pebble) works properly.
func TestCustomBackend(t *testing.T) {
t.Parallel()
// Test pebble, but only on 64-bit platforms
if strconv.IntSize != 64 {
t.Skip("Custom backends are only available on 64-bit platform")
}
genesis := `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000001338",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {}
}`
type backendTest struct {
initArgs []string
initExpect string
execArgs []string
execExpect string
}
testfunc := func(t *testing.T, tt backendTest) error {
// Create a temporary data directory to use and inspect later
datadir := t.TempDir()

// Initialize the data directory with the custom genesis block
json := filepath.Join(datadir, "genesis.json")
if err := os.WriteFile(json, []byte(genesis), 0600); err != nil {
return fmt.Errorf("failed to write genesis file: %v", err)
}
{ // Init
args := append(tt.initArgs, "--datadir", datadir, "init", json)
geth := runGeth(t, args...)
geth.ExpectRegexp(tt.initExpect)
geth.ExpectExit()
}
{ // Exec + query
args := append(tt.execArgs, "--networkid", "1337", "--syncmode=full", "--cache", "16",
"--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0",
"--nodiscover", "--nat", "none", "--ipcdisable",
"--exec", "eth.getBlock(0).nonce", "console")
geth := runGeth(t, args...)
geth.ExpectRegexp(tt.execExpect)
geth.ExpectExit()
}
return nil
}
for i, tt := range []backendTest{
{ // When not specified, it should default to leveldb
execArgs: []string{"--backingdb", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit leveldb
initArgs: []string{"--backingdb", "leveldb"},
execArgs: []string{"--backingdb", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit leveldb first, then autodiscover
initArgs: []string{"--backingdb", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit pebble
initArgs: []string{"--backingdb", "pebble"},
execArgs: []string{"--backingdb", "pebble"},
execExpect: "0x0000000000001338",
},
{ // Explicit pebble, then auto-discover
initArgs: []string{"--backingdb", "pebble"},
execExpect: "0x0000000000001338",
},
{ // Can't start pebble on top of leveldb
initArgs: []string{"--backingdb", "leveldb"},
execArgs: []string{"--backingdb", "pebble"},
execExpect: `Fatal: Failed to register the Ethereum service: backingdb choice was pebble but found pre-existing leveldb database in specified data directory`,
},
{ // Can't start leveldb on top of pebble
initArgs: []string{"--backingdb", "pebble"},
execArgs: []string{"--backingdb", "leveldb"},
execExpect: `Fatal: Failed to register the Ethereum service: backingdb choice was leveldb but found pre-existing pebble database in specified data directory`,
},
{ // Reject invalid backend choice
initArgs: []string{"--backingdb", "mssql"},
initExpect: `Fatal: invalid choice for backing db: mssql`,
// Since the init fails, this will return the (default) mainnet genesis
// block nonce
execExpect: `0x0000000000000042`,
},
} {
if err := testfunc(t, tt); err != nil {
t.Fatalf("test %d-leveldb: %v", i, err)
}
}
}
20 changes: 20 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ var (
Usage: "URL for remote database",
Category: flags.LoggingCategory,
}
BackingDBFlag = &cli.StringFlag{
Name: "backingdb",
Usage: "Backing database implementation to use",
holiman marked this conversation as resolved.
Show resolved Hide resolved
Value: "leveldb",
Category: flags.EthCategory,
}
AncientFlag = &flags.DirectoryFlag{
Name: "datadir.ancient",
Usage: "Root directory for ancient data (default = inside chaindata)",
Expand Down Expand Up @@ -1015,6 +1021,12 @@ var (
}
)

func init() {
if rawdb.PebbleEnabled {
DatabasePathFlags = append(DatabasePathFlags, BackingDBFlag)
}
}

// MakeDataDir retrieves the currently requested data directory, terminating
// if none (or the empty string) is specified. If the node is starting a testnet,
// then a subdirectory of the specified datadir will be used.
Expand Down Expand Up @@ -1497,6 +1509,14 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
if ctx.IsSet(InsecureUnlockAllowedFlag.Name) {
cfg.InsecureUnlockAllowed = ctx.Bool(InsecureUnlockAllowedFlag.Name)
}
if ctx.IsSet(BackingDBFlag.Name) {
backingDB := ctx.String(BackingDBFlag.Name)
if backingDB != "leveldb" && backingDB != "pebble" {
Fatalf("invalid choice for backing db: %s", backingDB)
holiman marked this conversation as resolved.
Show resolved Hide resolved
}
log.Info(fmt.Sprintf("Using %s as backing db", backingDB))
cfg.BackingDB = backingDB
}
}

func setSmartCard(ctx *cli.Context, cfg *node.Config) {
Expand Down
22 changes: 18 additions & 4 deletions core/blockchain_repair_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1756,7 +1756,10 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -1829,7 +1832,11 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err = rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})

if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down Expand Up @@ -1884,7 +1891,11 @@ func TestIssue23496(t *testing.T) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})

if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -1944,7 +1955,10 @@ func TestIssue23496(t *testing.T) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err = rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down
5 changes: 4 additions & 1 deletion core/blockchain_sethead_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1956,7 +1956,10 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down
11 changes: 9 additions & 2 deletions core/blockchain_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -250,7 +253,11 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false)
newdb, err := rawdb.Open(rawdb.OpenOptions{
Directory: snaptest.datadir,
AncientsDirectory: snaptest.datadir,
})

if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down
78 changes: 71 additions & 7 deletions core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync/atomic"
"time"
Expand Down Expand Up @@ -302,19 +303,82 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r
if err != nil {
return nil, err
}
log.Info("Using LevelDB as the backing database")
return NewDatabase(db), nil
}

// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
// freezer moving immutable chain segments into cold storage. The passed ancient
// indicates the path of root ancient directory where the chain freezer can be
// opened.
func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
kvdb, err := leveldb.New(file, cache, handles, namespace, readonly)
const (
dbPebble = "pebble"
dbLeveldb = "leveldb"
)

// hasPreexistingDb checks the given data directory whether a database is already
// instantiated at that location, and if so, returns the type of database (or the
// empty string).
func hasPreexistingDb(path string) string {
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
return "" // No pre-existing db
}
if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil {
if err != nil {
panic(err) // only possible if the pattern is malformed
}
return dbPebble
}
return dbLeveldb
}

// OpenOptions contains the options to apply when opening a database.
// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used.
type OpenOptions struct {
Type string // "leveldb" | "pebble"
Directory string // the datadir
AncientsDirectory string // the ancients-dir
Namespace string // the namespace for database relevant metrics
Cache int // the capacity(in megabytes) of the data caching
Handles int // the capacity of the open files caching
holiman marked this conversation as resolved.
Show resolved Hide resolved
ReadOnly bool
}

// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
//
// type == null type != null
// +----------------------------------------
// db is non-existent | leveldb default | specified type
// db is existent | from db | specified type (if compatible)
func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
existingDb := hasPreexistingDb(o.Directory)
if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb {
return nil, fmt.Errorf("backingdb choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
}
if o.Type == dbPebble || existingDb == dbPebble {
if PebbleEnabled {
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
} else {
return nil, errors.New("backingdb choice not supported on this platform")
holiman marked this conversation as resolved.
Show resolved Hide resolved
}
}
if len(o.Type) != 0 && o.Type != dbLeveldb {
return nil, fmt.Errorf("unknown backend %v", o.Type)
holiman marked this conversation as resolved.
Show resolved Hide resolved
}
// Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly
return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
}

// Open opens both a disk-based key-value database such as leveldb or pebble, but also
// integrates it with a freezer database -- if the AncientDir option has been
// set on the provided OpenOptions.
// The passed o.AncientDir indicates the path of root ancient directory where
// the chain freezer can be opened.
func Open(o OpenOptions) (ethdb.Database, error) {
kvdb, err := openKeyValueDatabase(o)
if err != nil {
return nil, err
}
frdb, err := NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly)
if len(o.AncientsDirectory) == 0 {
return kvdb, nil
}
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly)
if err != nil {
kvdb.Close()
return nil, err
Expand Down
39 changes: 39 additions & 0 deletions core/rawdb/databases_64bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>

//go:build arm64 || amd64

package rawdb

import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/pebble"
"github.com/ethereum/go-ethereum/log"
)

// Pebble is unsuported on 32bit architecture
const PebbleEnabled = true

// NewPebbleDBDatabase creates a persistent key-value database without a freezer
// moving immutable chain segments into cold storage.
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
db, err := pebble.New(file, cache, handles, namespace, readonly)
if err != nil {
return nil, err
}
log.Info("Using Pebble as the backing database")
holiman marked this conversation as resolved.
Show resolved Hide resolved
return NewDatabase(db), nil
}
Loading