* Add vendor dir so builds dont require dep * Pin specific version go-eth version
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
)
|
||||
|
||||
// fetchBlockCmd defines the configuration options for the fetchblock command.
|
||||
type fetchBlockCmd struct{}
|
||||
|
||||
var (
|
||||
// fetchBlockCfg defines the configuration options for the command.
|
||||
fetchBlockCfg = fetchBlockCmd{}
|
||||
)
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *fetchBlockCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("required block hash parameter not specified")
|
||||
}
|
||||
blockHash, err := chainhash.NewHashFromStr(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return db.View(func(tx database.Tx) error {
|
||||
log.Infof("Fetching block %s", blockHash)
|
||||
startTime := time.Now()
|
||||
blockBytes, err := tx.FetchBlock(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Loaded block in %v", time.Since(startTime))
|
||||
log.Infof("Block Hex: %s", hex.EncodeToString(blockBytes))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Usage overrides the usage display for the command.
|
||||
func (cmd *fetchBlockCmd) Usage() string {
|
||||
return "<block-hash>"
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
)
|
||||
|
||||
// blockRegionCmd defines the configuration options for the fetchblockregion
|
||||
// command.
|
||||
type blockRegionCmd struct{}
|
||||
|
||||
var (
|
||||
// blockRegionCfg defines the configuration options for the command.
|
||||
blockRegionCfg = blockRegionCmd{}
|
||||
)
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *blockRegionCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure expected arguments.
|
||||
if len(args) < 1 {
|
||||
return errors.New("required block hash parameter not specified")
|
||||
}
|
||||
if len(args) < 2 {
|
||||
return errors.New("required start offset parameter not " +
|
||||
"specified")
|
||||
}
|
||||
if len(args) < 3 {
|
||||
return errors.New("required region length parameter not " +
|
||||
"specified")
|
||||
}
|
||||
|
||||
// Parse arguments.
|
||||
blockHash, err := chainhash.NewHashFromStr(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
startOffset, err := strconv.ParseUint(args[1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regionLen, err := strconv.ParseUint(args[2], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return db.View(func(tx database.Tx) error {
|
||||
log.Infof("Fetching block region %s<%d:%d>", blockHash,
|
||||
startOffset, startOffset+regionLen-1)
|
||||
region := database.BlockRegion{
|
||||
Hash: blockHash,
|
||||
Offset: uint32(startOffset),
|
||||
Len: uint32(regionLen),
|
||||
}
|
||||
startTime := time.Now()
|
||||
regionBytes, err := tx.FetchBlockRegion(®ion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Loaded block region in %v", time.Since(startTime))
|
||||
log.Infof("Double Hash: %s", chainhash.DoubleHashH(regionBytes))
|
||||
log.Infof("Region Hex: %s", hex.EncodeToString(regionBytes))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Usage overrides the usage display for the command.
|
||||
func (cmd *blockRegionCmd) Usage() string {
|
||||
return "<block-hash> <start-offset> <length-of-region>"
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
var (
|
||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||
knownDbTypes = database.SupportedDrivers()
|
||||
activeNetParams = &chaincfg.MainNetParams
|
||||
|
||||
// Default global config.
|
||||
cfg = &config{
|
||||
DataDir: filepath.Join(btcdHomeDir, "data"),
|
||||
DbType: "ffldb",
|
||||
}
|
||||
)
|
||||
|
||||
// config defines the global configuration options.
|
||||
type config struct {
|
||||
DataDir string `short:"b" long:"datadir" description:"Location of the btcd data directory"`
|
||||
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
|
||||
SimNet bool `long:"simnet" description:"Use the simulation test network"`
|
||||
}
|
||||
|
||||
// fileExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// validDbType returns whether or not dbType is a supported database type.
|
||||
func validDbType(dbType string) bool {
|
||||
for _, knownType := range knownDbTypes {
|
||||
if dbType == knownType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// netName returns the name used when referring to a bitcoin network. At the
|
||||
// time of writing, btcd currently places blocks for testnet version 3 in the
|
||||
// data and log directory "testnet", which does not match the Name field of the
|
||||
// chaincfg parameters. This function can be used to override this directory name
|
||||
// as "testnet" when the passed active network matches wire.TestNet3.
|
||||
//
|
||||
// A proper upgrade to move the data and log directories for this network to
|
||||
// "testnet3" is planned for the future, at which point this function can be
|
||||
// removed and the network parameter's name used instead.
|
||||
func netName(chainParams *chaincfg.Params) string {
|
||||
switch chainParams.Net {
|
||||
case wire.TestNet3:
|
||||
return "testnet"
|
||||
default:
|
||||
return chainParams.Name
|
||||
}
|
||||
}
|
||||
|
||||
// setupGlobalConfig examine the global configuration options for any conditions
|
||||
// which are invalid as well as performs any addition setup necessary after the
|
||||
// initial parse.
|
||||
func setupGlobalConfig() error {
|
||||
// Multiple networks can't be selected simultaneously.
|
||||
// Count number of network flags passed; assign active network params
|
||||
// while we're at it
|
||||
numNets := 0
|
||||
if cfg.TestNet3 {
|
||||
numNets++
|
||||
activeNetParams = &chaincfg.TestNet3Params
|
||||
}
|
||||
if cfg.RegressionTest {
|
||||
numNets++
|
||||
activeNetParams = &chaincfg.RegressionNetParams
|
||||
}
|
||||
if cfg.SimNet {
|
||||
numNets++
|
||||
activeNetParams = &chaincfg.SimNetParams
|
||||
}
|
||||
if numNets > 1 {
|
||||
return errors.New("The testnet, regtest, and simnet params " +
|
||||
"can't be used together -- choose one of the three")
|
||||
}
|
||||
|
||||
// Validate database type.
|
||||
if !validDbType(cfg.DbType) {
|
||||
str := "The specified database type [%v] is invalid -- " +
|
||||
"supported types %v"
|
||||
return fmt.Errorf(str, cfg.DbType, knownDbTypes)
|
||||
}
|
||||
|
||||
// Append the network type to the data directory so it is "namespaced"
|
||||
// per network. In addition to the block database, there are other
|
||||
// pieces of data that are saved to disk such as address manager state.
|
||||
// All data is specific to a network, so namespacing the data directory
|
||||
// means each individual piece of serialized data does not have to
|
||||
// worry about changing names per network and such.
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, netName(activeNetParams))
|
||||
|
||||
return nil
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite 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"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// importCmd defines the configuration options for the insecureimport command.
|
||||
type importCmd struct {
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
|
||||
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
|
||||
}
|
||||
|
||||
var (
|
||||
// importCfg defines the configuration options for the command.
|
||||
importCfg = importCmd{
|
||||
InFile: "bootstrap.dat",
|
||||
Progress: 10,
|
||||
}
|
||||
|
||||
// zeroHash is a simply a hash with all zeros. It is defined here to
|
||||
// avoid creating it multiple times.
|
||||
zeroHash = chainhash.Hash{}
|
||||
)
|
||||
|
||||
// importResults houses the stats and result as an import operation.
|
||||
type importResults struct {
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
err error
|
||||
}
|
||||
|
||||
// blockImporter houses information about an ongoing import from a block data
|
||||
// file to the block database.
|
||||
type blockImporter struct {
|
||||
db database.DB
|
||||
r io.ReadSeeker
|
||||
processQueue chan []byte
|
||||
doneChan chan bool
|
||||
errChan chan error
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
receivedLogBlocks int64
|
||||
receivedLogTx int64
|
||||
lastHeight int64
|
||||
lastBlockTime time.Time
|
||||
lastLogTime time.Time
|
||||
}
|
||||
|
||||
// readBlock reads the next block from the input file.
|
||||
func (bi *blockImporter) readBlock() ([]byte, error) {
|
||||
// The block file format is:
|
||||
// <network> <block length> <serialized block>
|
||||
var net uint32
|
||||
err := binary.Read(bi.r, binary.LittleEndian, &net)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No block and no error means there are no more blocks to read.
|
||||
return nil, nil
|
||||
}
|
||||
if net != uint32(activeNetParams.Net) {
|
||||
return nil, fmt.Errorf("network mismatch -- got %x, want %x",
|
||||
net, uint32(activeNetParams.Net))
|
||||
}
|
||||
|
||||
// Read the block length and ensure it is sane.
|
||||
var blockLen uint32
|
||||
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blockLen > wire.MaxBlockPayload {
|
||||
return nil, fmt.Errorf("block payload of %d bytes is larger "+
|
||||
"than the max allowed %d bytes", blockLen,
|
||||
wire.MaxBlockPayload)
|
||||
}
|
||||
|
||||
serializedBlock := make([]byte, blockLen)
|
||||
if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializedBlock, nil
|
||||
}
|
||||
|
||||
// processBlock potentially imports the block into the database. It first
|
||||
// deserializes the raw block while checking for errors. Already known blocks
|
||||
// are skipped and orphan blocks are considered errors. Returns whether the
|
||||
// block was imported along with any potential errors.
|
||||
//
|
||||
// NOTE: This is not a safe import as it does not verify chain rules.
|
||||
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
|
||||
// Deserialize the block which includes checks for malformed blocks.
|
||||
block, err := btcutil.NewBlockFromBytes(serializedBlock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// update progress statistics
|
||||
bi.lastBlockTime = block.MsgBlock().Header.Timestamp
|
||||
bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
|
||||
// Skip blocks that already exist.
|
||||
var exists bool
|
||||
err = bi.db.View(func(tx database.Tx) error {
|
||||
exists, err = tx.HasBlock(block.Hash())
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't bother trying to process orphans.
|
||||
prevHash := &block.MsgBlock().Header.PrevBlock
|
||||
if !prevHash.IsEqual(&zeroHash) {
|
||||
var exists bool
|
||||
err := bi.db.View(func(tx database.Tx) error {
|
||||
exists, err = tx.HasBlock(prevHash)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exists {
|
||||
return false, fmt.Errorf("import file contains block "+
|
||||
"%v which does not link to the available "+
|
||||
"block chain", prevHash)
|
||||
}
|
||||
}
|
||||
|
||||
// Put the blocks into the database with no checking of chain rules.
|
||||
err = bi.db.Update(func(tx database.Tx) error {
|
||||
return tx.StoreBlock(block)
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// readHandler is the main handler for reading blocks from the import file.
|
||||
// This allows block processing to take place in parallel with block reads.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) readHandler() {
|
||||
out:
|
||||
for {
|
||||
// Read the next block from the file and if anything goes wrong
|
||||
// notify the status handler with the error and bail.
|
||||
serializedBlock, err := bi.readBlock()
|
||||
if err != nil {
|
||||
bi.errChan <- fmt.Errorf("Error reading from input "+
|
||||
"file: %v", err.Error())
|
||||
break out
|
||||
}
|
||||
|
||||
// A nil block with no error means we're done.
|
||||
if serializedBlock == nil {
|
||||
break out
|
||||
}
|
||||
|
||||
// Send the block or quit if we've been signalled to exit by
|
||||
// the status handler due to an error elsewhere.
|
||||
select {
|
||||
case bi.processQueue <- serializedBlock:
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
// Close the processing channel to signal no more blocks are coming.
|
||||
close(bi.processQueue)
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// logProgress logs block progress as an information message. In order to
|
||||
// prevent spam, it limits logging to one message every importCfg.Progress
|
||||
// seconds with duration and totals included.
|
||||
func (bi *blockImporter) logProgress() {
|
||||
bi.receivedLogBlocks++
|
||||
|
||||
now := time.Now()
|
||||
duration := now.Sub(bi.lastLogTime)
|
||||
if duration < time.Second*time.Duration(importCfg.Progress) {
|
||||
return
|
||||
}
|
||||
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
durationMillis := int64(duration / time.Millisecond)
|
||||
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
|
||||
|
||||
// Log information about new block height.
|
||||
blockStr := "blocks"
|
||||
if bi.receivedLogBlocks == 1 {
|
||||
blockStr = "block"
|
||||
}
|
||||
txStr := "transactions"
|
||||
if bi.receivedLogTx == 1 {
|
||||
txStr = "transaction"
|
||||
}
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
|
||||
bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx,
|
||||
txStr, bi.lastHeight, bi.lastBlockTime)
|
||||
|
||||
bi.receivedLogBlocks = 0
|
||||
bi.receivedLogTx = 0
|
||||
bi.lastLogTime = now
|
||||
}
|
||||
|
||||
// processHandler is the main handler for processing blocks. This allows block
|
||||
// processing to take place in parallel with block reads from the import file.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) processHandler() {
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case serializedBlock, ok := <-bi.processQueue:
|
||||
// We're done when the channel is closed.
|
||||
if !ok {
|
||||
break out
|
||||
}
|
||||
|
||||
bi.blocksProcessed++
|
||||
bi.lastHeight++
|
||||
imported, err := bi.processBlock(serializedBlock)
|
||||
if err != nil {
|
||||
bi.errChan <- err
|
||||
break out
|
||||
}
|
||||
|
||||
if imported {
|
||||
bi.blocksImported++
|
||||
}
|
||||
|
||||
bi.logProgress()
|
||||
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// statusHandler waits for updates from the import operation and notifies
|
||||
// the passed doneChan with the results of the import. It also causes all
|
||||
// goroutines to exit if an error is reported from any of them.
|
||||
func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
|
||||
select {
|
||||
// An error from either of the goroutines means we're done so signal
|
||||
// caller with the error and signal all goroutines to quit.
|
||||
case err := <-bi.errChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: err,
|
||||
}
|
||||
close(bi.quit)
|
||||
|
||||
// The import finished normally.
|
||||
case <-bi.doneChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import is the core function which handles importing the blocks from the file
|
||||
// associated with the block importer to the database. It returns a channel
|
||||
// on which the results will be returned when the operation has completed.
|
||||
func (bi *blockImporter) Import() chan *importResults {
|
||||
// Start up the read and process handling goroutines. This setup allows
|
||||
// blocks to be read from disk in parallel while being processed.
|
||||
bi.wg.Add(2)
|
||||
go bi.readHandler()
|
||||
go bi.processHandler()
|
||||
|
||||
// Wait for the import to finish in a separate goroutine and signal
|
||||
// the status handler when done.
|
||||
go func() {
|
||||
bi.wg.Wait()
|
||||
bi.doneChan <- true
|
||||
}()
|
||||
|
||||
// Start the status handler and return the result channel that it will
|
||||
// send the results on when the import is done.
|
||||
resultChan := make(chan *importResults)
|
||||
go bi.statusHandler(resultChan)
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// newBlockImporter returns a new importer for the provided file reader seeker
|
||||
// and database.
|
||||
func newBlockImporter(db database.DB, r io.ReadSeeker) *blockImporter {
|
||||
return &blockImporter{
|
||||
db: db,
|
||||
r: r,
|
||||
processQueue: make(chan []byte, 2),
|
||||
doneChan: make(chan bool),
|
||||
errChan: make(chan error),
|
||||
quit: make(chan struct{}),
|
||||
lastLogTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *importCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the specified block file exists.
|
||||
if !fileExists(cmd.InFile) {
|
||||
str := "The specified block file [%v] does not exist"
|
||||
return fmt.Errorf(str, cmd.InFile)
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Ensure the database is sync'd and closed on Ctrl+C.
|
||||
addInterruptHandler(func() {
|
||||
log.Infof("Gracefully shutting down the database...")
|
||||
db.Close()
|
||||
})
|
||||
|
||||
fi, err := os.Open(importCfg.InFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
// Create a block importer for the database and input file and start it.
|
||||
// The results channel returned from start will contain an error if
|
||||
// anything went wrong.
|
||||
importer := newBlockImporter(db, fi)
|
||||
|
||||
// Perform the import asynchronously and signal the main goroutine when
|
||||
// done. 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. This is done
|
||||
// in a separate goroutine rather than waiting directly so the main
|
||||
// goroutine can be signaled for shutdown by either completion, error,
|
||||
// or from the main interrupt handler. This is necessary since the main
|
||||
// goroutine must be kept running long enough for the interrupt handler
|
||||
// goroutine to finish.
|
||||
go func() {
|
||||
log.Info("Starting import")
|
||||
resultsChan := importer.Import()
|
||||
results := <-resultsChan
|
||||
if results.err != nil {
|
||||
dbErr, ok := results.err.(database.Error)
|
||||
if !ok || ok && dbErr.ErrorCode != database.ErrDbNotOpen {
|
||||
shutdownChannel <- results.err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Processed a total of %d blocks (%d imported, %d "+
|
||||
"already known)", results.blocksProcessed,
|
||||
results.blocksImported,
|
||||
results.blocksProcessed-results.blocksImported)
|
||||
shutdownChannel <- nil
|
||||
}()
|
||||
|
||||
// Wait for shutdown signal from either a normal completion or from the
|
||||
// interrupt handler.
|
||||
err = <-shutdownChannel
|
||||
return err
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
)
|
||||
|
||||
// headersCmd defines the configuration options for the loadheaders command.
|
||||
type headersCmd struct {
|
||||
Bulk bool `long:"bulk" description:"Use bulk loading of headers instead of one at a time"`
|
||||
}
|
||||
|
||||
var (
|
||||
// headersCfg defines the configuration options for the command.
|
||||
headersCfg = headersCmd{
|
||||
Bulk: false,
|
||||
}
|
||||
)
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *headersCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// NOTE: This code will only work for ffldb. Ideally the package using
|
||||
// the database would keep a metadata index of its own.
|
||||
blockIdxName := []byte("ffldb-blockidx")
|
||||
if !headersCfg.Bulk {
|
||||
err = db.View(func(tx database.Tx) error {
|
||||
totalHdrs := 0
|
||||
blockIdxBucket := tx.Metadata().Bucket(blockIdxName)
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
totalHdrs++
|
||||
return nil
|
||||
})
|
||||
log.Infof("Loading headers for %d blocks...", totalHdrs)
|
||||
numLoaded := 0
|
||||
startTime := time.Now()
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
var hash chainhash.Hash
|
||||
copy(hash[:], k)
|
||||
_, err := tx.FetchBlockHeader(&hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numLoaded++
|
||||
return nil
|
||||
})
|
||||
log.Infof("Loaded %d headers in %v", numLoaded,
|
||||
time.Since(startTime))
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Bulk load headers.
|
||||
err = db.View(func(tx database.Tx) error {
|
||||
blockIdxBucket := tx.Metadata().Bucket(blockIdxName)
|
||||
hashes := make([]chainhash.Hash, 0, 500000)
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
var hash chainhash.Hash
|
||||
copy(hash[:], k)
|
||||
hashes = append(hashes, hash)
|
||||
return nil
|
||||
})
|
||||
|
||||
log.Infof("Loading headers for %d blocks...", len(hashes))
|
||||
startTime := time.Now()
|
||||
hdrs, err := tx.FetchBlockHeaders(hashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Loaded %d headers in %v", len(hdrs),
|
||||
time.Since(startTime))
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btclog"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
const (
|
||||
// blockDbNamePrefix is the prefix for the btcd block database.
|
||||
blockDbNamePrefix = "blocks"
|
||||
)
|
||||
|
||||
var (
|
||||
log btclog.Logger
|
||||
shutdownChannel = make(chan error)
|
||||
)
|
||||
|
||||
// loadBlockDB opens the block database and returns a handle to it.
|
||||
func loadBlockDB() (database.DB, error) {
|
||||
// The database name is based on the database type.
|
||||
dbName := blockDbNamePrefix + "_" + cfg.DbType
|
||||
dbPath := filepath.Join(cfg.DataDir, dbName)
|
||||
|
||||
log.Infof("Loading block database from '%s'", dbPath)
|
||||
db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
|
||||
if err != nil {
|
||||
// Return the error if it's not because the database doesn't
|
||||
// exist.
|
||||
if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
|
||||
database.ErrDbDoesNotExist {
|
||||
|
||||
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 = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Block database loaded")
|
||||
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 {
|
||||
// Setup logging.
|
||||
backendLogger := btclog.NewBackend(os.Stdout)
|
||||
defer os.Stdout.Sync()
|
||||
log = backendLogger.Logger("MAIN")
|
||||
dbLog := backendLogger.Logger("BCDB")
|
||||
dbLog.SetLevel(btclog.LevelDebug)
|
||||
database.UseLogger(dbLog)
|
||||
|
||||
// Setup the parser options and commands.
|
||||
appName := filepath.Base(os.Args[0])
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
parserFlags := flags.Options(flags.HelpFlag | flags.PassDoubleDash)
|
||||
parser := flags.NewNamedParser(appName, parserFlags)
|
||||
parser.AddGroup("Global Options", "", cfg)
|
||||
parser.AddCommand("insecureimport",
|
||||
"Insecurely import bulk block data from bootstrap.dat",
|
||||
"Insecurely import bulk block data from bootstrap.dat. "+
|
||||
"WARNING: This is NOT secure because it does NOT "+
|
||||
"verify chain rules. It is only provided for testing "+
|
||||
"purposes.", &importCfg)
|
||||
parser.AddCommand("loadheaders",
|
||||
"Time how long to load headers for all blocks in the database",
|
||||
"", &headersCfg)
|
||||
parser.AddCommand("fetchblock",
|
||||
"Fetch the specific block hash from the database", "",
|
||||
&fetchBlockCfg)
|
||||
parser.AddCommand("fetchblockregion",
|
||||
"Fetch the specified block region from the database", "",
|
||||
&blockRegionCfg)
|
||||
|
||||
// Parse command line and invoke the Execute function for the specified
|
||||
// command.
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Use all processor cores.
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
// Work around defer not working after os.Exit()
|
||||
if err := realMain(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
|
||||
var interruptChannel chan os.Signal
|
||||
|
||||
// addHandlerChannel is used to add an interrupt handler to the list of handlers
|
||||
// to be invoked on SIGINT (Ctrl+C) signals.
|
||||
var addHandlerChannel = make(chan func())
|
||||
|
||||
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the
|
||||
// interruptChannel and invokes the registered interruptCallbacks accordingly.
|
||||
// It also listens for callback registration. It must be run as a goroutine.
|
||||
func mainInterruptHandler() {
|
||||
// interruptCallbacks is a list of callbacks to invoke when a
|
||||
// SIGINT (Ctrl+C) is received.
|
||||
var interruptCallbacks []func()
|
||||
|
||||
// isShutdown is a flag which is used to indicate whether or not
|
||||
// the shutdown signal has already been received and hence any future
|
||||
// attempts to add a new interrupt handler should invoke them
|
||||
// immediately.
|
||||
var isShutdown bool
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-interruptChannel:
|
||||
// Ignore more than one shutdown signal.
|
||||
if isShutdown {
|
||||
log.Infof("Received SIGINT (Ctrl+C). " +
|
||||
"Already shutting down...")
|
||||
continue
|
||||
}
|
||||
|
||||
isShutdown = true
|
||||
log.Infof("Received SIGINT (Ctrl+C). Shutting down...")
|
||||
|
||||
// Run handlers in LIFO order.
|
||||
for i := range interruptCallbacks {
|
||||
idx := len(interruptCallbacks) - 1 - i
|
||||
callback := interruptCallbacks[idx]
|
||||
callback()
|
||||
}
|
||||
|
||||
// Signal the main goroutine to shutdown.
|
||||
go func() {
|
||||
shutdownChannel <- nil
|
||||
}()
|
||||
|
||||
case handler := <-addHandlerChannel:
|
||||
// The shutdown signal has already been received, so
|
||||
// just invoke and new handlers immediately.
|
||||
if isShutdown {
|
||||
handler()
|
||||
}
|
||||
|
||||
interruptCallbacks = append(interruptCallbacks, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is
|
||||
// received.
|
||||
func addInterruptHandler(handler func()) {
|
||||
// Create the channel and start the main interrupt handler which invokes
|
||||
// all other callbacks and exits if not already done.
|
||||
if interruptChannel == nil {
|
||||
interruptChannel = make(chan os.Signal, 1)
|
||||
signal.Notify(interruptChannel, os.Interrupt)
|
||||
go mainInterruptHandler()
|
||||
}
|
||||
|
||||
addHandlerChannel <- handler
|
||||
}
|
||||
Reference in New Issue
Block a user