diff --git a/blockchain/chain.go b/blockchain/chain.go index c49d728b..71c1b2ee 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1085,6 +1085,17 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) { fastAdd := flags&BFFastAdd == BFFastAdd + flushIndexState := func() { + // Intentionally ignore errors writing updated node status to DB. If + // it fails to write, it's not the end of the world. If the block is + // valid, we flush in connectBlock and if the block is invalid, the + // worst that can happen is we revalidate the block after a restart. + if writeErr := b.index.flushToDB(); writeErr != nil { + log.Warnf("Error flushing block index changes to disk: %v", + writeErr) + } + } + // We are extending the main (best) chain with a new block. This is the // most common case. parentHash := &block.MsgBlock().Header.PrevBlock @@ -1108,14 +1119,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla return false, err } - // Intentionally ignore errors writing updated node status to DB. If - // it fails to write, it's not the end of the world. If the block is - // valid, we flush in connectBlock and if the block is invalid, the - // worst that can happen is we revalidate the block after a restart. - if writeErr := b.index.flushToDB(); writeErr != nil { - log.Warnf("Error flushing block index changes to disk: %v", - writeErr) - } + flushIndexState() if err != nil { return false, err @@ -1140,9 +1144,28 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // Connect the block to the main chain. err := b.connectBlock(node, block, view, stxos) if err != nil { + // If we got hit with a rule error, then we'll mark + // that status of the block as invalid and flush the + // index state to disk before returning with the error. + if _, ok := err.(RuleError); ok { + b.index.SetStatusFlags( + node, statusValidateFailed, + ) + } + + flushIndexState() + return false, err } + // If this is fast add, or this block node isn't yet marked as + // valid, then we'll update its status and flush the state to + // disk again. + if fastAdd || !b.index.NodeStatus(node).KnownValid() { + b.index.SetStatusFlags(node, statusValid) + flushIndexState() + } + return true, nil } if fastAdd { diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 8bd56276..8d76d3ca 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1128,7 +1128,7 @@ func (b *BlockChain) initChainState() error { } // Attempt to load the chain state from the database. - return b.db.View(func(dbTx database.Tx) error { + err = b.db.View(func(dbTx database.Tx) error { // Fetch the stored chain state from the database metadata. // When it doesn't exist, it means the database hasn't been // initialized for use with chain yet, so break out now to allow @@ -1221,6 +1221,25 @@ func (b *BlockChain) initChainState() error { return err } + // As a final consistency check, we'll run through all the + // nodes which are ancestors of the current chain tip, and mark + // them as valid if they aren't already marked as such. This + // is a safe assumption as all the block before the current tip + // are valid by definition. + for iterNode := tip; iterNode != nil; iterNode = iterNode.parent { + // If this isn't already marked as valid in the index, then + // we'll mark it as valid now to ensure consistency once + // we're up and running. + if !iterNode.status.KnownValid() { + log.Infof("Block %v (height=%v) ancestor of "+ + "chain tip not marked as valid, "+ + "upgrading to valid for consistency", + iterNode.hash, iterNode.height) + + b.index.SetStatusFlags(iterNode, statusValid) + } + } + // Initialize the state related to the best block. blockSize := uint64(len(blockBytes)) blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block))) @@ -1230,6 +1249,14 @@ func (b *BlockChain) initChainState() error { return nil }) + if err != nil { + return err + } + + // As we might have updated the index after it was loaded, we'll + // attempt to flush the index to the DB. This will only result in a + // write if the elements are dirty, so it'll usually be a noop. + return b.index.flushToDB() } // deserializeBlockRow parses a value in the block index bucket into a block