blockchain: NodeStatus & Set/UnsetStatusFlags methods on blockIndex.

These method allows safe concurrent access to reading and modifying
block node statuses. When block statuses get persisted in a later
change, the setter methods can be used to mark block nodes as dirty.
This commit is contained in:
Jim Posen 2017-10-13 14:36:40 -07:00 committed by Dave Collins
parent e1ef2f899b
commit c7588cbf76
4 changed files with 65 additions and 35 deletions

View file

@ -29,7 +29,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
if prevNode == nil { if prevNode == nil {
str := fmt.Sprintf("previous block %s is unknown", prevHash) str := fmt.Sprintf("previous block %s is unknown", prevHash)
return false, ruleError(ErrPreviousBlockUnknown, str) return false, ruleError(ErrPreviousBlockUnknown, str)
} else if prevNode.KnownInvalid() { } else if b.index.NodeStatus(prevNode).KnownInvalid() {
str := fmt.Sprintf("previous block %s is known to be invalid", prevHash) str := fmt.Sprintf("previous block %s is known to be invalid", prevHash)
return false, ruleError(ErrInvalidAncestorBlock, str) return false, ruleError(ErrInvalidAncestorBlock, str)
} }
@ -64,7 +64,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// block chain (could be either a side chain or the main chain). // block chain (could be either a side chain or the main chain).
blockHeader := &block.MsgBlock().Header blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, blockHeight) newNode := newBlockNode(blockHeader, blockHeight)
newNode.status |= statusDataStored newNode.status = statusDataStored
if prevNode != nil { if prevNode != nil {
newNode.parent = prevNode newNode.parent = prevNode
newNode.height = blockHeight newNode.height = blockHeight

View file

@ -39,6 +39,27 @@ const (
statusNone blockStatus = 0 statusNone blockStatus = 0
) )
// HaveData returns whether the full block data is stored in the database. This
// will return false for a block node where only the header is downloaded or
// kept.
func (status blockStatus) HaveData() bool {
return status&statusDataStored != 0
}
// KnownValid returns whether the block is known to be valid. This will return
// false for a valid block that has not been fully validated yet.
func (status blockStatus) KnownValid() bool {
return status&statusValid != 0
}
// KnownInvalid returns whether the block is known to be invalid. This may be
// because the block itself failed validation or any of its ancestors is
// invalid. This will return false for invalid blocks that have not been proven
// invalid yet.
func (status blockStatus) KnownInvalid() bool {
return status&(statusValidateFailed|statusInvalidAncestor) != 0
}
// blockNode represents a block within the block chain and is primarily used to // blockNode represents a block within the block chain and is primarily used to
// aid in selecting the best chain to be the main chain. The main chain is // aid in selecting the best chain to be the main chain. The main chain is
// stored into the block database. // stored into the block database.
@ -73,7 +94,10 @@ type blockNode struct {
timestamp int64 timestamp int64
merkleRoot chainhash.Hash merkleRoot chainhash.Hash
// status is a bitfield representing the validation state of the block // status is a bitfield representing the validation state of the block. The
// status field, unlike the other fields, may be written to and so should
// only be accessed using the concurrent-safe NodeStatus method on
// blockIndex once the node has been added to the global index.
status blockStatus status blockStatus
} }
@ -193,20 +217,6 @@ func (node *blockNode) CalcPastMedianTime() time.Time {
return time.Unix(medianTimestamp, 0) return time.Unix(medianTimestamp, 0)
} }
// KnownValid returns whether the block is known to be valid. This will return
// false for a valid block that has not been fully validated yet.
func (node *blockNode) KnownValid() bool {
return node.status&statusValid != 0
}
// KnownInvalid returns whether the block is known to be invalid. This may be
// because the block itself failed validation or any of its ancestors is
// invalid. This will return false for invalid blocks that have not been proven
// invalid yet.
func (node *blockNode) KnownInvalid() bool {
return node.status&(statusValidateFailed|statusInvalidAncestor) != 0
}
// blockIndex provides facilities for keeping track of an in-memory index of the // blockIndex provides facilities for keeping track of an in-memory index of the
// block chain. Although the name block chain suggests a single chain of // block chain. Although the name block chain suggests a single chain of
// blocks, it is actually a tree-shaped structure where any node can have // blocks, it is actually a tree-shaped structure where any node can have
@ -265,13 +275,33 @@ func (bi *blockIndex) AddNode(node *blockNode) {
bi.Unlock() bi.Unlock()
} }
// RemoveNode removes the provided node from the block index. There is no check // NodeStatus provides concurrent-safe access to the status field of a node.
// whether another node in the index depends on this one, so it is up to caller
// to avoid that situation.
// //
// This function is safe for concurrent access. // This function is safe for concurrent access.
func (bi *blockIndex) RemoveNode(node *blockNode) { func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
bi.RLock()
status := node.status
bi.RUnlock()
return status
}
// SetStatusFlags flips the provided status flags on the block node to on,
// regardless of whether they were on or off previously. This does not unset any
// flags currently on.
//
// This function is safe for concurrent access.
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock() bi.Lock()
delete(bi.index, node.hash) node.status |= flags
bi.Unlock()
}
// UnsetStatusFlags flips the provided status flags on the block node to off,
// regardless of whether they were on or off previously.
//
// This function is safe for concurrent access.
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
node.status &^= flags
bi.Unlock() bi.Unlock()
} }

