blockchain: Generalize chain reorg.
This generalizes the reorganizeChain function in the blockchain package to allow it to rewind the chain in the case no attach nodes are provided or extend the chain in the case no detach nodes are provided. It also adds several assertions to ensure the assumptions about the state hold and cleans up the handling of setting invalid ancestor nodes in the case of a failed block validation.
This commit is contained in:
parent
fdfc19097e
commit
b091d71646
1 changed files with 68 additions and 24 deletions
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue