Merge pull request #1215 from davecgh/blockchain_generalize_reorganizechain

blockchain: Generalize chain reorg.
This commit is contained in:
Olaoluwa Osuntokun 2018-07-17 20:36:26 -07:00 committed by GitHub
commit f673a4b563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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
}