View file

@ -503,8 +503,8 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
// Do not reorganize to a known invalid chain. Ancestors deeper than the // Do not reorganize to a known invalid chain. Ancestors deeper than the
// direct parent are checked below but this is a quick check before doing // direct parent are checked below but this is a quick check before doing
// more unnecessary work. // more unnecessary work.
if node.parent.KnownInvalid() { if b.index.NodeStatus(node.parent).KnownInvalid() {
node.status |= statusInvalidAncestor b.index.SetStatusFlags(node, statusInvalidAncestor)
return detachNodes, attachNodes return detachNodes, attachNodes
} }
@ -515,7 +515,7 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
forkNode := b.bestChain.FindFork(node) forkNode := b.bestChain.FindFork(node)
invalidChain := false invalidChain := false
for n := node; n != nil && n != forkNode; n = n.parent { for n := node; n != nil && n != forkNode; n = n.parent {
if n.KnownInvalid() { if b.index.NodeStatus(n).KnownInvalid() {
invalidChain = true invalidChain = true
break break
} }
@ -529,7 +529,7 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
for e := attachNodes.Front(); e != nil; e = next { for e := attachNodes.Front(); e != nil; e = next {
next = e.Next() next = e.Next()
n := attachNodes.Remove(e).(*blockNode) n := attachNodes.Remove(e).(*blockNode)
n.status |= statusInvalidAncestor b.index.SetStatusFlags(n, statusInvalidAncestor)
} }
return detachNodes, attachNodes return detachNodes, attachNodes
} }
@ -882,7 +882,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// If any previous nodes in attachNodes failed validation, // If any previous nodes in attachNodes failed validation,
// mark this one as having an invalid ancestor. // mark this one as having an invalid ancestor.
if validationError != nil { if validationError != nil {
n.status |= statusInvalidAncestor b.index.SetStatusFlags(n, statusInvalidAncestor)
continue continue
} }
@ -902,7 +902,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// Skip checks if node has already been fully validated. Although // Skip checks if node has already been fully validated. Although
// checkConnectBlock gets skipped, we still need to update the UTXO // checkConnectBlock gets skipped, we still need to update the UTXO
// view. // view.
if n.KnownValid() { if b.index.NodeStatus(n).KnownValid() {
err = view.fetchInputUtxos(b.db, block) err = view.fetchInputUtxos(b.db, block)
if err != nil { if err != nil {
return err return err
@ -924,13 +924,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// continue to loop through remaining nodes, marking them as // continue to loop through remaining nodes, marking them as
// having an invalid ancestor. // having an invalid ancestor.
if _, ok := err.(RuleError); ok { if _, ok := err.(RuleError); ok {
n.status |= statusValidateFailed b.index.SetStatusFlags(n, statusValidateFailed)
validationError = err validationError = err
continue continue
} }
return err return err
} }
n.status |= statusValid b.index.SetStatusFlags(n, statusValid)
} }
if validationError != nil { if validationError != nil {
@ -1034,7 +1034,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
parentHash := &block.MsgBlock().Header.PrevBlock parentHash := &block.MsgBlock().Header.PrevBlock
if parentHash.IsEqual(&b.bestChain.Tip().hash) { if parentHash.IsEqual(&b.bestChain.Tip().hash) {
// Skip checks if node has already been fully validated. // Skip checks if node has already been fully validated.
fastAdd = fastAdd || node.KnownValid() fastAdd = fastAdd || b.index.NodeStatus(node).KnownValid()
// Perform several checks to verify the block can be connected // Perform several checks to verify the block can be connected
// to the main chain without violating any rules and without // to the main chain without violating any rules and without
@ -1046,11 +1046,11 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
err := b.checkConnectBlock(node, block, view, &stxos) err := b.checkConnectBlock(node, block, view, &stxos)
if err != nil { if err != nil {
if _, ok := err.(RuleError); ok { if _, ok := err.(RuleError); ok {
node.status |= statusValidateFailed b.index.SetStatusFlags(node, statusValidateFailed)
} }
return false, err return false, err
} }
node.status |= statusValid b.index.SetStatusFlags(node, statusValid)
} }
// In the fast add case the code to check the block connection // In the fast add case the code to check the block connection

View file

@ -1060,7 +1060,7 @@ func (b *BlockChain) createChainState() error {
genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock) genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock)
header := &genesisBlock.MsgBlock().Header header := &genesisBlock.MsgBlock().Header
node := newBlockNode(header, 0) node := newBlockNode(header, 0)
node.status |= statusDataStored | statusValid node.status = statusDataStored | statusValid
b.bestChain.SetTip(node) b.bestChain.SetTip(node)
// Add the new node to the index which is used for faster lookups. // Add the new node to the index which is used for faster lookups.
@ -1165,7 +1165,7 @@ func (b *BlockChain) initChainState() error {
// and add it to the block index. // and add it to the block index.
node := &blockNodes[height] node := &blockNodes[height]
initBlockNode(node, header, height) initBlockNode(node, header, height)
node.status |= statusDataStored | statusValid node.status = statusDataStored | statusValid
if tip != nil { if tip != nil {
node.parent = tip node.parent = tip
node.workSum = node.workSum.Add(tip.workSum, node.workSum = node.workSum.Add(tip.workSum,