blockchain: Expose main chain flag on ProcessBlock.

This modifies the blockchain.ProcessBlock function to return an
additional boolean as the first parameter which indicates whether or not
the block ended up on the main chain.

This is primarily useful for upcoming test code that needs to be able to
tell the difference between a block accepted to a side chain and a block
that either extends the main chain or causes a reorganize that causes it
to become the main chain.  However, it is also useful for the addblock
utility since it allows a better error in the case a file with out of
order blocks is provided.
This commit is contained in:
Dave Collins 2016-10-12 19:43:01 -05:00
parent 42a4366ba8
commit 77913ad2e8
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
8 changed files with 67 additions and 59 deletions

View file

@ -6,10 +6,11 @@ package blockchain
import "github.com/btcsuite/btcutil" import "github.com/btcsuite/btcutil"
// maybeAcceptBlock potentially accepts a block into the memory block chain. // maybeAcceptBlock potentially accepts a block into the block chain and, if
// It performs several validation checks which depend on its position within // accepted, returns whether or not it is on the main chain. It performs
// the block chain before adding it. The block is expected to have already gone // several validation checks which depend on its position within the block chain
// through ProcessBlock before calling this function with it. // before adding it. The block is expected to have already gone through
// ProcessBlock before calling this function with it.
// //
// The flags modify the behavior of this function as follows: // The flags modify the behavior of this function as follows:
// - BFDryRun: The memory chain index will not be pruned and no accept // - BFDryRun: The memory chain index will not be pruned and no accept
@ -19,7 +20,7 @@ import "github.com/btcsuite/btcutil"
// their documentation for how the flags modify their behavior. // their documentation for how the flags modify their behavior.
// //
// This function MUST be called with the chain state lock held (for writes). // This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) error { func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
dryRun := flags&BFDryRun == BFDryRun dryRun := flags&BFDryRun == BFDryRun
// Get a block node for the block previous to this one. Will be nil // Get a block node for the block previous to this one. Will be nil
@ -27,7 +28,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
prevNode, err := b.getPrevNodeFromBlock(block) prevNode, err := b.getPrevNodeFromBlock(block)
if err != nil { if err != nil {
log.Errorf("getPrevNodeFromBlock: %v", err) log.Errorf("getPrevNodeFromBlock: %v", err)
return err return false, err
} }
// The height of this block is one more than the referenced previous // The height of this block is one more than the referenced previous
@ -42,7 +43,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// position of the block within the block chain. // position of the block within the block chain.
err = b.checkBlockContext(block, prevNode, flags) err = b.checkBlockContext(block, prevNode, flags)
if err != nil { if err != nil {
return err return false, err
} }
// Prune block nodes which are no longer needed before creating // Prune block nodes which are no longer needed before creating
@ -50,7 +51,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
if !dryRun { if !dryRun {
err = b.pruneBlockNodes() err = b.pruneBlockNodes()
if err != nil { if err != nil {
return err return false, err
} }
} }
@ -67,9 +68,9 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// Connect the passed block to the chain while respecting proper chain // Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This // selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts. // also handles validation of the transaction scripts.
err = b.connectBestChain(newNode, block, flags) isMainChain, err := b.connectBestChain(newNode, block, flags)
if err != nil { if err != nil {
return err return false, err
} }
// Notify the caller that the new block was accepted into the block // Notify the caller that the new block was accepted into the block
@ -81,5 +82,5 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
b.chainLock.Lock() b.chainLock.Lock()
} }
return nil return isMainChain, nil
} }

View file

