View definition


Defined in


IsCheckpointCandidate returns whether or not the passed block is a good checkpoint candidate.

The factors used to determine a good checkpoint are:

- The block must be in the main chain
- The block must be at least 'CheckpointConfirmations' blocks prior to the
  current end of the main chain
- The timestamps for the blocks before and after the checkpoint must have
  timestamps which are also before and after the checkpoint, respectively
  (due to the median time allowance this is not always the case)
- The block must not contain any strange transaction such as those with
  nonstandard scripts

The intent is that candidates are reviewed by a developer to make the final decision and then manually added to the list of checkpoints for a network.

This function is safe for concurrent access.

IsCheckpointCandidate is referenced in 2 repositories



func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) {
	defer b.chainLock.RUnlock()

	// Checkpoints must be enabled.
	if b.noCheckpoints {
		return false, fmt.Errorf("checkpoints are disabled")

	var isCandidate bool
	err := b.db.View(func(dbTx database.Tx) error {
		// A checkpoint must be in the main chain.
		blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash())
		if err != nil {
			// Only return an error if it's not due to the block not
			// being in the main chain.
			if !isNotInMainChainErr(err) {
				return err
			return nil

		// Ensure the height of the passed block and the entry for the
		// block in the main chain match.  This should always be the
		// case unless the caller provided an invalid block.
		if blockHeight != block.Height() {
			return fmt.Errorf("passed block height of %d does not "+
				"match the main chain height of %d",
				block.Height(), blockHeight)

		// A checkpoint must be at least CheckpointConfirmations blocks
		// before the end of the main chain.
		mainChainHeight := b.bestNode.height
		if blockHeight > (mainChainHeight - CheckpointConfirmations) {
			return nil

		// Get the previous block header.
		prevHash := &block.MsgBlock().Header.PrevBlock
		prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash)
		if err != nil {
			return err

		// Get the next block header.
		nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1)
		if err != nil {
			return err

		// A checkpoint must have timestamps for the block and the
		// blocks on either side of it in order (due to the median time
		// allowance this is not always the case).
		prevTime := prevHeader.Timestamp
		curTime := block.MsgBlock().Header.Timestamp
		nextTime := nextHeader.Timestamp
		if prevTime.After(curTime) || nextTime.Before(curTime) {
			return nil

		// A checkpoint must have transactions that only contain
		// standard scripts.
		for _, tx := range block.Transactions() {
			if isNonstandardTransaction(tx) {
				return nil

		// All of the checks passed, so the block is a candidate.
		isCandidate = true
		return nil
	return isCandidate, err