// Copyright (c) 2013 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/btcdb" _ "github.com/conformal/btcdb/sqlite3" "github.com/conformal/btcutil" "github.com/conformal/btcwire" "os" "path/filepath" "sync" "time" ) const ( chanBufferSize = 50 ) // blockMsg packages a bitcoin block message and the peer it came from together // so the block handler has access to that information. type blockMsg struct { block *btcutil.Block peer *peer } // txMsg packages a bitcoin tx message and the peer it came from together // so the block handler has access to that information. type txMsg struct { msg *btcwire.MsgTx peer *peer } // blockManager provides a concurrency safe block manager for handling all // incoming blocks. type blockManager struct { server *server started bool shutdown bool blockChain *btcchain.BlockChain blockPeer map[btcwire.ShaHash]*peer receivedLogBlocks int64 receivedLogTx int64 lastBlockLogTime time.Time processingReqs bool newBlocks chan bool blockQueue chan *blockMsg chainNotify chan *btcchain.Notification wg sync.WaitGroup quit chan bool } // logBlockHeight logs a new block height as an information message to show // progress to the user. In order to prevent spam, it limits logging to one // message every 10 seconds with duration and totals included. func (b *blockManager) logBlockHeight(numTx, height int64) { b.receivedLogBlocks++ b.receivedLogTx += numTx now := time.Now() duration := now.Sub(b.lastBlockLogTime) if duration < time.Second*10 { return } // Log information about new block height. blockStr := "blocks" if b.receivedLogBlocks == 1 { blockStr = "block" } txStr := "transactions" if b.receivedLogTx == 1 { txStr = "transaction" } log.Infof("[BMGR] Processed %d %s (%d %s) in the last %s - Block "+ "height %d", b.receivedLogBlocks, blockStr, b.receivedLogTx, txStr, duration, height) b.receivedLogBlocks = 0 b.receivedLogTx = 0 b.lastBlockLogTime = now } // handleBlockMsg handles block messages from all peers. func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { // Keep track of which peer the block was sent from so the notification // handler can request the parent blocks from the appropriate peer. blockSha, _ := bmsg.block.Sha() b.blockPeer[*blockSha] = bmsg.peer // Process the block to include validation, best chain selection, orphan // handling, etc. err := b.blockChain.ProcessBlock(bmsg.block) if err != nil { delete(b.blockPeer, *blockSha) log.Warnf("[BMGR] Failed to process block %v: %v", blockSha, err) return } // Don't keep track of the peer that sent the block any longer if it's // not an orphan. if !b.blockChain.IsKnownOrphan(blockSha) { delete(b.blockPeer, *blockSha) } // Log info about the new block height. _, height, err := b.server.db.NewestSha() if err != nil { log.Warnf("[BMGR] Failed to obtain latest sha - %v", err) return } b.logBlockHeight(int64(len(bmsg.block.MsgBlock().Transactions)), height) // Sync the db to disk. b.server.db.Sync() } // blockHandler is the main handler for the block manager. It must be run // as a goroutine. It processes block and inv messages in a separate goroutine // from the peer handlers so the block (MsgBlock) and tx (MsgTx) messages are // handled by a single thread without needing to lock memory data structures. // This is important because the block manager controls which blocks are needed // and how the fetching should proceed. // // NOTE: Tx messages need to be handled here too. // (either that or block and tx need to be handled in separate threads) func (b *blockManager) blockHandler() { out: for !b.shutdown { select { // Handle new block messages. case bmsg := <-b.blockQueue: b.handleBlockMsg(bmsg) bmsg.peer.blockProcessed <- true case <-b.quit: break out } } b.wg.Done() log.Trace("[BMGR] Block handler done") } // handleNotifyMsg handles notifications from btcchain. Currently it doesn't // respond to any notifications, but the idea is that it requests missing blocks // in response to orphan notifications and updates the wallet for blocks // connected to and disconnected from the main chain. func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) { switch notification.Type { case btcchain.NTOrphanBlock: orphanRoot := notification.Data.(*btcwire.ShaHash) if peer, exists := b.blockPeer[*orphanRoot]; exists { locator, err := b.blockChain.LatestBlockLocator() if err != nil { log.Error("[BMGR] Failed to get block locator "+ "for the latest block: %v", err) break } peer.pushGetBlocksMsg(locator, orphanRoot) delete(b.blockPeer, *orphanRoot) break } case btcchain.NTBlockAccepted: // TODO(davec): Relay inventory, but don't relay old inventory // during initial block download. } } // chainNotificationHandler is the handler for asynchronous notifications from // btcchain. It must be run as a goroutine. func (b *blockManager) chainNotificationHandler() { out: for !b.shutdown { select { case notification := <-b.chainNotify: b.handleNotifyMsg(notification) case <-b.quit: break out } } b.wg.Done() log.Trace("[BMGR] Chain notification handler done") } // QueueBlock adds the passed block message and peer to the block handling queue. func (b *blockManager) QueueBlock(block *btcutil.Block, p *peer) { // Don't accept more blocks if we're shutting down. if b.shutdown { p.blockProcessed <- false return } bmsg := blockMsg{block: block, peer: p} b.blockQueue <- &bmsg } // Start begins the core block handler which processes block and inv messages. func (b *blockManager) Start() { // Already started? if b.started { return } log.Trace("[BMGR] Starting block manager") go b.blockHandler() go b.chainNotificationHandler() b.wg.Add(2) b.started = true } // Stop gracefully shuts down the block manager by stopping all asynchronous // handlers and waiting for them to finish. func (b *blockManager) Stop() error { if b.shutdown { log.Warnf("[BMGR] Block manager is already in the process of " + "shutting down") return nil } log.Infof("[BMGR] Block manager shutting down") b.shutdown = true close(b.quit) b.wg.Wait() return nil } // newBlockManager returns a new bitcoin block manager. // Use Start to begin processing asynchronous block and inv updates. func newBlockManager(s *server) *blockManager { chainNotify := make(chan *btcchain.Notification, chanBufferSize) bm := blockManager{ server: s, blockChain: btcchain.New(s.db, s.btcnet, chainNotify), blockPeer: make(map[btcwire.ShaHash]*peer), lastBlockLogTime: time.Now(), newBlocks: make(chan bool, 1), blockQueue: make(chan *blockMsg, chanBufferSize), chainNotify: chainNotify, quit: make(chan bool), } bm.blockChain.DisableVerify(cfg.VerifyDisabled) return &bm } // loadBlockDB opens the block database and returns a handle to it. func loadBlockDB() (btcdb.Db, error) { dbPath := filepath.Join(cfg.DbDir, activeNetParams.dbName) log.Infof("[BMGR] Loading block database from '%s'", dbPath) db, err := btcdb.OpenDB("sqlite", 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.DbDir, 0700) if err != nil { return nil, err } db, err = btcdb.CreateDB("sqlite", 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 } // Insert the appropriate genesis block for the bitcoin network being // connected to if needed. if height == -1 { genesis := btcutil.NewBlock(activeNetParams.genesisBlock) _, err := db.InsertBlock(genesis) if err != nil { db.Close() return nil, err } log.Infof("[BMGR] Inserted genesis block %v", activeNetParams.genesisHash) height = 0 } log.Infof("[BMGR] Block database loaded with block height %d", height) return db, nil }