@ -1205,7 +1205,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// proof of work. In the typical case, the new block simply extends the main // proof of work. In the typical case, the new block simply extends the main
// chain. However, it may also be extending (or creating) a side chain (fork) // chain. However, it may also be extending (or creating) a side chain (fork)
// which may or may not end up becoming the main chain depending on which fork // which may or may not end up becoming the main chain depending on which fork
// cumulatively has the most proof of work. // cumulatively has the most proof of work. It returns whether or not the block
// ended up on the main chain (either due to extending the main chain or causing
// a reorganization to become the main chain).
// //
// The flags modify the behavior of this function as follows: // The flags modify the behavior of this function as follows:
// - BFFastAdd: Avoids several expensive transaction validation operations. // - BFFastAdd: Avoids several expensive transaction validation operations.
@ -1215,7 +1217,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// modifying the state are avoided. // modifying the state are avoided.
// //
// This function MUST be called with the chain state lock held (for writes). // This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) error { func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
fastAdd := flags&BFFastAdd == BFFastAdd fastAdd := flags&BFFastAdd == BFFastAdd
dryRun := flags&BFDryRun == BFDryRun dryRun := flags&BFDryRun == BFDryRun
@ -1231,13 +1233,13 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
if !fastAdd { if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos) err := b.checkConnectBlock(node, block, view, &stxos)
if err != nil { if err != nil {
return err return false, err
} }
} }
// Don't connect the block if performing a dry run. // Don't connect the block if performing a dry run.
if dryRun { if dryRun {
return nil return true, nil
} }
// In the fast add case the code to check the block connection // In the fast add case the code to check the block connection
@ -1247,18 +1249,18 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
if fastAdd { if fastAdd {
err := view.fetchInputUtxos(b.db, block) err := view.fetchInputUtxos(b.db, block)
if err != nil { if err != nil {
return err return false, err
} }
err = view.connectTransactions(block, &stxos) err = view.connectTransactions(block, &stxos)
if err != nil { if err != nil {
return err return false, err
} }
} }
// Connect the block to the main chain. // Connect the block to the main chain.
err := b.connectBlock(node, block, view, stxos) err := b.connectBlock(node, block, view, stxos)
if err != nil { if err != nil {
return err return false, err
} }
// Connect the parent node to this node. // Connect the parent node to this node.
@ -1266,7 +1268,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
node.parent.children = append(node.parent.children, node) node.parent.children = append(node.parent.children, node)
} }
return nil return true, nil
} }
if fastAdd { if fastAdd {
log.Warnf("fastAdd set in the side chain case? %v\n", log.Warnf("fastAdd set in the side chain case? %v\n",
@ -1305,7 +1307,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
if node.workSum.Cmp(b.bestNode.workSum) <= 0 { if node.workSum.Cmp(b.bestNode.workSum) <= 0 {
// Skip Logging info when the dry run flag is set. // Skip Logging info when the dry run flag is set.
if dryRun { if dryRun {
return nil return false, nil
} }
// Find the fork point. // Find the fork point.
@ -1327,7 +1329,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
node.hash, fork.height, fork.hash) node.hash, fork.height, fork.hash)
} }
return nil return false, nil
} }
// We're extending (or creating) a side chain and the cumulative work // We're extending (or creating) a side chain and the cumulative work
@ -1346,10 +1348,10 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
} }
err := b.reorganizeChain(detachNodes, attachNodes, flags) err := b.reorganizeChain(detachNodes, attachNodes, flags)
if err != nil { if err != nil {
return err return false, err
} }
return nil return true, nil
} }
// IsCurrent returns whether or not the chain believes it is current. Several // IsCurrent returns whether or not the chain believes it is current. Several

View file

