diff --git a/cmd/conf/database.go b/cmd/conf/database.go index 6fde00579f..a75cca77d5 100644 --- a/cmd/conf/database.go +++ b/cmd/conf/database.go @@ -32,7 +32,7 @@ var PersistentConfigDefault = PersistentConfig{ LogDir: "", Handles: 512, Ancient: "", - DBEngine: "leveldb", + DBEngine: "", // auto-detect database type based on the db dir contents Pebble: PebbleConfigDefault, } @@ -42,7 +42,7 @@ func PersistentConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".log-dir", PersistentConfigDefault.LogDir, "directory to store log file") f.Int(prefix+".handles", PersistentConfigDefault.Handles, "number of file descriptor handles to use for the database") f.String(prefix+".ancient", PersistentConfigDefault.Ancient, "directory of ancient where the chain freezer can be opened") - f.String(prefix+".db-engine", PersistentConfigDefault.DBEngine, "backing database implementation to use ('leveldb' or 'pebble')") + f.String(prefix+".db-engine", PersistentConfigDefault.DBEngine, "backing database implementation to use. If set to empty string the database type will be autodetected and if no pre-existing database is found it will default to creating new pebble database ('leveldb', 'pebble' or '' = auto-detect)") PebbleConfigAddOptions(prefix+".pebble", f) } @@ -97,11 +97,12 @@ func DatabaseInDirectory(path string) bool { } func (c *PersistentConfig) Validate() error { - // we are validating .db-engine here to avoid unintended behaviour as empty string value also has meaning in geth's node.Config.DBEngine - if c.DBEngine != "leveldb" && c.DBEngine != "pebble" { - return fmt.Errorf(`invalid .db-engine choice: %q, allowed "leveldb" or "pebble"`, c.DBEngine) + if c.DBEngine != "leveldb" && c.DBEngine != "pebble" && c.DBEngine != "" { + return fmt.Errorf(`invalid .db-engine choice: %q, allowed "leveldb", "pebble" or ""`, c.DBEngine) } - if c.DBEngine == "pebble" { + // if DBEngine == "" then we may end up opening pebble database, so we want to validate the Pebble config + // if pre-existing database is leveldb backed, then user shouldn't change the Pebble config defaults => this check should also succeed + if c.DBEngine == "pebble" || c.DBEngine == "" { if err := c.Pebble.Validate(); err != nil { return err } diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 49d30fd59c..9c23cdf852 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -17,6 +17,7 @@ import ( "os" "path" "path/filepath" + "regexp" "runtime" "strings" "sync" @@ -326,6 +327,16 @@ func dirExists(path string) bool { return info.IsDir() } +var pebbleNotExistErrorRegex = regexp.MustCompile("pebble: database .* does not exist") + +func isPebbleNotExistError(err error) bool { + return pebbleNotExistErrorRegex.MatchString(err.Error()) +} + +func isLeveldbNotExistError(err error) bool { + return os.IsNotExist(err) +} + func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { if !config.Init.Force { if readOnlyDb, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", 0, 0, "", "l2chaindata/", true, persistentConfig.Pebble.ExtraOptions("l2chaindata")); err == nil { @@ -396,6 +407,9 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo return chainDb, l2BlockChain, nil } readOnlyDb.Close() + } else if !isLeveldbNotExistError(err) && !isPebbleNotExistError(err) { + // we only want to continue if the error is pebble or leveldb not exist error + return nil, nil, fmt.Errorf("Failed to open database: %w", err) } } diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go index 17bac3d670..47ab4b4491 100644 --- a/cmd/nitro/init_test.go +++ b/cmd/nitro/init_test.go @@ -17,6 +17,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/util/testhelpers" ) @@ -177,3 +178,35 @@ func startFileServer(t *testing.T, ctx context.Context, dir string) string { }() return addr } + +func testIsNotExistError(t *testing.T, dbEngine string, isNotExist func(error) bool) { + stackConf := node.DefaultConfig + stackConf.DataDir = t.TempDir() + stackConf.DBEngine = dbEngine + stack, err := node.New(&stackConf) + if err != nil { + t.Fatalf("Failed to created test stack: %v", err) + } + defer stack.Close() + readonly := true + _, err = stack.OpenDatabaseWithExtraOptions("test", 16, 16, "", readonly, nil) + if err == nil { + t.Fatal("Opening non-existent database did not fail") + } + if !isNotExist(err) { + t.Fatalf("Failed to classify error as not exist error - internal implementation of OpenDatabaseWithExtraOptions might have changed, err: %v", err) + } + err = errors.New("some other error") + if isNotExist(err) { + t.Fatalf("Classified other error as not exist, err: %v", err) + } +} + +func TestIsNotExistError(t *testing.T) { + t.Run("TestIsPebbleNotExistError", func(t *testing.T) { + testIsNotExistError(t, "pebble", isPebbleNotExistError) + }) + t.Run("TestIsLeveldbNotExistError", func(t *testing.T) { + testIsNotExistError(t, "leveldb", isLeveldbNotExistError) + }) +}