From 00d0edd5c47d8d2bb455250442d93365b47f7722 Mon Sep 17 00:00:00 2001 From: eugene Date: Mon, 28 Nov 2022 12:54:47 -0500 Subject: [PATCH 1/2] blockchain: intro HeaderCtx, ChainCtx + refactor CheckBlockHeaderContext This change will allow an external program to provide its own HeaderCtx and ChainCtx and be able to perform contextual block header checks. --- blockchain/blockindex.go | 64 ++++++++++++++++- blockchain/chain.go | 7 +- blockchain/chain_test.go | 4 +- blockchain/chainio.go | 2 +- blockchain/difficulty.go | 73 ++++++++++--------- blockchain/interfaces.go | 55 +++++++++++++++ blockchain/thresholdstate.go | 2 +- blockchain/validate.go | 131 ++++++++++++++++++++++++++++------- 8 files changed, 265 insertions(+), 73 deletions(-) create mode 100644 blockchain/interfaces.go diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index 2ff2fa27c4..aa2db6a755 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -169,6 +169,60 @@ func (node *blockNode) Ancestor(height int32) *blockNode { return n } +// Height returns the blockNode's height in the chain. +// +// NOTE: Part of the HeaderCtx interface. +func (node *blockNode) Height() int32 { + return node.height +} + +// Bits returns the blockNode's nBits. +// +// NOTE: Part of the HeaderCtx interface. +func (node *blockNode) Bits() uint32 { + return node.bits +} + +// Timestamp returns the blockNode's timestamp. +// +// NOTE: Part of the HeaderCtx interface. +func (node *blockNode) Timestamp() int64 { + return node.timestamp +} + +// Parent returns the blockNode's parent. +// +// NOTE: Part of the HeaderCtx interface. +func (node *blockNode) Parent() HeaderCtx { + if node.parent == nil { + // This is required since node.parent is a *blockNode and if we + // do not explicitly return nil here, the caller may fail when + // nil-checking this. + return nil + } + + return node.parent +} + +// RelativeAncestorCtx returns the blockNode's ancestor that is distance blocks +// before it in the chain. This is equivalent to the RelativeAncestor function +// below except that the return type is different. +// +// This function is safe for concurrent access. +// +// NOTE: Part of the HeaderCtx interface. +func (node *blockNode) RelativeAncestorCtx(distance int32) HeaderCtx { + ancestor := node.RelativeAncestor(distance) + if ancestor == nil { + // This is required since RelativeAncestor returns a *blockNode + // and if we do not explicitly return nil here, the caller may + // fail when nil-checking this. + return nil + } + + return ancestor +} + // RelativeAncestor returns the ancestor block node a relative 'distance' blocks // before this node. This is equivalent to calling Ancestor with the node's // height minus provided distance. @@ -182,17 +236,17 @@ func (node *blockNode) RelativeAncestor(distance int32) *blockNode { // prior to, and including, the block node. // // This function is safe for concurrent access. -func (node *blockNode) CalcPastMedianTime() time.Time { +func CalcPastMedianTime(node HeaderCtx) time.Time { // Create a slice of the previous few block timestamps used to calculate // the median per the number defined by the constant medianTimeBlocks. timestamps := make([]int64, medianTimeBlocks) numNodes := 0 iterNode := node for i := 0; i < medianTimeBlocks && iterNode != nil; i++ { - timestamps[i] = iterNode.timestamp + timestamps[i] = iterNode.Timestamp() numNodes++ - iterNode = iterNode.parent + iterNode = iterNode.Parent() } // Prune the slice to the actual number of available timestamps which @@ -217,6 +271,10 @@ func (node *blockNode) CalcPastMedianTime() time.Time { return time.Unix(medianTimestamp, 0) } +// A compile-time assertion to ensure blockNode implements the HeaderCtx +// interface. +var _ HeaderCtx = (*blockNode)(nil) + // blockIndex provides facilities for keeping track of an in-memory index of the // block chain. Although the name block chain suggests a single chain of // blocks, it is actually a tree-shaped structure where any node can have diff --git a/blockchain/chain.go b/blockchain/chain.go index f29455b17a..c013ff3b15 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -437,7 +437,7 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, utxoView prevInputHeight = 0 } blockNode := node.Ancestor(prevInputHeight) - medianTime := blockNode.CalcPastMedianTime() + medianTime := CalcPastMedianTime(blockNode) // Time based relative time-locks as defined by BIP 68 // have a time granularity of RelativeLockSeconds, so @@ -595,7 +595,8 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, blockSize := uint64(block.MsgBlock().SerializeSize()) blockWeight := uint64(GetBlockWeight(block)) state := newBestState(node, blockSize, blockWeight, numTxns, - curTotalTxns+numTxns, node.CalcPastMedianTime()) + curTotalTxns+numTxns, CalcPastMedianTime(node), + ) // Atomically insert info into the database. err = b.db.Update(func(dbTx database.Tx) error { @@ -708,7 +709,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view blockWeight := uint64(GetBlockWeight(prevBlock)) newTotalTxns := curTotalTxns - uint64(len(block.MsgBlock().Transactions)) state := newBestState(prevNode, blockSize, blockWeight, numTxns, - newTotalTxns, prevNode.CalcPastMedianTime()) + newTotalTxns, CalcPastMedianTime(prevNode)) err = b.db.Update(func(dbTx database.Tx) error { // Update best block state. diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 6569b7ec86..8d6ca89174 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -163,13 +163,13 @@ func TestCalcSequenceLock(t *testing.T) { // Obtain the median time past from the PoV of the input created above. // The MTP for the input is the MTP from the PoV of the block *prior* // to the one that included it. - medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix() + medianTime := CalcPastMedianTime(node.RelativeAncestor(5)).Unix() // The median time calculated from the PoV of the best block in the // test chain. For unconfirmed inputs, this value will be used since // the MTP will be calculated from the PoV of the yet-to-be-mined // block. - nextMedianTime := node.CalcPastMedianTime().Unix() + nextMedianTime := CalcPastMedianTime(node).Unix() nextBlockHeight := int32(numBlocksToActivate) + 1 // Add an additional transaction which will serve as our unconfirmed diff --git a/blockchain/chainio.go b/blockchain/chainio.go index c29201c5f8..4914da6859 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1236,7 +1236,7 @@ func (b *BlockChain) initChainState() error { blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block))) numTxns := uint64(len(block.Transactions)) b.stateSnapshot = newBestState(tip, blockSize, blockWeight, - numTxns, state.totalTxns, tip.CalcPastMedianTime()) + numTxns, state.totalTxns, CalcPastMedianTime(tip)) return nil }) diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index 1fdc8999a1..1fa850cc37 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -193,88 +193,87 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) // findPrevTestNetDifficulty returns the difficulty of the previous block which // did not have the special testnet minimum difficulty rule applied. -// -// This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) uint32 { +func findPrevTestNetDifficulty(startNode HeaderCtx, c ChainCtx) uint32 { // Search backwards through the chain for the last block without // the special rule applied. iterNode := startNode - for iterNode != nil && iterNode.height%b.blocksPerRetarget != 0 && - iterNode.bits == b.chainParams.PowLimitBits { + for iterNode != nil && iterNode.Height()%c.BlocksPerRetarget() != 0 && + iterNode.Bits() == c.ChainParams().PowLimitBits { - iterNode = iterNode.parent + iterNode = iterNode.Parent() } // Return the found difficulty or the minimum difficulty if no // appropriate block was found. - lastBits := b.chainParams.PowLimitBits + lastBits := c.ChainParams().PowLimitBits if iterNode != nil { - lastBits = iterNode.bits + lastBits = iterNode.Bits() } return lastBits } // calcNextRequiredDifficulty calculates the required difficulty for the block -// after the passed previous block node based on the difficulty retarget rules. +// after the passed previous HeaderCtx based on the difficulty retarget rules. // This function differs from the exported CalcNextRequiredDifficulty in that -// the exported version uses the current best chain as the previous block node -// while this function accepts any block node. -func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, - newBlockTime time.Time) (uint32, error) { +// the exported version uses the current best chain as the previous HeaderCtx +// while this function accepts any block node. This function accepts a ChainCtx +// parameter that gives the necessary difficulty context variables. +func calcNextRequiredDifficulty(lastNode HeaderCtx, newBlockTime time.Time, + c ChainCtx) (uint32, error) { // Emulate the same behavior as Bitcoin Core that for regtest there is // no difficulty retargeting. - if b.chainParams.PoWNoRetargeting { - return b.chainParams.PowLimitBits, nil + if c.ChainParams().PoWNoRetargeting { + return c.ChainParams().PowLimitBits, nil } // Genesis block. if lastNode == nil { - return b.chainParams.PowLimitBits, nil + return c.ChainParams().PowLimitBits, nil } // Return the previous block's difficulty requirements if this block // is not at a difficulty retarget interval. - if (lastNode.height+1)%b.blocksPerRetarget != 0 { + if (lastNode.Height()+1)%c.BlocksPerRetarget() != 0 { // For networks that support it, allow special reduction of the // required difficulty once too much time has elapsed without // mining a block. - if b.chainParams.ReduceMinDifficulty { + if c.ChainParams().ReduceMinDifficulty { // Return minimum difficulty when more than the desired // amount of time has elapsed without mining a block. - reductionTime := int64(b.chainParams.MinDiffReductionTime / + reductionTime := int64(c.ChainParams().MinDiffReductionTime / time.Second) - allowMinTime := lastNode.timestamp + reductionTime + allowMinTime := lastNode.Timestamp() + reductionTime if newBlockTime.Unix() > allowMinTime { - return b.chainParams.PowLimitBits, nil + return c.ChainParams().PowLimitBits, nil } // The block was mined within the desired timeframe, so // return the difficulty for the last block which did // not have the special minimum difficulty rule applied. - return b.findPrevTestNetDifficulty(lastNode), nil + return findPrevTestNetDifficulty(lastNode, c), nil } // For the main network (or any unrecognized networks), simply // return the previous block's difficulty requirements. - return lastNode.bits, nil + return lastNode.Bits(), nil } // Get the block node at the previous retarget (targetTimespan days // worth of blocks). - firstNode := lastNode.RelativeAncestor(b.blocksPerRetarget - 1) + firstNode := lastNode.RelativeAncestorCtx(c.BlocksPerRetarget() - 1) if firstNode == nil { return 0, AssertError("unable to obtain previous retarget block") } // Limit the amount of adjustment that can occur to the previous // difficulty. - actualTimespan := lastNode.timestamp - firstNode.timestamp + actualTimespan := lastNode.Timestamp() - firstNode.Timestamp() adjustedTimespan := actualTimespan - if actualTimespan < b.minRetargetTimespan { - adjustedTimespan = b.minRetargetTimespan - } else if actualTimespan > b.maxRetargetTimespan { - adjustedTimespan = b.maxRetargetTimespan + if actualTimespan < c.MinRetargetTimespan() { + adjustedTimespan = c.MinRetargetTimespan() + } else if actualTimespan > c.MaxRetargetTimespan() { + adjustedTimespan = c.MaxRetargetTimespan() } // Calculate new target difficulty as: @@ -282,14 +281,14 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, // The result uses integer division which means it will be slightly // rounded down. Bitcoind also uses integer division to calculate this // result. - oldTarget := CompactToBig(lastNode.bits) + oldTarget := CompactToBig(lastNode.Bits()) newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan)) - targetTimeSpan := int64(b.chainParams.TargetTimespan / time.Second) + targetTimeSpan := int64(c.ChainParams().TargetTimespan / time.Second) newTarget.Div(newTarget, big.NewInt(targetTimeSpan)) // Limit new value to the proof of work limit. - if newTarget.Cmp(b.chainParams.PowLimit) > 0 { - newTarget.Set(b.chainParams.PowLimit) + if newTarget.Cmp(c.ChainParams().PowLimit) > 0 { + newTarget.Set(c.ChainParams().PowLimit) } // Log new target difficulty and return it. The new target logging is @@ -297,13 +296,13 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, // newTarget since conversion to the compact representation loses // precision. newTargetBits := BigToCompact(newTarget) - log.Debugf("Difficulty retarget at block height %d", lastNode.height+1) - log.Debugf("Old target %08x (%064x)", lastNode.bits, oldTarget) + log.Debugf("Difficulty retarget at block height %d", lastNode.Height()+1) + log.Debugf("Old target %08x (%064x)", lastNode.Bits(), oldTarget) log.Debugf("New target %08x (%064x)", newTargetBits, CompactToBig(newTargetBits)) log.Debugf("Actual timespan %v, adjusted timespan %v, target timespan %v", time.Duration(actualTimespan)*time.Second, time.Duration(adjustedTimespan)*time.Second, - b.chainParams.TargetTimespan) + c.ChainParams().TargetTimespan) return newTargetBits, nil } @@ -315,7 +314,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, // This function is safe for concurrent access. func (b *BlockChain) CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) { b.chainLock.Lock() - difficulty, err := b.calcNextRequiredDifficulty(b.bestChain.Tip(), timestamp) + difficulty, err := calcNextRequiredDifficulty(b.bestChain.Tip(), timestamp, b) b.chainLock.Unlock() return difficulty, err } diff --git a/blockchain/interfaces.go b/blockchain/interfaces.go new file mode 100644 index 0000000000..cae9b3b9f0 --- /dev/null +++ b/blockchain/interfaces.go @@ -0,0 +1,55 @@ +package blockchain + +import ( + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +// ChainCtx is an interface that abstracts away blockchain parameters. +type ChainCtx interface { + // ChainParams returns the chain's configured chaincfg.Params. + ChainParams() *chaincfg.Params + + // BlocksPerRetarget returns the number of blocks before retargeting + // occurs. + BlocksPerRetarget() int32 + + // MinRetargetTimespan returns the minimum amount of time to use in the + // difficulty calculation. + MinRetargetTimespan() int64 + + // MaxRetargetTimespan returns the maximum amount of time to use in the + // difficulty calculation. + MaxRetargetTimespan() int64 + + // VerifyCheckpoint returns whether the passed height and hash match + // the checkpoint data. Not all instances of VerifyCheckpoint will use + // this function for validation. + VerifyCheckpoint(height int32, hash *chainhash.Hash) bool + + // FindPreviousCheckpoint returns the most recent checkpoint that we + // have validated. Not all instances of FindPreviousCheckpoint will use + // this function for validation. + FindPreviousCheckpoint() (HeaderCtx, error) +} + +// HeaderCtx is an interface that describes information about a block. This is +// used so that external libraries can provide their own context (the header's +// parent, bits, etc.) when attempting to contextually validate a header. +type HeaderCtx interface { + // Height returns the header's height. + Height() int32 + + // Bits returns the header's bits. + Bits() uint32 + + // Timestamp returns the header's timestamp. + Timestamp() int64 + + // Parent returns the header's parent. + Parent() HeaderCtx + + // RelativeAncestorCtx returns the header's ancestor that is distance + // blocks before it in the chain. + RelativeAncestorCtx(distance int32) HeaderCtx +} diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index 35653bf8fc..d62c2de3c2 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -153,7 +153,7 @@ func (b *BlockChain) PastMedianTime(blockHeader *wire.BlockHeader) (time.Time, e blockNode := newBlockNode(blockHeader, prevNode) - return blockNode.CalcPastMedianTime(), nil + return CalcPastMedianTime(blockNode), nil } // thresholdStateTransition given a state, a previous node, and a toeholds diff --git a/blockchain/validate.go b/blockchain/validate.go index dc7f0abe8e..662eb41b72 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -633,22 +633,30 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error { return nil } -// checkBlockHeaderContext performs several validation checks on the block header +// CheckBlockHeaderContext performs several validation checks on the block header // which depend on its position within the block chain. // // The flags modify the behavior of this function as follows: // - BFFastAdd: All checks except those involving comparing the header against // the checkpoints are not performed. // +// The skipCheckpoint boolean is used so that libraries can skip the checkpoint +// sanity checks. +// // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error { +// NOTE: Ignore the above lock requirement if this function is not passed a +// *Blockchain instance as the ChainCtx argument. +func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx, + flags BehaviorFlags, c ChainCtx, skipCheckpoint bool) error { + fastAdd := flags&BFFastAdd == BFFastAdd if !fastAdd { // Ensure the difficulty specified in the block header matches // the calculated difficulty based on the previous block and // difficulty retarget rules. - expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, - header.Timestamp) + expectedDifficulty, err := calcNextRequiredDifficulty( + prevNode, header.Timestamp, c, + ) if err != nil { return err } @@ -661,7 +669,7 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // Ensure the timestamp for the block header is after the // median time of the last several blocks (medianTimeBlocks). - medianTime := prevNode.CalcPastMedianTime() + medianTime := CalcPastMedianTime(prevNode) if !header.Timestamp.After(medianTime) { str := "block timestamp of %v is not after expected %v" str = fmt.Sprintf(str, header.Timestamp, medianTime) @@ -671,11 +679,30 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // The height of this block is one more than the referenced previous // block. - blockHeight := prevNode.height + 1 + blockHeight := prevNode.Height() + 1 + + // Reject outdated block versions once a majority of the network + // has upgraded. These were originally voted on by BIP0034, + // BIP0065, and BIP0066. + params := c.ChainParams() + if header.Version < 2 && blockHeight >= params.BIP0034Height || + header.Version < 3 && blockHeight >= params.BIP0066Height || + header.Version < 4 && blockHeight >= params.BIP0065Height { + + str := "new blocks with version %d are no longer valid" + str = fmt.Sprintf(str, header.Version) + return ruleError(ErrBlockVersionTooOld, str) + } + + if skipCheckpoint { + // If the caller wants us to skip the checkpoint checks, we'll + // return early. + return nil + } // Ensure chain matches up to predetermined checkpoints. blockHash := header.BlockHash() - if !b.verifyCheckpoint(blockHeight, &blockHash) { + if !c.VerifyCheckpoint(blockHeight, &blockHash) { str := fmt.Sprintf("block at height %d does not match "+ "checkpoint hash", blockHeight) return ruleError(ErrBadCheckpoint, str) @@ -685,30 +712,17 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // chain before it. This prevents storage of new, otherwise valid, // blocks which build off of old blocks that are likely at a much easier // difficulty and therefore could be used to waste cache and disk space. - checkpointNode, err := b.findPreviousCheckpoint() + checkpointNode, err := c.FindPreviousCheckpoint() if err != nil { return err } - if checkpointNode != nil && blockHeight < checkpointNode.height { + if checkpointNode != nil && blockHeight < checkpointNode.Height() { str := fmt.Sprintf("block at height %d forks the main chain "+ "before the previous checkpoint at height %d", - blockHeight, checkpointNode.height) + blockHeight, checkpointNode.Height()) return ruleError(ErrForkTooOld, str) } - // Reject outdated block versions once a majority of the network - // has upgraded. These were originally voted on by BIP0034, - // BIP0065, and BIP0066. - params := b.chainParams - if header.Version < 2 && blockHeight >= params.BIP0034Height || - header.Version < 3 && blockHeight >= params.BIP0066Height || - header.Version < 4 && blockHeight >= params.BIP0065Height { - - str := "new blocks with version %d are no longer valid" - str = fmt.Sprintf(str, header.Version) - return ruleError(ErrBlockVersionTooOld, str) - } - return nil } @@ -726,7 +740,7 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode, flags BehaviorFlags) error { // Perform all block header related validation checks. header := &block.MsgBlock().Header - err := b.checkBlockHeaderContext(header, prevNode, flags) + err := CheckBlockHeaderContext(header, prevNode, flags, b, false) if err != nil { return err } @@ -746,7 +760,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode // timestamps for all lock-time based checks. blockTime := header.Timestamp if csvState == ThresholdActive { - blockTime = prevNode.CalcPastMedianTime() + blockTime = CalcPastMedianTime(prevNode) } // The height of this block is one more than the referenced @@ -1186,7 +1200,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi // We obtain the MTP of the *previous* block in order to // determine if transactions in the current block are final. - medianTime := node.parent.CalcPastMedianTime() + medianTime := CalcPastMedianTime(node.parent) // Additionally, if the CSV soft-fork package is now active, // then we also enforce the relative sequence number based @@ -1288,3 +1302,68 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error { newNode := newBlockNode(&header, tip) return b.checkConnectBlock(newNode, block, view, nil) } + +// ChainParams returns the Blockchain's configured chaincfg.Params. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) ChainParams() *chaincfg.Params { + return b.chainParams +} + +// BlocksPerRetarget returns the number of blocks before retargeting occurs. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) BlocksPerRetarget() int32 { + return b.blocksPerRetarget +} + +// MinRetargetTimespan returns the minimum amount of time to use in the +// difficulty calculation. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) MinRetargetTimespan() int64 { + return b.minRetargetTimespan +} + +// MaxRetargetTimespan returns the maximum amount of time to use in the +// difficulty calculation. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) MaxRetargetTimespan() int64 { + return b.maxRetargetTimespan +} + +// VerifyCheckpoint checks that the height and hash match the stored +// checkpoints. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) VerifyCheckpoint(height int32, + hash *chainhash.Hash) bool { + + return b.verifyCheckpoint(height, hash) +} + +// FindPreviousCheckpoint finds the checkpoint we've encountered during +// validation. +// +// NOTE: Part of the ChainCtx interface. +func (b *BlockChain) FindPreviousCheckpoint() (HeaderCtx, error) { + checkpoint, err := b.findPreviousCheckpoint() + if err != nil { + return nil, err + } + + if checkpoint == nil { + // This check is necessary because if we just return the nil + // blockNode as a HeaderCtx, a caller performing a nil-check + // will fail. This is a quirk of go where a nil value stored in + // an interface is different from the actual nil interface. + return nil, nil + } + + return checkpoint, err +} + +// A compile-time assertion to ensure BlockChain implements the ChainCtx +// interface. +var _ ChainCtx = (*BlockChain)(nil) From ee6c0e19626f221ab6fd516c72c3f30421d62001 Mon Sep 17 00:00:00 2001 From: eugene Date: Mon, 28 Nov 2022 12:57:33 -0500 Subject: [PATCH 2/2] blockchain: export CheckBlockHeaderSanity as a library function --- blockchain/validate.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/blockchain/validate.go b/blockchain/validate.go index 662eb41b72..e686175aad 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -421,13 +421,15 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint) return totalSigOps, nil } -// checkBlockHeaderSanity performs some preliminary checks on a block header to +// CheckBlockHeaderSanity performs some preliminary checks on a block header to // ensure it is sane before continuing with processing. These checks are // context free. // // The flags do not modify the behavior of this function directly, however they // are needed to pass along to checkProofOfWork. -func checkBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error { +func CheckBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, + timeSource MedianTimeSource, flags BehaviorFlags) error { + // Ensure the proof of work bits in the block header is in min/max range // and the block hash is less than the target value described by the // bits. @@ -467,7 +469,7 @@ func checkBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, timeSou func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error { msgBlock := block.MsgBlock() header := &msgBlock.Header - err := checkBlockHeaderSanity(header, powLimit, timeSource, flags) + err := CheckBlockHeaderSanity(header, powLimit, timeSource, flags) if err != nil { return err }