@ -49,7 +49,7 @@ func TestHaveBlock(t *testing.T) {
chain.TstSetCoinbaseMaturity(1) chain.TstSetCoinbaseMaturity(1)
for i := 1; i < len(blocks); i++ { for i := 1; i < len(blocks); i++ {
isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone) _, isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone)
if err != nil { if err != nil {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return return
@ -62,7 +62,7 @@ func TestHaveBlock(t *testing.T) {
} }
// Insert an orphan block. // Insert an orphan block.
isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000), _, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
blockchain.BFNone) blockchain.BFNone)
if err != nil { if err != nil {
t.Errorf("Unable to process block: %v", err) t.Errorf("Unable to process block: %v", err)

View file

@ -59,11 +59,13 @@ func ExampleBlockChain_ProcessBlock() {
// cause an error by trying to process the genesis block which already // cause an error by trying to process the genesis block which already
// exists. // exists.
genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
isOrphan, err := chain.ProcessBlock(genesisBlock, blockchain.BFNone) isMainChain, isOrphan, err := chain.ProcessBlock(genesisBlock,
blockchain.BFNone)
if err != nil { if err != nil {
fmt.Printf("Failed to process block: %v\n", err) fmt.Printf("Failed to process block: %v\n", err)
return return
} }
fmt.Printf("Block accepted. Is it on the main chain?: %v", isMainChain)
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan) fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
// Output: // Output:

View file

@ -101,7 +101,7 @@ func (b *BlockChain) processOrphans(hash *chainhash.Hash, flags BehaviorFlags) e
i-- i--
// Potentially accept the block into the block chain. // Potentially accept the block into the block chain.
err := b.maybeAcceptBlock(orphan.block, flags) _, err := b.maybeAcceptBlock(orphan.block, flags)
if err != nil { if err != nil {
return err return err
} }
@ -120,12 +120,12 @@ func (b *BlockChain) processOrphans(hash *chainhash.Hash, flags BehaviorFlags) e
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into // blocks, ensuring blocks follow all rules, orphan handling, and insertion into
// the block chain along with best chain selection and reorganization. // the block chain along with best chain selection and reorganization.
// //
// It returns a bool which indicates whether or not the block is an orphan and // When no errors occurred during processing, the first return value indicates
// any errors that occurred during processing. The returned bool is only valid // whether or not the block is on the main chain and the second indicates
// when the error is nil. // whether or not the block is an orphan.
// //
// This function is safe for concurrent access. // This function is safe for concurrent access.
func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) { func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, bool, error) {
b.chainLock.Lock() b.chainLock.Lock()
defer b.chainLock.Unlock() defer b.chainLock.Unlock()
@ -138,23 +138,23 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// The block must not already exist in the main chain or side chains. // The block must not already exist in the main chain or side chains.
exists, err := b.blockExists(blockHash) exists, err := b.blockExists(blockHash)
if err != nil { if err != nil {
return false, err return false, false, err
} }
if exists { if exists {
str := fmt.Sprintf("already have block %v", blockHash) str := fmt.Sprintf("already have block %v", blockHash)
return false, ruleError(ErrDuplicateBlock, str) return false, false, ruleError(ErrDuplicateBlock, str)
} }
// The block must not already exist as an orphan. // The block must not already exist as an orphan.
if _, exists := b.orphans[*blockHash]; exists { if _, exists := b.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %v", blockHash) str := fmt.Sprintf("already have block (orphan) %v", blockHash)
return false, ruleError(ErrDuplicateBlock, str) return false, false, ruleError(ErrDuplicateBlock, str)
} }
// Perform preliminary sanity checks on the block and its transactions. // Perform preliminary sanity checks on the block and its transactions.
err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags) err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)
if err != nil { if err != nil {
return false, err return false, false, err
} }
// Find the previous checkpoint and perform some additional checks based // Find the previous checkpoint and perform some additional checks based
@ -166,7 +166,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
blockHeader := &block.MsgBlock().Header blockHeader := &block.MsgBlock().Header
checkpointBlock, err := b.findPreviousCheckpoint() checkpointBlock, err := b.findPreviousCheckpoint()
if err != nil { if err != nil {
return false, err return false, false, err
} }
if checkpointBlock != nil { if checkpointBlock != nil {
// Ensure the block timestamp is after the checkpoint timestamp. // Ensure the block timestamp is after the checkpoint timestamp.
@ -176,7 +176,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
str := fmt.Sprintf("block %v has timestamp %v before "+ str := fmt.Sprintf("block %v has timestamp %v before "+
"last checkpoint timestamp %v", blockHash, "last checkpoint timestamp %v", blockHash,
blockHeader.Timestamp, checkpointTime) blockHeader.Timestamp, checkpointTime)
return false, ruleError(ErrCheckpointTimeTooOld, str) return false, false, ruleError(ErrCheckpointTimeTooOld, str)
} }
if !fastAdd { if !fastAdd {
// Even though the checks prior to now have already ensured the // Even though the checks prior to now have already ensured the
@ -193,17 +193,16 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
str := fmt.Sprintf("block target difficulty of %064x "+ str := fmt.Sprintf("block target difficulty of %064x "+
"is too low when compared to the previous "+ "is too low when compared to the previous "+
"checkpoint", currentTarget) "checkpoint", currentTarget)
return false, ruleError(ErrDifficultyTooLow, str) return false, false, ruleError(ErrDifficultyTooLow, str)
} }
} }
} }
// Handle orphan blocks. // Handle orphan blocks.
prevHash := &blockHeader.PrevBlock prevHash := &blockHeader.PrevBlock
if !prevHash.IsEqual(zeroHash) {
prevHashExists, err := b.blockExists(prevHash) prevHashExists, err := b.blockExists(prevHash)
if err != nil { if err != nil {
return false, err return false, false, err
} }
if !prevHashExists { if !prevHashExists {
if !dryRun { if !dryRun {
@ -212,15 +211,14 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
b.addOrphanBlock(block) b.addOrphanBlock(block)
} }
return true, nil return false, true, nil
}
} }
// The block has passed all context independent checks and appears sane // The block has passed all context independent checks and appears sane
// enough to potentially accept it into the block chain. // enough to potentially accept it into the block chain.
err = b.maybeAcceptBlock(block, flags) isMainChain, err := b.maybeAcceptBlock(block, flags)
if err != nil { if err != nil {
return false, err return false, false, err
} }
// Don't process any orphans or log when the dry run flag is set. // Don't process any orphans or log when the dry run flag is set.
@ -230,11 +228,11 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// there are no more. // there are no more.
err := b.processOrphans(blockHash, flags) err := b.processOrphans(blockHash, flags)
if err != nil { if err != nil {
return false, err return false, false, err
} }
log.Debugf("Accepted block %v", blockHash) log.Debugf("Accepted block %v", blockHash)
} }
return false, nil return isMainChain, false, nil
} }

