checkpoints.go raw

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