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"
// maybeAcceptBlock potentially accepts a block into the memory block chain.
// It performs several validation checks which depend on its position within
// the block chain before adding it. The block is expected to have already gone
// through ProcessBlock before calling this function with it.
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns whether or not it is on the main chain. It performs
// several validation checks which depend on its position within the block chain
// 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:
// - 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.
//
// 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
// 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)
if err != nil {
log.Errorf("getPrevNodeFromBlock: %v", err)
return err
return false, err
}
// 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.
err = b.checkBlockContext(block, prevNode, flags)
if err != nil {
return err
return false, err
}
// 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 {
err = b.pruneBlockNodes()
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
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
err = b.connectBestChain(newNode, block, flags)
isMainChain, err := b.connectBestChain(newNode, block, flags)
if err != nil {
return err
return false, err
}
// 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()
}
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
// 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
// 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:
// - 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.
//
// 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
dryRun := flags&BFDryRun == BFDryRun
@ -1231,13 +1233,13 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos)
if err != nil {
return err
return false, err
}
}
// Don't connect the block if performing a dry run.
if dryRun {
return nil
return true, nil
}
// 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 {
err := view.fetchInputUtxos(b.db, block)
if err != nil {
return err
return false, err
}
err = view.connectTransactions(block, &stxos)
if err != nil {
return err
return false, err
}
}
// Connect the block to the main chain.
err := b.connectBlock(node, block, view, stxos)
if err != nil {
return err
return false, err
}
// 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)
}
return nil
return true, nil
}
if fastAdd {
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 {
// Skip Logging info when the dry run flag is set.
if dryRun {
return nil
return false, nil
}
// Find the fork point.
@ -1327,7 +1329,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
node.hash, fork.height, fork.hash)
}
return nil
return false, nil
}
// 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)
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

View file

@ -49,7 +49,7 @@ func TestHaveBlock(t *testing.T) {
chain.TstSetCoinbaseMaturity(1)
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 {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return
@ -62,7 +62,7 @@ func TestHaveBlock(t *testing.T) {
}
// Insert an orphan block.
isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
_, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
blockchain.BFNone)
if err != nil {
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
// exists.
genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
isOrphan, err := chain.ProcessBlock(genesisBlock, blockchain.BFNone)
isMainChain, isOrphan, err := chain.ProcessBlock(genesisBlock,
blockchain.BFNone)
if err != nil {
fmt.Printf("Failed to process block: %v\n", err)
return
}
fmt.Printf("Block accepted. Is it on the main chain?: %v", isMainChain)
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
// Output:

View file

@ -101,7 +101,7 @@ func (b *BlockChain) processOrphans(hash *chainhash.Hash, flags BehaviorFlags) e
i--
// Potentially accept the block into the block chain.
err := b.maybeAcceptBlock(orphan.block, flags)
_, err := b.maybeAcceptBlock(orphan.block, flags)
if err != nil {
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
// 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
// any errors that occurred during processing. The returned bool is only valid
// when the error is nil.
// When no errors occurred during processing, the first return value indicates
// whether or not the block is on the main chain and the second indicates
// whether or not the block is an orphan.
//
// 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()
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.
exists, err := b.blockExists(blockHash)
if err != nil {
return false, err
return false, false, err
}
if exists {
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.
if _, exists := b.orphans[*blockHash]; exists {
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.
err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)
if err != nil {
return false, err
return false, false, err
}
// 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
checkpointBlock, err := b.findPreviousCheckpoint()
if err != nil {
return false, err
return false, false, err
}
if checkpointBlock != nil {
// 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 "+
"last checkpoint timestamp %v", blockHash,
blockHeader.Timestamp, checkpointTime)
return false, ruleError(ErrCheckpointTimeTooOld, str)
return false, false, ruleError(ErrCheckpointTimeTooOld, str)
}
if !fastAdd {
// Even though the checks prior to now have already ensured the
@ -193,34 +193,32 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
str := fmt.Sprintf("block target difficulty of %064x "+
"is too low when compared to the previous "+
"checkpoint", currentTarget)
return false, ruleError(ErrDifficultyTooLow, str)
return false, false, ruleError(ErrDifficultyTooLow, str)
}
}
}
// Handle orphan blocks.
prevHash := &blockHeader.PrevBlock
if !prevHash.IsEqual(zeroHash) {
prevHashExists, err := b.blockExists(prevHash)
if err != nil {
return false, err
prevHashExists, err := b.blockExists(prevHash)
if err != nil {
return false, false, err
}
if !prevHashExists {
if !dryRun {
log.Infof("Adding orphan block %v with parent %v",
blockHash, prevHash)
b.addOrphanBlock(block)
}
if !prevHashExists {
if !dryRun {
log.Infof("Adding orphan block %v with parent %v",
blockHash, prevHash)
b.addOrphanBlock(block)
}
return true, nil
}
return false, true, nil
}
// The block has passed all context independent checks and appears sane
// enough to potentially accept it into the block chain.
err = b.maybeAcceptBlock(block, flags)
isMainChain, err := b.maybeAcceptBlock(block, flags)
if err != nil {
return false, err
return false, false, err
}
// 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.
err := b.processOrphans(blockHash, flags)
if err != nil {
return false, err
return false, false, err
}
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: {}}
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 {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return

View file

@ -566,7 +566,7 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
// Process the block to include validation, best chain selection, orphan
// handling, etc.
isOrphan, err := b.chain.ProcessBlock(bmsg.block, behaviorFlags)
_, isOrphan, err := b.chain.ProcessBlock(bmsg.block, behaviorFlags)
if err != nil {
// When the error is a rule error, it means the block was simply
// rejected as opposed to something actually going wrong, so log
@ -1121,8 +1121,8 @@ out:
msg.reply <- b.syncPeer
case processBlockMsg:
isOrphan, err := b.chain.ProcessBlock(msg.block,
msg.flags)
_, isOrphan, err := b.chain.ProcessBlock(
msg.block, msg.flags)
if err != nil {
msg.reply <- processBlockResponse{
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
// known checkpoints.
isOrphan, err := bi.chain.ProcessBlock(block, blockchain.BFFastAdd)
isMainChain, isOrphan, err := bi.chain.ProcessBlock(block,
blockchain.BFFastAdd)
if err != nil {
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 {
return false, fmt.Errorf("import file contains an orphan "+
"block: %v", blockHash)