github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/database/cmd/dbtool/insecureimport.go (about)

     1  // Copyright (c) 2015-2016 The btcsuite developers
     2  // Copyright (c) 2016 The Dash developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package main
     7  
     8  import (
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/dashpay/godash/database"
    17  	"github.com/dashpay/godash/wire"
    18  	"github.com/dashpay/godashutil"
    19  )
    20  
    21  // importCmd defines the configuration options for the insecureimport command.
    22  type importCmd struct {
    23  	InFile   string `short:"i" long:"infile" description:"File containing the block(s)"`
    24  	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"`
    25  }
    26  
    27  var (
    28  	// importCfg defines the configuration options for the command.
    29  	importCfg = importCmd{
    30  		InFile:   "bootstrap.dat",
    31  		Progress: 10,
    32  	}
    33  
    34  	// zeroHash is a simply a hash with all zeros.  It is defined here to
    35  	// avoid creating it multiple times.
    36  	zeroHash = wire.ShaHash{}
    37  )
    38  
    39  // importResults houses the stats and result as an import operation.
    40  type importResults struct {
    41  	blocksProcessed int64
    42  	blocksImported  int64
    43  	err             error
    44  }
    45  
    46  // blockImporter houses information about an ongoing import from a block data
    47  // file to the block database.
    48  type blockImporter struct {
    49  	db                database.DB
    50  	r                 io.ReadSeeker
    51  	processQueue      chan []byte
    52  	doneChan          chan bool
    53  	errChan           chan error
    54  	quit              chan struct{}
    55  	wg                sync.WaitGroup
    56  	blocksProcessed   int64
    57  	blocksImported    int64
    58  	receivedLogBlocks int64
    59  	receivedLogTx     int64
    60  	lastHeight        int64
    61  	lastBlockTime     time.Time
    62  	lastLogTime       time.Time
    63  }
    64  
    65  // readBlock reads the next block from the input file.
    66  func (bi *blockImporter) readBlock() ([]byte, error) {
    67  	// The block file format is:
    68  	//  <network> <block length> <serialized block>
    69  	var net uint32
    70  	err := binary.Read(bi.r, binary.LittleEndian, &net)
    71  	if err != nil {
    72  		if err != io.EOF {
    73  			return nil, err
    74  		}
    75  
    76  		// No block and no error means there are no more blocks to read.
    77  		return nil, nil
    78  	}
    79  	if net != uint32(activeNetParams.Net) {
    80  		return nil, fmt.Errorf("network mismatch -- got %x, want %x",
    81  			net, uint32(activeNetParams.Net))
    82  	}
    83  
    84  	// Read the block length and ensure it is sane.
    85  	var blockLen uint32
    86  	if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
    87  		return nil, err
    88  	}
    89  	if blockLen > wire.MaxBlockPayload {
    90  		return nil, fmt.Errorf("block payload of %d bytes is larger "+
    91  			"than the max allowed %d bytes", blockLen,
    92  			wire.MaxBlockPayload)
    93  	}
    94  
    95  	serializedBlock := make([]byte, blockLen)
    96  	if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	return serializedBlock, nil
   101  }
   102  
   103  // processBlock potentially imports the block into the database.  It first
   104  // deserializes the raw block while checking for errors.  Already known blocks
   105  // are skipped and orphan blocks are considered errors.  Returns whether the
   106  // block was imported along with any potential errors.
   107  //
   108  // NOTE: This is not a safe import as it does not verify chain rules.
   109  func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
   110  	// Deserialize the block which includes checks for malformed blocks.
   111  	block, err := godashutil.NewBlockFromBytes(serializedBlock)
   112  	if err != nil {
   113  		return false, err
   114  	}
   115  
   116  	// update progress statistics
   117  	bi.lastBlockTime = block.MsgBlock().Header.Timestamp
   118  	bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
   119  
   120  	// Skip blocks that already exist.
   121  	var exists bool
   122  	err = bi.db.View(func(tx database.Tx) error {
   123  		exists, err = tx.HasBlock(block.Sha())
   124  		if err != nil {
   125  			return err
   126  		}
   127  		return nil
   128  	})
   129  	if err != nil {
   130  		return false, err
   131  	}
   132  	if exists {
   133  		return false, nil
   134  	}
   135  
   136  	// Don't bother trying to process orphans.
   137  	prevHash := &block.MsgBlock().Header.PrevBlock
   138  	if !prevHash.IsEqual(&zeroHash) {
   139  		var exists bool
   140  		err := bi.db.View(func(tx database.Tx) error {
   141  			exists, err = tx.HasBlock(prevHash)
   142  			if err != nil {
   143  				return err
   144  			}
   145  			return nil
   146  		})
   147  		if err != nil {
   148  			return false, err
   149  		}
   150  		if !exists {
   151  			return false, fmt.Errorf("import file contains block "+
   152  				"%v which does not link to the available "+
   153  				"block chain", prevHash)
   154  		}
   155  	}
   156  
   157  	// Put the blocks into the database with no checking of chain rules.
   158  	err = bi.db.Update(func(tx database.Tx) error {
   159  		return tx.StoreBlock(block)
   160  	})
   161  	if err != nil {
   162  		return false, err
   163  	}
   164  
   165  	return true, nil
   166  }
   167  
   168  // readHandler is the main handler for reading blocks from the import file.
   169  // This allows block processing to take place in parallel with block reads.
   170  // It must be run as a goroutine.
   171  func (bi *blockImporter) readHandler() {
   172  out:
   173  	for {
   174  		// Read the next block from the file and if anything goes wrong
   175  		// notify the status handler with the error and bail.
   176  		serializedBlock, err := bi.readBlock()
   177  		if err != nil {
   178  			bi.errChan <- fmt.Errorf("Error reading from input "+
   179  				"file: %v", err.Error())
   180  			break out
   181  		}
   182  
   183  		// A nil block with no error means we're done.
   184  		if serializedBlock == nil {
   185  			break out
   186  		}
   187  
   188  		// Send the block or quit if we've been signalled to exit by
   189  		// the status handler due to an error elsewhere.
   190  		select {
   191  		case bi.processQueue <- serializedBlock:
   192  		case <-bi.quit:
   193  			break out
   194  		}
   195  	}
   196  
   197  	// Close the processing channel to signal no more blocks are coming.
   198  	close(bi.processQueue)
   199  	bi.wg.Done()
   200  }
   201  
   202  // logProgress logs block progress as an information message.  In order to
   203  // prevent spam, it limits logging to one message every importCfg.Progress
   204  // seconds with duration and totals included.
   205  func (bi *blockImporter) logProgress() {
   206  	bi.receivedLogBlocks++
   207  
   208  	now := time.Now()
   209  	duration := now.Sub(bi.lastLogTime)
   210  	if duration < time.Second*time.Duration(importCfg.Progress) {
   211  		return
   212  	}
   213  
   214  	// Truncate the duration to 10s of milliseconds.
   215  	durationMillis := int64(duration / time.Millisecond)
   216  	tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
   217  
   218  	// Log information about new block height.
   219  	blockStr := "blocks"
   220  	if bi.receivedLogBlocks == 1 {
   221  		blockStr = "block"
   222  	}
   223  	txStr := "transactions"
   224  	if bi.receivedLogTx == 1 {
   225  		txStr = "transaction"
   226  	}
   227  	log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
   228  		bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx,
   229  		txStr, bi.lastHeight, bi.lastBlockTime)
   230  
   231  	bi.receivedLogBlocks = 0
   232  	bi.receivedLogTx = 0
   233  	bi.lastLogTime = now
   234  }
   235  
   236  // processHandler is the main handler for processing blocks.  This allows block
   237  // processing to take place in parallel with block reads from the import file.
   238  // It must be run as a goroutine.
   239  func (bi *blockImporter) processHandler() {
   240  out:
   241  	for {
   242  		select {
   243  		case serializedBlock, ok := <-bi.processQueue:
   244  			// We're done when the channel is closed.
   245  			if !ok {
   246  				break out
   247  			}
   248  
   249  			bi.blocksProcessed++
   250  			bi.lastHeight++
   251  			imported, err := bi.processBlock(serializedBlock)
   252  			if err != nil {
   253  				bi.errChan <- err
   254  				break out
   255  			}
   256  
   257  			if imported {
   258  				bi.blocksImported++
   259  			}
   260  
   261  			bi.logProgress()
   262  
   263  		case <-bi.quit:
   264  			break out
   265  		}
   266  	}
   267  	bi.wg.Done()
   268  }
   269  
   270  // statusHandler waits for updates from the import operation and notifies
   271  // the passed doneChan with the results of the import.  It also causes all
   272  // goroutines to exit if an error is reported from any of them.
   273  func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
   274  	select {
   275  	// An error from either of the goroutines means we're done so signal
   276  	// caller with the error and signal all goroutines to quit.
   277  	case err := <-bi.errChan:
   278  		resultsChan <- &importResults{
   279  			blocksProcessed: bi.blocksProcessed,
   280  			blocksImported:  bi.blocksImported,
   281  			err:             err,
   282  		}
   283  		close(bi.quit)
   284  
   285  	// The import finished normally.
   286  	case <-bi.doneChan:
   287  		resultsChan <- &importResults{
   288  			blocksProcessed: bi.blocksProcessed,
   289  			blocksImported:  bi.blocksImported,
   290  			err:             nil,
   291  		}
   292  	}
   293  }
   294  
   295  // Import is the core function which handles importing the blocks from the file
   296  // associated with the block importer to the database.  It returns a channel
   297  // on which the results will be returned when the operation has completed.
   298  func (bi *blockImporter) Import() chan *importResults {
   299  	// Start up the read and process handling goroutines.  This setup allows
   300  	// blocks to be read from disk in parallel while being processed.
   301  	bi.wg.Add(2)
   302  	go bi.readHandler()
   303  	go bi.processHandler()
   304  
   305  	// Wait for the import to finish in a separate goroutine and signal
   306  	// the status handler when done.
   307  	go func() {
   308  		bi.wg.Wait()
   309  		bi.doneChan <- true
   310  	}()
   311  
   312  	// Start the status handler and return the result channel that it will
   313  	// send the results on when the import is done.
   314  	resultChan := make(chan *importResults)
   315  	go bi.statusHandler(resultChan)
   316  	return resultChan
   317  }
   318  
   319  // newBlockImporter returns a new importer for the provided file reader seeker
   320  // and database.
   321  func newBlockImporter(db database.DB, r io.ReadSeeker) *blockImporter {
   322  	return &blockImporter{
   323  		db:           db,
   324  		r:            r,
   325  		processQueue: make(chan []byte, 2),
   326  		doneChan:     make(chan bool),
   327  		errChan:      make(chan error),
   328  		quit:         make(chan struct{}),
   329  		lastLogTime:  time.Now(),
   330  	}
   331  }
   332  
   333  // Execute is the main entry point for the command.  It's invoked by the parser.
   334  func (cmd *importCmd) Execute(args []string) error {
   335  	// Setup the global config options and ensure they are valid.
   336  	if err := setupGlobalConfig(); err != nil {
   337  		return err
   338  	}
   339  
   340  	// Ensure the specified block file exists.
   341  	if !fileExists(cmd.InFile) {
   342  		str := "The specified block file [%v] does not exist"
   343  		return fmt.Errorf(str, cmd.InFile)
   344  	}
   345  
   346  	// Load the block database.
   347  	db, err := loadBlockDB()
   348  	if err != nil {
   349  		return err
   350  	}
   351  	defer db.Close()
   352  
   353  	// Ensure the database is sync'd and closed on Ctrl+C.
   354  	addInterruptHandler(func() {
   355  		log.Infof("Gracefully shutting down the database...")
   356  		db.Close()
   357  	})
   358  
   359  	fi, err := os.Open(importCfg.InFile)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	defer fi.Close()
   364  
   365  	// Create a block importer for the database and input file and start it.
   366  	// The results channel returned from start will contain an error if
   367  	// anything went wrong.
   368  	importer := newBlockImporter(db, fi)
   369  
   370  	// Perform the import asynchronously and signal the main goroutine when
   371  	// done.  This allows blocks to be processed and read in parallel.  The
   372  	// results channel returned from Import contains the statistics about
   373  	// the import including an error if something went wrong.  This is done
   374  	// in a separate goroutine rather than waiting directly so the main
   375  	// goroutine can be signaled for shutdown by either completion, error,
   376  	// or from the main interrupt handler.  This is necessary since the main
   377  	// goroutine must be kept running long enough for the interrupt handler
   378  	// goroutine to finish.
   379  	go func() {
   380  		log.Info("Starting import")
   381  		resultsChan := importer.Import()
   382  		results := <-resultsChan
   383  		if results.err != nil {
   384  			dbErr, ok := results.err.(database.Error)
   385  			if !ok || ok && dbErr.ErrorCode != database.ErrDbNotOpen {
   386  				shutdownChannel <- results.err
   387  				return
   388  			}
   389  		}
   390  
   391  		log.Infof("Processed a total of %d blocks (%d imported, %d "+
   392  			"already known)", results.blocksProcessed,
   393  			results.blocksImported,
   394  			results.blocksProcessed-results.blocksImported)
   395  		shutdownChannel <- nil
   396  	}()
   397  
   398  	// Wait for shutdown signal from either a normal completion or from the
   399  	// interrupt handler.
   400  	err = <-shutdownChannel
   401  	return err
   402  }