github.com/palcoin-project/palcd@v1.0.0/blockchain/checkpoints.go (about)

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