// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package main import ( "encoding/binary" "errors" _ "net/http/pprof" "os" "path/filepath" "github.com/decred/dcrd/blockchain/v4" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/database/v3" "github.com/decred/dcrd/internal/progresslog" ) const ( // blockDbNamePrefix is the prefix for the block database name. The // database type is appended to this value to form the full block // database name. blockDbNamePrefix = "blocks" ) // removeDB removes the database at the provided path. The fi parameter MUST // agree with the provided path. func removeDB(dbPath string, fi os.FileInfo) error { if fi.IsDir() { return os.RemoveAll(dbPath) } return os.Remove(dbPath) } // removeRegressionDB removes the existing regression test database if running // in regression test mode and it already exists. func removeRegressionDB(dbPath string) error { // Don't do anything if not in regression test mode. if !cfg.RegNet { return nil } // Remove the old regression test database if it already exists. fi, err := os.Stat(dbPath) if err == nil { dcrdLog.Infof("Removing regression test database from '%s'", dbPath) return removeDB(dbPath, fi) } return nil } // blockDbPath returns the path to the block database given a database type. func blockDbPath(dbType string) string { // The database name is based on the database type. dbName := blockDbNamePrefix + "_" + dbType if dbType == "sqlite" { dbName = dbName + ".db" } dbPath := filepath.Join(cfg.DataDir, dbName) return dbPath } // warnMultipleDBs shows a warning if multiple block database types are detected. // This is not a situation most users want. It is handy for development however // to support multiple side-by-side databases. func warnMultipleDBs() { // This is intentionally not using the known db types which depend on the // database types compiled into the binary since we want to detect legacy db // types as well. dbTypes := []string{"ffldb", "leveldb", "sqlite"} duplicateDbPaths := make([]string, 0, len(dbTypes)-1) for _, dbType := range dbTypes { if dbType == cfg.DbType { continue } // Store db path as a duplicate db if it exists. dbPath := blockDbPath(dbType) if fileExists(dbPath) { duplicateDbPaths = append(duplicateDbPaths, dbPath) } } // Warn if there are extra databases. if len(duplicateDbPaths) > 0 { selectedDbPath := blockDbPath(cfg.DbType) dcrdLog.Warnf("WARNING: There are multiple block chain databases "+ "using different database types.\nYou probably don't want to "+ "waste disk space by having more than one.\nYour current database "+ "is located at [%v].\nThe additional database is located at %v", selectedDbPath, duplicateDbPaths) } } // loadBlockDB loads (or creates when needed) the block database taking into // account the selected database backend and returns a handle to it. It also // contains additional logic such warning the user if there are multiple // databases which consume space on the file system and ensuring the regression // test database is clean when in regression test mode. func loadBlockDB(params *chaincfg.Params) (database.DB, error) { // The memdb backend does not have a file path associated with it, so handle // it uniquely. We also don't want to worry about the multiple database // type warnings when running with the memory database. if cfg.DbType == "memdb" { dcrdLog.Infof("Creating block database in memory.") db, err := database.Create(cfg.DbType) if err != nil { return nil, err } return db, nil } warnMultipleDBs() // The database name is based on the database type. dbPath := blockDbPath(cfg.DbType) // The regression test is special in that it needs a clean database for // each run, so remove it now if it already exists. removeRegressionDB(dbPath) // createDB is a convenience func that creates the database with the type // and network specified in the config at the path determined above while // also creating any intermediate directories in the configured data directory // path as needed. createDB := func() (database.DB, error) { // Create the data dir if it does not exist. err := os.MkdirAll(cfg.DataDir, 0700) if err != nil { return nil, err } return database.Create(cfg.DbType, dbPath, params.Net) } // Open the existing database or create a new one as needed. dcrdLog.Infof("Loading block database from '%s'", dbPath) db, err := database.Open(cfg.DbType, dbPath, params.Net) if err != nil { // Return the error if it's not because the database doesn't exist. if !errors.Is(err, database.ErrDbDoesNotExist) { return nil, err } db, err = createDB() if err != nil { return nil, err } } // Remove and recreate the blockchain database when it can no longer be // upgraded due to being too old. if err := blockchain.CheckDBTooOldToUpgrade(db); err != nil { // Any errors other than the database being too old upgrade are // unexpected. if !errors.Is(err, blockchain.ErrDBTooOldToUpgrade) { return nil, err } dcrdLog.Infof("Removing database from '%s': %v", dbPath, err) // Close the database so it can be removed cleanly. if err := db.Close(); err != nil { return nil, err } // Remove the old database and create/open a new one. fi, err := os.Stat(dbPath) if err != nil { return nil, err } err = removeDB(dbPath, fi) if err != nil { return nil, err } db, err = createDB() if err != nil { return nil, err } } dcrdLog.Info("Block database loaded") return db, nil } // dumpBlockChain dumps a map of the blockchain blocks as serialized bytes. func dumpBlockChain(params *chaincfg.Params, b *blockchain.BlockChain) error { dcrdLog.Infof("Writing the blockchain to flat file %q. This might take a "+ "while...", cfg.DumpBlockchain) progressLogger := progresslog.New("Wrote", dcrdLog) file, err := os.Create(cfg.DumpBlockchain) if err != nil { return err } defer file.Close() // Store the network ID in an array for later writing. var net [4]byte binary.LittleEndian.PutUint32(net[:], uint32(params.Net)) // Write the blocks sequentially, excluding the genesis block. tipHeight := b.BestSnapshot().Height var sz [4]byte for i := int64(1); i <= tipHeight; i++ { bl, err := b.BlockByHeight(i) if err != nil { return err } // Serialize the block for writing. blB, err := bl.Bytes() if err != nil { return err } // Write the network ID first. _, err = file.Write(net[:]) if err != nil { return err } // Write the size of the block as a little endian uint32, // then write the block itself serialized. binary.LittleEndian.PutUint32(sz[:], uint32(len(blB))) _, err = file.Write(sz[:]) if err != nil { return err } _, err = file.Write(blB) if err != nil { return err } msgBlock := bl.MsgBlock() forceLog := int64(msgBlock.Header.Height) >= tipHeight progressLogger.LogProgress(msgBlock, forceLog, func() float64 { return float64(msgBlock.Header.Height) / float64(tipHeight) * 100 }) } srvrLog.Infof("Successfully dumped the blockchain (%v blocks) to %v.", tipHeight, cfg.DumpBlockchain) return nil }