diff --git a/blockchain/chain.go b/blockchain/chain.go index 84996f312..c13fabd9a 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1,4 +1,5 @@ -// Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2013-2018 The btcsuite developers +// Copyright (c) 2015-2018 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -819,6 +820,38 @@ func countSpentOutputs(block *btcutil.Block) int { // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error { + // Nothing to do if no reorganize nodes were provided. + if detachNodes.Len() == 0 && attachNodes.Len() == 0 { + return nil + } + + // Ensure the provided nodes match the current best chain. + tip := b.bestChain.Tip() + if detachNodes.Len() != 0 { + firstDetachNode := detachNodes.Front().Value.(*blockNode) + if firstDetachNode.hash != tip.hash { + return AssertError(fmt.Sprintf("reorganize nodes to detach are "+ + "not for the current best chain -- first detach node %v, "+ + "current chain %v", &firstDetachNode.hash, &tip.hash)) + } + } + + // Ensure the provided nodes are for the same fork point. + if attachNodes.Len() != 0 && detachNodes.Len() != 0 { + firstAttachNode := attachNodes.Front().Value.(*blockNode) + lastDetachNode := detachNodes.Back().Value.(*blockNode) + if firstAttachNode.parent.hash != lastDetachNode.parent.hash { + return AssertError(fmt.Sprintf("reorganize nodes do not have the "+ + "same fork point -- first attach parent %v, last detach "+ + "parent %v", &firstAttachNode.parent.hash, + &lastDetachNode.parent.hash)) + } + } + + // Track the old and new best chains heads. + oldBest := tip + newBest := tip + // All of the blocks to detach and related spend journal entries needed // to unspend transaction outputs in the blocks being disconnected must // be loaded from the database during the reorg check phase below and @@ -833,7 +866,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // database and using that information to unspend all of the spent txos // and remove the utxos created by the blocks. view := NewUtxoViewpoint() - view.SetBestHash(&b.bestChain.Tip().hash) + view.SetBestHash(&oldBest.hash) for e := detachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) var block *btcutil.Block @@ -845,6 +878,11 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if err != nil { return err } + if n.hash != *block.Hash() { + return AssertError(fmt.Sprintf("detach block node hash %v (height "+ + "%v) does not match previous parent block hash %v", &n.hash, + n.height, block.Hash())) + } // Load all of the utxos referenced by the block that aren't // already in the view. @@ -872,6 +910,15 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if err != nil { return err } + + newBest = n.parent + } + + // Set the fork point only if there are nodes to attach since otherwise + // blocks are only being disconnected and thus there is no fork point. + var forkNode *blockNode + if attachNodes.Len() > 0 { + forkNode = newBest } // Perform several checks to verify each block that needs to be attached @@ -886,17 +933,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // at least a couple of ways accomplish that rollback, but both involve // tweaking the chain and/or database. This approach catches these // issues before ever modifying the chain. - var validationError error for e := attachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) - // If any previous nodes in attachNodes failed validation, - // mark this one as having an invalid ancestor. - if validationError != nil { - b.index.SetStatusFlags(n, statusInvalidAncestor) - continue - } - var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error @@ -922,6 +961,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if err != nil { return err } + + newBest = n continue } @@ -929,23 +970,24 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // thus will not be generated. This is done because the state // is not being immediately written to the database, so it is // not needed. + // + // In the case the block is determined to be invalid due to a + // rule violation, mark it as invalid and mark all of its + // descendants as having an invalid ancestor. err = b.checkConnectBlock(n, block, view, nil) if err != nil { - // If the block failed validation mark it as invalid, then - // continue to loop through remaining nodes, marking them as - // having an invalid ancestor. if _, ok := err.(RuleError); ok { b.index.SetStatusFlags(n, statusValidateFailed) - validationError = err - continue + for de := e.Next(); de != nil; de = de.Next() { + dn := de.Value.(*blockNode) + b.index.SetStatusFlags(dn, statusInvalidAncestor) + } } return err } b.index.SetStatusFlags(n, statusValid) - } - if validationError != nil { - return validationError + newBest = n } // Reset the view for the actual connection code below. This is @@ -1014,12 +1056,14 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Log the point where the chain forked and old and new best chain // heads. - firstAttachNode := attachNodes.Front().Value.(*blockNode) - firstDetachNode := detachNodes.Front().Value.(*blockNode) - lastAttachNode := attachNodes.Back().Value.(*blockNode) - log.Infof("REORGANIZE: Chain forks at %v", firstAttachNode.parent.hash) - log.Infof("REORGANIZE: Old best chain head was %v", firstDetachNode.hash) - log.Infof("REORGANIZE: New best chain head is %v", lastAttachNode.hash) + if forkNode != nil { + log.Infof("REORGANIZE: Chain forks at %v (height %v)", forkNode.hash, + forkNode.height) + } + log.Infof("REORGANIZE: Old best chain head was %v (height %v)", + &oldBest.hash, oldBest.height) + log.Infof("REORGANIZE: New best chain head is %v (height %v)", + newBest.hash, newBest.height) return nil }