View file

@ -60,7 +60,7 @@ func TestReorganization(t *testing.T) {
expectedOrphans := map[int]struct{}{5: {}, 6: {}} expectedOrphans := map[int]struct{}{5: {}, 6: {}}
for i := 1; i < len(blocks); i++ { for i := 1; i < len(blocks); i++ {
isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone) _, isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone)
if err != nil { if err != nil {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return return

View file

@ -566,7 +566,7 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
// Process the block to include validation, best chain selection, orphan // Process the block to include validation, best chain selection, orphan
// handling, etc. // handling, etc.
isOrphan, err := b.chain.ProcessBlock(bmsg.block, behaviorFlags) _, isOrphan, err := b.chain.ProcessBlock(bmsg.block, behaviorFlags)
if err != nil { if err != nil {
// When the error is a rule error, it means the block was simply // When the error is a rule error, it means the block was simply
// rejected as opposed to something actually going wrong, so log // rejected as opposed to something actually going wrong, so log
@ -1121,8 +1121,8 @@ out:
msg.reply <- b.syncPeer msg.reply <- b.syncPeer
case processBlockMsg: case processBlockMsg:
isOrphan, err := b.chain.ProcessBlock(msg.block, _, isOrphan, err := b.chain.ProcessBlock(
msg.flags) msg.block, msg.flags)
if err != nil { if err != nil {
msg.reply <- processBlockResponse{ msg.reply <- processBlockResponse{
isOrphan: false, isOrphan: false,

View file

@ -129,10 +129,15 @@ func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
// Ensure the blocks follows all of the chain rules and match up to the // Ensure the blocks follows all of the chain rules and match up to the
// known checkpoints. // known checkpoints.
isOrphan, err := bi.chain.ProcessBlock(block, blockchain.BFFastAdd) isMainChain, isOrphan, err := bi.chain.ProcessBlock(block,
blockchain.BFFastAdd)
if err != nil { if err != nil {
return false, err return false, err
} }
if !isMainChain {
return false, fmt.Errorf("import file contains an block that "+
"does not extend the main chain: %v", blockHash)
}
if isOrphan { if isOrphan {
return false, fmt.Errorf("import file contains an orphan "+ return false, fmt.Errorf("import file contains an orphan "+
"block: %v", blockHash) "block: %v", blockHash)