github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/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  
    10  	"github.com/mit-dci/lit/btcutil"
    11  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
    12  	"github.com/mit-dci/lit/btcutil/database"
    13  	"github.com/mit-dci/lit/btcutil/txscript"
    14  	"github.com/mit-dci/lit/coinparam"
    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  // DisableCheckpoints provides a mechanism to disable validation against
    31  // checkpoints which you DO NOT want to do in production.  It is provided only
    32  // for debug purposes.
    33  //
    34  // This function is safe for concurrent access.
    35  func (b *BlockChain) DisableCheckpoints(disable bool) {
    36  	b.chainLock.Lock()
    37  	b.noCheckpoints = disable
    38  	b.chainLock.Unlock()
    39  }
    40  
    41  // Checkpoints returns a slice of checkpoints (regardless of whether they are
    42  // already known).  When checkpoints are disabled or there are no checkpoints
    43  // for the active network, it will return nil.
    44  //
    45  // This function is safe for concurrent access.
    46  func (b *BlockChain) Checkpoints() []coinparam.Checkpoint {
    47  	b.chainLock.RLock()
    48  	defer b.chainLock.RUnlock()
    49  
    50  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    51  		return nil
    52  	}
    53  
    54  	return b.chainParams.Checkpoints
    55  }
    56  
    57  // latestCheckpoint returns the most recent checkpoint (regardless of whether it
    58  // is already known).  When checkpoints are disabled or there are no checkpoints
    59  // for the active network, it will return nil.
    60  //
    61  // This function MUST be called with the chain state lock held (for reads).
    62  func (b *BlockChain) latestCheckpoint() *coinparam.Checkpoint {
    63  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    64  		return nil
    65  	}
    66  
    67  	checkpoints := b.chainParams.Checkpoints
    68  	return &checkpoints[len(checkpoints)-1]
    69  }
    70  
    71  // LatestCheckpoint returns the most recent checkpoint (regardless of whether it
    72  // is already known).  When checkpoints are disabled or there are no checkpoints
    73  // for the active network, it will return nil.
    74  //
    75  // This function is safe for concurrent access.
    76  func (b *BlockChain) LatestCheckpoint() *coinparam.Checkpoint {
    77  	b.chainLock.RLock()
    78  	checkpoint := b.latestCheckpoint()
    79  	b.chainLock.RUnlock()
    80  	return checkpoint
    81  }
    82  
    83  // verifyCheckpoint returns whether the passed block height and hash combination
    84  // match the hard-coded checkpoint data.  It also returns true if there is no
    85  // checkpoint data for the passed block height.
    86  //
    87  // This function MUST be called with the chain lock held (for reads).
    88  func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
    89  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
    90  		return true
    91  	}
    92  
    93  	// Nothing to check if there is no checkpoint data for the block height.
    94  	checkpoint, exists := b.checkpointsByHeight[height]
    95  	if !exists {
    96  		return true
    97  	}
    98  
    99  	if !checkpoint.Hash.IsEqual(hash) {
   100  		return false
   101  	}
   102  
   103  	return true
   104  }
   105  
   106  // findPreviousCheckpoint finds the most recent checkpoint that is already
   107  // available in the downloaded portion of the block chain and returns the
   108  // associated block.  It returns nil if a checkpoint can't be found (this should
   109  // really only happen for blocks before the first checkpoint).
   110  //
   111  // This function MUST be called with the chain lock held (for reads).
   112  func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) {
   113  	if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
   114  		return nil, nil
   115  	}
   116  
   117  	// No checkpoints.
   118  	checkpoints := b.chainParams.Checkpoints
   119  	numCheckpoints := len(checkpoints)
   120  	if numCheckpoints == 0 {
   121  		return nil, nil
   122  	}
   123  
   124  	// Perform the initial search to find and cache the latest known
   125  	// checkpoint if the best chain is not known yet or we haven't already
   126  	// previously searched.
   127  	if b.checkpointBlock == nil && b.nextCheckpoint == nil {
   128  		// Loop backwards through the available checkpoints to find one
   129  		// that is already available.
   130  		checkpointIndex := -1
   131  		err := b.db.View(func(dbTx database.Tx) error {
   132  			for i := numCheckpoints - 1; i >= 0; i-- {
   133  				if dbMainChainHasBlock(dbTx, checkpoints[i].Hash) {
   134  					checkpointIndex = i
   135  					break
   136  				}
   137  			}
   138  			return nil
   139  		})
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  
   144  		// No known latest checkpoint.  This will only happen on blocks
   145  		// before the first known checkpoint.  So, set the next expected
   146  		// checkpoint to the first checkpoint and return the fact there
   147  		// is no latest known checkpoint block.
   148  		if checkpointIndex == -1 {
   149  			b.nextCheckpoint = &checkpoints[0]
   150  			return nil, nil
   151  		}
   152  
   153  		// Cache the latest known checkpoint block for future lookups.
   154  		checkpoint := checkpoints[checkpointIndex]
   155  		err = b.db.View(func(dbTx database.Tx) error {
   156  			block, err := dbFetchBlockByHash(dbTx, checkpoint.Hash)
   157  			if err != nil {
   158  				return err
   159  			}
   160  			b.checkpointBlock = block
   161  
   162  			// Set the next expected checkpoint block accordingly.
   163  			b.nextCheckpoint = nil
   164  			if checkpointIndex < numCheckpoints-1 {
   165  				b.nextCheckpoint = &checkpoints[checkpointIndex+1]
   166  			}
   167  
   168  			return nil
   169  		})
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  
   174  		return b.checkpointBlock, nil
   175  	}
   176  
   177  	// At this point we've already searched for the latest known checkpoint,
   178  	// so when there is no next checkpoint, the current checkpoint lockin
   179  	// will always be the latest known checkpoint.
   180  	if b.nextCheckpoint == nil {
   181  		return b.checkpointBlock, nil
   182  	}
   183  
   184  	// When there is a next checkpoint and the height of the current best
   185  	// chain does not exceed it, the current checkpoint lockin is still
   186  	// the latest known checkpoint.
   187  	if b.bestNode.height < b.nextCheckpoint.Height {
   188  		return b.checkpointBlock, nil
   189  	}
   190  
   191  	// We've reached or exceeded the next checkpoint height.  Note that
   192  	// once a checkpoint lockin has been reached, forks are prevented from
   193  	// any blocks before the checkpoint, so we don't have to worry about the
   194  	// checkpoint going away out from under us due to a chain reorganize.
   195  
   196  	// Cache the latest known checkpoint block for future lookups.  Note
   197  	// that if this lookup fails something is very wrong since the chain
   198  	// has already passed the checkpoint which was verified as accurate
   199  	// before inserting it.
   200  	err := b.db.View(func(tx database.Tx) error {
   201  		block, err := dbFetchBlockByHash(tx, b.nextCheckpoint.Hash)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		b.checkpointBlock = block
   206  		return nil
   207  	})
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	// Set the next expected checkpoint.
   213  	checkpointIndex := -1
   214  	for i := numCheckpoints - 1; i >= 0; i-- {
   215  		if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) {
   216  			checkpointIndex = i
   217  			break
   218  		}
   219  	}
   220  	b.nextCheckpoint = nil
   221  	if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 {
   222  		b.nextCheckpoint = &checkpoints[checkpointIndex+1]
   223  	}
   224  
   225  	return b.checkpointBlock, nil
   226  }
   227  
   228  // isNonstandardTransaction determines whether a transaction contains any
   229  // scripts which are not one of the standard types.
   230  func isNonstandardTransaction(tx *btcutil.Tx) bool {
   231  	// Check all of the output public key scripts for non-standard scripts.
   232  	for _, txOut := range tx.MsgTx().TxOut {
   233  		scriptClass := txscript.GetScriptClass(txOut.PkScript)
   234  		if scriptClass == txscript.NonStandardTy {
   235  			return true
   236  		}
   237  	}
   238  	return false
   239  }
   240  
   241  // IsCheckpointCandidate returns whether or not the passed block is a good
   242  // checkpoint candidate.
   243  //
   244  // The factors used to determine a good checkpoint are:
   245  //  - The block must be in the main chain
   246  //  - The block must be at least 'CheckpointConfirmations' blocks prior to the
   247  //    current end of the main chain
   248  //  - The timestamps for the blocks before and after the checkpoint must have
   249  //    timestamps which are also before and after the checkpoint, respectively
   250  //    (due to the median time allowance this is not always the case)
   251  //  - The block must not contain any strange transaction such as those with
   252  //    nonstandard scripts
   253  //
   254  // The intent is that candidates are reviewed by a developer to make the final
   255  // decision and then manually added to the list of checkpoints for a network.
   256  //
   257  // This function is safe for concurrent access.
   258  func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) {
   259  	b.chainLock.RLock()
   260  	defer b.chainLock.RUnlock()
   261  
   262  	// Checkpoints must be enabled.
   263  	if b.noCheckpoints {
   264  		return false, fmt.Errorf("checkpoints are disabled")
   265  	}
   266  
   267  	var isCandidate bool
   268  	err := b.db.View(func(dbTx database.Tx) error {
   269  		// A checkpoint must be in the main chain.
   270  		blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash())
   271  		if err != nil {
   272  			// Only return an error if it's not due to the block not
   273  			// being in the main chain.
   274  			if !isNotInMainChainErr(err) {
   275  				return err
   276  			}
   277  			return nil
   278  		}
   279  
   280  		// Ensure the height of the passed block and the entry for the
   281  		// block in the main chain match.  This should always be the
   282  		// case unless the caller provided an invalid block.
   283  		if blockHeight != block.Height() {
   284  			return fmt.Errorf("passed block height of %d does not "+
   285  				"match the main chain height of %d",
   286  				block.Height(), blockHeight)
   287  		}
   288  
   289  		// A checkpoint must be at least CheckpointConfirmations blocks
   290  		// before the end of the main chain.
   291  		mainChainHeight := b.bestNode.height
   292  		if blockHeight > (mainChainHeight - CheckpointConfirmations) {
   293  			return nil
   294  		}
   295  
   296  		// Get the previous block header.
   297  		prevHash := &block.MsgBlock().Header.PrevBlock
   298  		prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash)
   299  		if err != nil {
   300  			return err
   301  		}
   302  
   303  		// Get the next block header.
   304  		nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1)
   305  		if err != nil {
   306  			return err
   307  		}
   308  
   309  		// A checkpoint must have timestamps for the block and the
   310  		// blocks on either side of it in order (due to the median time
   311  		// allowance this is not always the case).
   312  		prevTime := prevHeader.Timestamp
   313  		curTime := block.MsgBlock().Header.Timestamp
   314  		nextTime := nextHeader.Timestamp
   315  		if prevTime.After(curTime) || nextTime.Before(curTime) {
   316  			return nil
   317  		}
   318  
   319  		// A checkpoint must have transactions that only contain
   320  		// standard scripts.
   321  		for _, tx := range block.Transactions() {
   322  			if isNonstandardTransaction(tx) {
   323  				return nil
   324  			}
   325  		}
   326  
   327  		// All of the checks passed, so the block is a candidate.
   328  		isCandidate = true
   329  		return nil
   330  	})
   331  	return isCandidate, err
   332  }