lbcd/util/addblock/addblock.go
Dave Collins 6222b1d8cc Rework and Improve addblock utility.
The addblock utility was originally written as a quick debug tool during
initial development to populate blocks into the database.  However, now
that it has been designated as the standard way to import bootstrap.dat
(and indeed block data files in general), it was lacking a few features
such as properly checking against the chain rules and known good
checkpoints.

This commit reworks and improves the utility in several ways:

- Imported blocks are now checked against the chain rules including
  checkpoints to ensure they match the known good chain
- The utility now properly shuts down after processing all blocks
- Attempting to import orphan blocks (blocks which build off a block you
  don't yet have in the database) returns an error
- Blocks that are already known are now skipped instead of causing an
  error which means you can stop and restart the import mid-way through
  without issues or start it after you've already downloaded a
  portion of the chain
- The block height is no longer assumed to start at 0 which means input
  files that start later in the chain will work properly so long as you
  already have the chain at least up to the point of the block just before
  the first one in the input file
- Improved error handling and reporting
- How often the progress display is shown is now configurable
- Statistics about how many blocks were processed, imported, and already
  known are now displayed after the input file has been fully processed

This resolves comments made in #60.
2014-01-14 01:18:52 -06:00

134 lines
3.4 KiB
Go

// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"github.com/conformal/btcchain"
"github.com/conformal/btcd/limits"
"github.com/conformal/btcdb"
_ "github.com/conformal/btcdb/ldb"
"github.com/conformal/btclog"
"os"
"path/filepath"
"runtime"
)
const (
// blockDbNamePrefix is the prefix for the btcd block database.
blockDbNamePrefix = "blocks"
)
var (
cfg *config
log btclog.Logger
)
// loadBlockDB opens the block database and returns a handle to it.
func loadBlockDB() (btcdb.Db, error) {
// The database name is based on the database type.
dbName := blockDbNamePrefix + "_" + cfg.DbType
if cfg.DbType == "sqlite" {
dbName = dbName + ".db"
}
dbPath := filepath.Join(cfg.DataDir, dbName)
log.Infof("Loading block database from '%s'", dbPath)
db, err := btcdb.OpenDB(cfg.DbType, dbPath)
if err != nil {
// Return the error if it's not because the database doesn't
// exist.
if err != btcdb.DbDoesNotExist {
return nil, err
}
// Create the db if it does not exist.
err = os.MkdirAll(cfg.DataDir, 0700)
if err != nil {
return nil, err
}
db, err = btcdb.CreateDB(cfg.DbType, dbPath)
if err != nil {
return nil, err
}
}
// Get the latest block height from the database.
_, height, err := db.NewestSha()
if err != nil {
db.Close()
return nil, err
}
log.Infof("Block database loaded with block height %d", height)
return db, nil
}
// realMain is the real main function for the utility. It is necessary to work
// around the fact that deferred functions do not run when os.Exit() is called.
func realMain() error {
// Load configuration and parse command line.
tcfg, _, err := loadConfig()
if err != nil {
return err
}
cfg = tcfg
// Setup logging.
backendLogger := btclog.NewDefaultBackendLogger()
defer backendLogger.Flush()
log = btclog.NewSubsystemLogger(backendLogger, "")
btcdb.UseLogger(btclog.NewSubsystemLogger(backendLogger, "BCDB: "))
btcchain.UseLogger(btclog.NewSubsystemLogger(backendLogger, "CHAN: "))
// Load the block database.
db, err := loadBlockDB()
if err != nil {
log.Errorf("Failed to load database: %v", err)
return err
}
defer db.Close()
fi, err := os.Open(cfg.InFile)
if err != nil {
log.Errorf("Failed to open file %v: %v", cfg.InFile, err)
return err
}
defer fi.Close()
// Create a block importer for the database and input file and start it.
// The done channel returned from start will contain an error if
// anything went wrong.
importer := newBlockImporter(db, fi)
// Perform the import asynchronously. This allows blocks to be
// processed and read in parallel. The results channel returned from
// Import contains the statistics about the import including an error
// if something went wrong.
log.Info("Starting import")
resultsChan := importer.Import()
results := <-resultsChan
if results.err != nil {
log.Errorf("%v", results.err)
return results.err
}
log.Infof("Processed a total of %d blocks (%d imported, %d already "+
"known)", results.blocksProcessed, results.blocksImported,
results.blocksProcessed-results.blocksImported)
return nil
}
func main() {
// Use all processor cores and up some limits.
runtime.GOMAXPROCS(runtime.NumCPU())
if err := limits.SetLimits(); err != nil {
os.Exit(1)
}
// Work around defer not working after os.Exit()
if err := realMain(); err != nil {
os.Exit(1)
}
}