dcrd/blockchain/checkpoints.go
Dave Collins 175d680afc
blockchain: Convert to use stdscript.
This converts the blockchain package to use the stdscript package
instead of txscript.

This is part of a series of commits to convert all packages in the
repository to make use of the new stdscript package.
2021-11-18 12:53:27 -06:00

186 lines
6.1 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"time"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/txscript/v4/stdscript"
)
// CheckpointConfirmations is the number of blocks before the end of the current
// best block chain that a good checkpoint candidate must be.
const CheckpointConfirmations = 4096
// LatestCheckpoint returns the most recent checkpoint (regardless of whether it
// is already known). When checkpoints are disabled or there are no checkpoints
// for the active network, it will return nil.
//
// This function is safe for concurrent access.
func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
return b.latestCheckpoint
}
// verifyCheckpoint returns whether the passed block height and hash combination
// match the hard-coded checkpoint data. It also returns true if there is no
// checkpoint data for the passed block height.
//
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
if b.latestCheckpoint == nil {
return true
}
// Nothing to check if there is no checkpoint data for the block height.
if b.latestCheckpoint.Height != height {
return true
}
if *b.latestCheckpoint.Hash != *hash {
return false
}
log.Debugf("Verified checkpoint at height %d/block %s",
b.latestCheckpoint.Height, b.latestCheckpoint.Hash)
return true
}
// maybeUpdateMostRecentCheckpoint potentially updates the most recently known
// checkpoint to the provided block node.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeUpdateMostRecentCheckpoint(node *blockNode) {
if b.latestCheckpoint == nil {
return
}
// Nothing to update if there is no checkpoint data for the block height or
// the checkpoint hash does not match.
if node.height != b.latestCheckpoint.Height ||
node.hash != *b.latestCheckpoint.Hash {
return
}
// Update the previous checkpoint to current block so long as it is more
// recent than the existing previous checkpoint.
if b.checkpointNode == nil ||
b.checkpointNode.height < b.latestCheckpoint.Height {
log.Debugf("Most recent checkpoint updated to %s (height %d)",
node.hash, node.height)
b.checkpointNode = node
}
}
// isKnownCheckpointAncestor determines whether the provided node is an ancestor
// of the most recently-known checkpoint. False is returned when no checkpoint
// is known or checkpoints are disabled.
//
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) isKnownCheckpointAncestor(node *blockNode) bool {
if b.checkpointNode == nil {
return false
}
return node.IsAncestorOf(b.checkpointNode)
}
// isNonstandardTransaction determines whether a transaction contains any
// scripts which are not one of the standard types.
func isNonstandardTransaction(tx *dcrutil.Tx) bool {
// Check all of the output public key scripts for non-standard scripts.
for _, txOut := range tx.MsgTx().TxOut {
typ := stdscript.DetermineScriptType(txOut.Version, txOut.PkScript)
if typ == stdscript.STNonStandard {
return true
}
}
return false
}
// 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.
func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
b.chainLock.RLock()
defer b.chainLock.RUnlock()
// A checkpoint must be in the main chain.
node := b.index.LookupNode(block.Hash())
if node == nil || !b.bestChain.Contains(node) {
return false, 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 node.height != block.Height() {
return false, fmt.Errorf("passed block height of %d does not "+
"match the main chain height of %d", block.Height(),
node.height)
}
// A checkpoint must be at least CheckpointConfirmations blocks before
// the end of the main chain.
if node.height > (b.bestChain.Tip().height - CheckpointConfirmations) {
return false, nil
}
// A checkpoint must be have at least one block after it.
//
// This should always succeed since the check above already made sure it
// is CheckpointConfirmations back, but be safe in case the constant
// changes.
nextNode := b.bestChain.Next(node)
if nextNode == nil {
return false, nil
}
// A checkpoint must be have at least one block before it.
if node.parent == nil {
return false, nil
}
// 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 := time.Unix(node.parent.timestamp, 0)
curTime := block.MsgBlock().Header.Timestamp
nextTime := time.Unix(nextNode.timestamp, 0)
if prevTime.After(curTime) || nextTime.Before(curTime) {
return false, nil
}
// A checkpoint must have transactions that only contain standard
// scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return false, nil
}
}
// All of the checks passed, so the block is a candidate.
return true, nil
}