This updates all users of the github.com/decred/dcrd/databasev/2 module to use the recently introduced v3 version. Replace directives are added as needed to ease development while the final version of the database/v3 v3.0.0 isn't tagged. The relevant documentation is also updated to use the new database/v3 module.
253 lines
7.2 KiB
Go
253 lines
7.2 KiB
Go
// 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
|
|
}
|