func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
	select {
	case err := <-bi.errChan:
		resultsChan <- &importResults{
			blocksProcessed: bi.blocksProcessed,
			blocksImported:  bi.blocksImported,
			err:             err,
		}
		close(bi.quit)
	case <-bi.doneChan:
		resultsChan <- &importResults{
			blocksProcessed: bi.blocksProcessed,
			blocksImported:  bi.blocksImported,
			err:             nil,
		}
	}
}
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.
package main
import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"sync"
	"time"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/database"
	"github.com/soteria-dag/soterd/wire"
	"github.com/soteria-dag/soterd/soterutil"
)
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 = importCmd{
		InFile:   "bootstrap.dat",
		Progress: 10,
	}
	zeroHash = chainhash.Hash{}
)
type importResults struct {
	blocksProcessed int64
	blocksImported  int64
	err             error
}
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
}
func (bi *blockImporter) readBlock() ([]byte, error) {
	var net uint32
	err := binary.Read(bi.r, binary.LittleEndian, &net)
	if err != nil {
		if err != io.EOF {
			return nil, err
		}
		return nil, nil
	}
	if net != uint32(activeNetParams.Net) {
		return nil, fmt.Errorf("network mismatch -- got %x, want %x",
			net, uint32(activeNetParams.Net))
	}
	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
}
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
	block, err := soterutil.NewBlockFromBytes(serializedBlock)
	if err != nil {
		return false, err
	}
	bi.lastBlockTime = block.MsgBlock().Header.Timestamp
	bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
	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
	}
	prevHash := &block.MsgBlock().Header.PrevBlock
	if !prevHash.IsEqual(&zeroHash) {
		parentsHash := block.MsgBlock().Parents.ParentHashes()
		for _, parentHash := range parentsHash {
			var exists bool
			err := bi.db.View(func(tx database.Tx) error {
				exists, err = tx.HasBlock(&parentHash)
				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", parentHash)
			}
		}
	}
	err = bi.db.Update(func(tx database.Tx) error {
		return tx.StoreBlock(block)
	})
	if err != nil {
		return false, err
	}
	return true, nil
}
func (bi *blockImporter) readHandler() {
out:
	for {
		serializedBlock, err := bi.readBlock()
		if err != nil {
			bi.errChan <- fmt.Errorf("Error reading from input "+
				"file: %v", err.Error())
			break out
		}
		if serializedBlock == nil {
			break out
		}
		select {
		case bi.processQueue <- serializedBlock:
		case <-bi.quit:
			break out
		}
	}
	close(bi.processQueue)
	bi.wg.Done()
}
func (bi *blockImporter) logProgress() {
	bi.receivedLogBlocks++
	now := time.Now()
	duration := now.Sub(bi.lastLogTime)
	if duration < time.Second*time.Duration(importCfg.Progress) {
		return
	}
	durationMillis := int64(duration / time.Millisecond)
	tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
	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
}
func (bi *blockImporter) processHandler() {
out:
	for {
		select {
		case serializedBlock, ok := <-bi.processQueue:
			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()
}