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:
parent
42a4366ba8
commit
77913ad2e8
8 changed files with 67 additions and 59 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue