github.com/decred/dcrd/blockchain@v1.2.1/accept.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2018 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/decred/dcrd/blockchain/stake"
    12  	"github.com/decred/dcrd/database"
    13  	"github.com/decred/dcrd/dcrutil"
    14  )
    15  
    16  // maybeAcceptBlock potentially accepts a block into the block chain and, if
    17  // accepted, returns the length of the fork the block extended.  It performs
    18  // several validation checks which depend on its position within the block chain
    19  // before adding it.  The block is expected to have already gone through
    20  // ProcessBlock before calling this function with it.  In the case the block
    21  // extends the best chain or is now the tip of the best chain due to causing a
    22  // reorganize, the fork length will be 0.
    23  //
    24  // The flags are also passed to checkBlockPositional, checkBlockContext and
    25  // connectBestChain.  See their documentation for how the flags modify their
    26  // behavior.
    27  //
    28  // This function MUST be called with the chain state lock held (for writes).
    29  func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (int64, error) {
    30  	// This function should never be called with orphan blocks or the
    31  	// genesis block.
    32  	prevHash := &block.MsgBlock().Header.PrevBlock
    33  	prevNode := b.index.LookupNode(prevHash)
    34  	if prevNode == nil {
    35  		str := fmt.Sprintf("previous block %s is not known", prevHash)
    36  		return 0, ruleError(ErrMissingParent, str)
    37  	}
    38  
    39  	// There is no need to validate the block if an ancestor is already
    40  	// known to be invalid.
    41  	if b.index.NodeStatus(prevNode).KnownInvalid() {
    42  		str := fmt.Sprintf("previous block %s is known to be invalid",
    43  			prevHash)
    44  		return 0, ruleError(ErrInvalidAncestorBlock, str)
    45  	}
    46  
    47  	// The block must pass all of the validation rules which depend on having
    48  	// the headers of all ancestors available, but do not rely on having the
    49  	// full block data of all ancestors available.
    50  	err := b.checkBlockPositional(block, prevNode, flags)
    51  	if err != nil {
    52  		return 0, err
    53  	}
    54  
    55  	// The block must pass all of the validation rules which depend on having
    56  	// the full block data for all of its ancestors available.
    57  	err = b.checkBlockContext(block, prevNode, flags)
    58  	if err != nil {
    59  		return 0, err
    60  	}
    61  
    62  	// Prune stake nodes which are no longer needed before creating a new
    63  	// node.
    64  	b.pruner.pruneChainIfNeeded()
    65  
    66  	// Insert the block into the database if it's not already there.  Even
    67  	// though it is possible the block will ultimately fail to connect, it
    68  	// has already passed all proof-of-work and validity tests which means
    69  	// it would be prohibitively expensive for an attacker to fill up the
    70  	// disk with a bunch of blocks that fail to connect.  This is necessary
    71  	// since it allows block download to be decoupled from the much more
    72  	// expensive connection logic.  It also has some other nice properties
    73  	// such as making blocks that never become part of the main chain or
    74  	// blocks that fail to connect available for further analysis.
    75  	err = b.db.Update(func(dbTx database.Tx) error {
    76  		return dbMaybeStoreBlock(dbTx, block)
    77  	})
    78  	if err != nil {
    79  		return 0, err
    80  	}
    81  
    82  	// Create a new block node for the block and add it to the block index.
    83  	// The block could either be on a side chain or the main chain, but it
    84  	// starts off as a side chain regardless.
    85  	blockHeader := &block.MsgBlock().Header
    86  	newNode := newBlockNode(blockHeader, prevNode)
    87  	newNode.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
    88  	newNode.status = statusDataStored
    89  	b.index.AddNode(newNode)
    90  
    91  	// Ensure the new block index entry is written to the database.
    92  	err = b.flushBlockIndex()
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  
    97  	// Notify the caller when the block intends to extend the main chain,
    98  	// the chain believes it is current, and the block has passed all of the
    99  	// sanity and contextual checks, such as having valid proof of work,
   100  	// valid merkle and stake roots, and only containing allowed votes and
   101  	// revocations.
   102  	//
   103  	// This allows the block to be relayed before doing the more expensive
   104  	// connection checks, because even though the block might still fail
   105  	// to connect and becomes the new main chain tip, that is quite rare in
   106  	// practice since a lot of work was expended to create a block that
   107  	// satisifies the proof of work requirement.
   108  	//
   109  	// Notice that the chain lock is not released before sending the
   110  	// notification.  This is intentional and must not be changed without
   111  	// understanding why!
   112  	if b.isCurrent() && b.bestChain.Tip() == prevNode {
   113  		b.sendNotification(NTNewTipBlockChecked, block)
   114  	}
   115  
   116  	// Fetching a stake node could enable a new DoS vector, so restrict
   117  	// this only to blocks that are recent in history.
   118  	if newNode.height < b.bestChain.Tip().height-minMemoryNodes {
   119  		newNode.stakeNode, err = b.fetchStakeNode(newNode)
   120  		if err != nil {
   121  			return 0, err
   122  		}
   123  	}
   124  
   125  	// Grab the parent block since it is required throughout the block
   126  	// connection process.
   127  	parent, err := b.fetchBlockByNode(newNode.parent)
   128  	if err != nil {
   129  		return 0, err
   130  	}
   131  
   132  	// Connect the passed block to the chain while respecting proper chain
   133  	// selection according to the chain with the most proof of work.  This
   134  	// also handles validation of the transaction scripts.
   135  	forkLen, err := b.connectBestChain(newNode, block, parent, flags)
   136  	if err != nil {
   137  		return 0, err
   138  	}
   139  
   140  	// Notify the caller that the new block was accepted into the block
   141  	// chain.  The caller would typically want to react by relaying the
   142  	// inventory to other peers unless it was already relayed above
   143  	// via NTNewTipBlockChecked.
   144  	bestHeight := b.bestChain.Tip().height
   145  	b.chainLock.Unlock()
   146  	b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{
   147  		BestHeight: bestHeight,
   148  		ForkLen:    forkLen,
   149  		Block:      block,
   150  	})
   151  	b.chainLock.Lock()
   152  
   153  	return forkLen, nil
   154  }