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

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2016 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  	"time"
    11  
    12  	"github.com/decred/dcrd/chaincfg"
    13  	"github.com/decred/dcrd/chaincfg/chainhash"
    14  	"github.com/decred/dcrd/dcrutil"
    15  	"github.com/decred/dcrd/txscript"
    16  )
    17  
    18  // CheckpointConfirmations is the number of blocks before the end of the current
    19  // best block chain that a good checkpoint candidate must be.
    20  const CheckpointConfirmations = 4096
    21  
    22  // DisableCheckpoints provides a mechanism to disable validation against
    23  // checkpoints which you DO NOT want to do in production.  It is provided only
    24  // for debug purposes.
    25  //
    26  // This function is safe for concurrent access.
    27  func (b *BlockChain) DisableCheckpoints(disable bool) {
    28  	b.chainLock.Lock()
    29  	b.noCheckpoints = disable
    30  	b.chainLock.Unlock()
    31  }
    32  
    33  // Checkpoints returns a slice of checkpoints (regardless of whether they are
    34  // already known).  When checkpoints are disabled or there are no checkpoints
    35  // for the active network, it will return nil.
    36  //
    37  // This function is safe for concurrent access.
    38  func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint {
    39  	b.chainLock.RLock()
    40  	defer b.chainLock.RUnlock()
    41  
    42  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    43  		return nil
    44  	}
    45  
    46  	return b.chainParams.Checkpoints
    47  }
    48  
    49  // latestCheckpoint returns the most recent checkpoint (regardless of whether it
    50  // is already known).  When checkpoints are disabled or there are no checkpoints
    51  // for the active network, it will return nil.
    52  //
    53  // This function MUST be called with the chain state lock held (for reads).
    54  func (b *BlockChain) latestCheckpoint() *chaincfg.Checkpoint {
    55  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    56  		return nil
    57  	}
    58  
    59  	checkpoints := b.chainParams.Checkpoints
    60  	return &checkpoints[len(checkpoints)-1]
    61  }
    62  
    63  // LatestCheckpoint returns the most recent checkpoint (regardless of whether it
    64  // is already known).  When checkpoints are disabled or there are no checkpoints
    65  // for the active network, it will return nil.
    66  //
    67  // This function is safe for concurrent access.
    68  func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
    69  	b.chainLock.RLock()
    70  	checkpoint := b.latestCheckpoint()
    71  	b.chainLock.RUnlock()
    72  	return checkpoint
    73  }
    74  
    75  // verifyCheckpoint returns whether the passed block height and hash combination
    76  // match the hard-coded checkpoint data.  It also returns true if there is no
    77  // checkpoint data for the passed block height.
    78  //
    79  // This function MUST be called with the chain lock held (for reads).
    80  func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
    81  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    82  		return true
    83  	}
    84  
    85  	// Nothing to check if there is no checkpoint data for the block height.
    86  	checkpoint, exists := b.checkpointsByHeight[height]
    87  	if !exists {
    88  		return true
    89  	}
    90  
    91  	if !checkpoint.Hash.IsEqual(hash) {
    92  		return false
    93  	}
    94  
    95  	log.Infof("Verified checkpoint at height %d/block %s", checkpoint.Height,
    96  		checkpoint.Hash)
    97  	return true
    98  }
    99  
   100  // findPreviousCheckpoint finds the most recent checkpoint that is already
   101  // available in the downloaded portion of the block chain and returns the
   102  // associated block node.  It returns nil if a checkpoint can't be found (this
   103  // should really only happen for blocks before the first checkpoint).
   104  //
   105  // This function MUST be called with the chain lock held (for reads).
   106  func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
   107  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
   108  		return nil, nil
   109  	}
   110  
   111  	// Perform the initial search to find and cache the latest known
   112  	// checkpoint if the best chain is not known yet or we haven't already
   113  	// previously searched.
   114  	checkpoints := b.chainParams.Checkpoints
   115  	numCheckpoints := len(checkpoints)
   116  	if b.checkpointNode == nil && b.nextCheckpoint == nil {
   117  		// Loop backwards through the available checkpoints to find one
   118  		// that is already available.
   119  		for i := numCheckpoints - 1; i >= 0; i-- {
   120  			node := b.index.LookupNode(checkpoints[i].Hash)
   121  			if node == nil || !b.bestChain.Contains(node) {
   122  				continue
   123  			}
   124  
   125  			// Checkpoint found.  Cache it for future lookups and
   126  			// set the next expected checkpoint accordingly.
   127  			b.checkpointNode = node
   128  			if i < numCheckpoints-1 {
   129  				b.nextCheckpoint = &checkpoints[i+1]
   130  			}
   131  			return b.checkpointNode, nil
   132  		}
   133  
   134  		// No known latest checkpoint.  This will only happen on blocks
   135  		// before the first known checkpoint.  So, set the next expected
   136  		// checkpoint to the first checkpoint and return the fact there
   137  		// is no latest known checkpoint block.
   138  		b.nextCheckpoint = &checkpoints[0]
   139  		return nil, nil
   140  	}
   141  
   142  	// At this point we've already searched for the latest known checkpoint,
   143  	// so when there is no next checkpoint, the current checkpoint lockin
   144  	// will always be the latest known checkpoint.
   145  	if b.nextCheckpoint == nil {
   146  		return b.checkpointNode, nil
   147  	}
   148  
   149  	// When there is a next checkpoint and the height of the current best
   150  	// chain does not exceed it, the current checkpoint lockin is still
   151  	// the latest known checkpoint.
   152  	if b.bestChain.Tip().height < b.nextCheckpoint.Height {
   153  		return b.checkpointNode, nil
   154  	}
   155  
   156  	// We've reached or exceeded the next checkpoint height.  Note that
   157  	// once a checkpoint lockin has been reached, forks are prevented from
   158  	// any blocks before the checkpoint, so we don't have to worry about the
   159  	// checkpoint going away out from under us due to a chain reorganize.
   160  
   161  	// Cache the latest known checkpoint for future lookups.  Note that if
   162  	// this lookup fails something is very wrong since the chain has already
   163  	// passed the checkpoint which was verified as accurate before inserting
   164  	// it.
   165  	checkpointNode := b.index.LookupNode(b.nextCheckpoint.Hash)
   166  	if checkpointNode == nil {
   167  		return nil, AssertError(fmt.Sprintf("findPreviousCheckpoint "+
   168  			"failed lookup of known good block node %s",
   169  			b.nextCheckpoint.Hash))
   170  	}
   171  	b.checkpointNode = checkpointNode
   172  
   173  	// Set the next expected checkpoint.
   174  	checkpointIndex := -1
   175  	for i := numCheckpoints - 1; i >= 0; i-- {
   176  		if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) {
   177  			checkpointIndex = i
   178  			break
   179  		}
   180  	}
   181  	b.nextCheckpoint = nil
   182  	if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 {
   183  		b.nextCheckpoint = &checkpoints[checkpointIndex+1]
   184  	}
   185  
   186  	return b.checkpointNode, nil
   187  }
   188  
   189  // isNonstandardTransaction determines whether a transaction contains any
   190  // scripts which are not one of the standard types.
   191  func isNonstandardTransaction(tx *dcrutil.Tx) bool {
   192  	// Check all of the output public key scripts for non-standard scripts.
   193  	for _, txOut := range tx.MsgTx().TxOut {
   194  		scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
   195  		if scriptClass == txscript.NonStandardTy {
   196  			return true
   197  		}
   198  	}
   199  	return false
   200  }
   201  
   202  // IsCheckpointCandidate returns whether or not the passed block is a good
   203  // checkpoint candidate.
   204  //
   205  // The factors used to determine a good checkpoint are:
   206  //  - The block must be in the main chain
   207  //  - The block must be at least 'CheckpointConfirmations' blocks prior to the
   208  //    current end of the main chain
   209  //  - The timestamps for the blocks before and after the checkpoint must have
   210  //    timestamps which are also before and after the checkpoint, respectively
   211  //    (due to the median time allowance this is not always the case)
   212  //  - The block must not contain any strange transaction such as those with
   213  //    nonstandard scripts
   214  //
   215  // The intent is that candidates are reviewed by a developer to make the final
   216  // decision and then manually added to the list of checkpoints for a network.
   217  //
   218  // This function is safe for concurrent access.
   219  func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
   220  	b.chainLock.RLock()
   221  	defer b.chainLock.RUnlock()
   222  
   223  	// Checkpoints must be enabled.
   224  	if b.noCheckpoints {
   225  		return false, fmt.Errorf("checkpoints are disabled")
   226  	}
   227  
   228  	// A checkpoint must be in the main chain.
   229  	node := b.index.LookupNode(block.Hash())
   230  	if node == nil || !b.bestChain.Contains(node) {
   231  		return false, nil
   232  	}
   233  
   234  	// Ensure the height of the passed block and the entry for the block in
   235  	// the main chain match.  This should always be the case unless the
   236  	// caller provided an invalid block.
   237  	if node.height != block.Height() {
   238  		return false, fmt.Errorf("passed block height of %d does not "+
   239  			"match the main chain height of %d", block.Height(),
   240  			node.height)
   241  	}
   242  
   243  	// A checkpoint must be at least CheckpointConfirmations blocks before
   244  	// the end of the main chain.
   245  	if node.height > (b.bestChain.Tip().height - CheckpointConfirmations) {
   246  		return false, nil
   247  	}
   248  
   249  	// A checkpoint must be have at least one block after it.
   250  	//
   251  	// This should always succeed since the check above already made sure it
   252  	// is CheckpointConfirmations back, but be safe in case the constant
   253  	// changes.
   254  	nextNode := b.bestChain.Next(node)
   255  	if nextNode == nil {
   256  		return false, nil
   257  	}
   258  
   259  	// A checkpoint must be have at least one block before it.
   260  	if node.parent == nil {
   261  		return false, nil
   262  	}
   263  
   264  	// A checkpoint must have timestamps for the block and the blocks on
   265  	// either side of it in order (due to the median time allowance this is
   266  	// not always the case).
   267  	prevTime := time.Unix(node.parent.timestamp, 0)
   268  	curTime := block.MsgBlock().Header.Timestamp
   269  	nextTime := time.Unix(nextNode.timestamp, 0)
   270  	if prevTime.After(curTime) || nextTime.Before(curTime) {
   271  		return false, nil
   272  	}
   273  
   274  	// A checkpoint must have transactions that only contain standard
   275  	// scripts.
   276  	for _, tx := range block.Transactions() {
   277  		if isNonstandardTransaction(tx) {
   278  			return false, nil
   279  		}
   280  	}
   281  
   282  	// All of the checks passed, so the block is a candidate.
   283  	return true, nil
   284  }