blockchain: Optimize checkpoint handling.

This modifies the code that determines the most recently known
checkpoint to take advantage of recent changes which make the entire
block index available in memory by only storing a reference to the
specific node in the index that represents the latest known checkpoint.

Previously, the entire block was stored and new checkpoints required
loading it from the database.
This commit is contained in:
Dave Collins 2017-02-03 21:18:39 -06:00
parent 349450379f
commit 72f2a3fe49
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
4 changed files with 43 additions and 76 deletions

View file

@ -124,7 +124,7 @@ type BlockChain struct {
// These fields are related to checkpoint handling. They are protected // These fields are related to checkpoint handling. They are protected
// by the chain lock. // by the chain lock.
nextCheckpoint *chaincfg.Checkpoint nextCheckpoint *chaincfg.Checkpoint
checkpointBlock *btcutil.Block checkpointNode *blockNode
// The state is used as a fairly efficient way to cache information // The state is used as a fairly efficient way to cache information
// about the current best chain state that is returned to callers when // about the current best chain state that is returned to callers when

View file

@ -80,87 +80,58 @@ func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
// findPreviousCheckpoint finds the most recent checkpoint that is already // findPreviousCheckpoint finds the most recent checkpoint that is already
// available in the downloaded portion of the block chain and returns the // available in the downloaded portion of the block chain and returns the
// associated block. It returns nil if a checkpoint can't be found (this should // associated block node. It returns nil if a checkpoint can't be found (this
// really only happen for blocks before the first checkpoint). // should really only happen for blocks before the first checkpoint).
// //
// This function MUST be called with the chain lock held (for reads). // This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) { func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
if !b.HasCheckpoints() { if !b.HasCheckpoints() {
return nil, nil return nil, nil
} }
checkpoints := b.checkpoints
numCheckpoints := len(checkpoints)
if numCheckpoints == 0 {
// No checkpoints.
return nil, nil
}
// Perform the initial search to find and cache the latest known // Perform the initial search to find and cache the latest known
// checkpoint if the best chain is not known yet or we haven't already // checkpoint if the best chain is not known yet or we haven't already
// previously searched. // previously searched.
if b.checkpointBlock == nil && b.nextCheckpoint == nil { checkpoints := b.checkpoints
numCheckpoints := len(checkpoints)
if b.checkpointNode == nil && b.nextCheckpoint == nil {
// Loop backwards through the available checkpoints to find one // Loop backwards through the available checkpoints to find one
// that is already available. // that is already available.
checkpointIndex := -1
err := b.db.View(func(dbTx database.Tx) error {
for i := numCheckpoints - 1; i >= 0; i-- { for i := numCheckpoints - 1; i >= 0; i-- {
if dbMainChainHasBlock(dbTx, checkpoints[i].Hash) { node := b.index.LookupNode(checkpoints[i].Hash)
checkpointIndex = i if node == nil || !node.inMainChain {
break continue
} }
// Checkpoint found. Cache it for future lookups and
// set the next expected checkpoint accordingly.
b.checkpointNode = node
if i < numCheckpoints-1 {
b.nextCheckpoint = &checkpoints[i+1]
} }
return nil return b.checkpointNode, nil
})
if err != nil {
return nil, err
} }
// No known latest checkpoint. This will only happen on blocks // No known latest checkpoint. This will only happen on blocks
// before the first known checkpoint. So, set the next expected // before the first known checkpoint. So, set the next expected
// checkpoint to the first checkpoint and return the fact there // checkpoint to the first checkpoint and return the fact there
// is no latest known checkpoint block. // is no latest known checkpoint block.
if checkpointIndex == -1 {
b.nextCheckpoint = &checkpoints[0] b.nextCheckpoint = &checkpoints[0]
return nil, nil return nil, nil
} }
// Cache the latest known checkpoint block for future lookups.
checkpoint := checkpoints[checkpointIndex]
err = b.db.View(func(dbTx database.Tx) error {
block, err := dbFetchBlockByHash(dbTx, checkpoint.Hash)
if err != nil {
return err
}
b.checkpointBlock = block
// Set the next expected checkpoint block accordingly.
b.nextCheckpoint = nil
if checkpointIndex < numCheckpoints-1 {
b.nextCheckpoint = &checkpoints[checkpointIndex+1]
}
return nil
})
if err != nil {
return nil, err
}
return b.checkpointBlock, nil
}
// At this point we've already searched for the latest known checkpoint, // At this point we've already searched for the latest known checkpoint,
// so when there is no next checkpoint, the current checkpoint lockin // so when there is no next checkpoint, the current checkpoint lockin
// will always be the latest known checkpoint. // will always be the latest known checkpoint.
if b.nextCheckpoint == nil { if b.nextCheckpoint == nil {
return b.checkpointBlock, nil return b.checkpointNode, nil
} }
// When there is a next checkpoint and the height of the current best // When there is a next checkpoint and the height of the current best
// chain does not exceed it, the current checkpoint lockin is still // chain does not exceed it, the current checkpoint lockin is still
// the latest known checkpoint. // the latest known checkpoint.
if b.bestNode.height < b.nextCheckpoint.Height { if b.bestNode.height < b.nextCheckpoint.Height {
return b.checkpointBlock, nil return b.checkpointNode, nil
} }
// We've reached or exceeded the next checkpoint height. Note that // We've reached or exceeded the next checkpoint height. Note that
@ -168,21 +139,17 @@ func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) {
// any blocks before the checkpoint, so we don't have to worry about the // any blocks before the checkpoint, so we don't have to worry about the
// checkpoint going away out from under us due to a chain reorganize. // checkpoint going away out from under us due to a chain reorganize.
// Cache the latest known checkpoint block for future lookups. Note // Cache the latest known checkpoint for future lookups. Note that if
// that if this lookup fails something is very wrong since the chain // this lookup fails something is very wrong since the chain has already
// has already passed the checkpoint which was verified as accurate // passed the checkpoint which was verified as accurate before inserting
// before inserting it. // it.
err := b.db.View(func(tx database.Tx) error { checkpointNode := b.index.LookupNode(b.nextCheckpoint.Hash)
block, err := dbFetchBlockByHash(tx, b.nextCheckpoint.Hash) if checkpointNode == nil {
if err != nil { return nil, AssertError(fmt.Sprintf("findPreviousCheckpoint "+
return err "failed lookup of known good block node %s",
} b.nextCheckpoint.Hash))
b.checkpointBlock = block
return nil
})
if err != nil {
return nil, err
} }
b.checkpointNode = checkpointNode
// Set the next expected checkpoint. // Set the next expected checkpoint.
checkpointIndex := -1 checkpointIndex := -1
@ -197,7 +164,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) {
b.nextCheckpoint = &checkpoints[checkpointIndex+1] b.nextCheckpoint = &checkpoints[checkpointIndex+1]
} }
return b.checkpointBlock, nil return b.checkpointNode, nil
} }
// isNonstandardTransaction determines whether a transaction contains any // isNonstandardTransaction determines whether a transaction contains any

View file

@ -6,6 +6,7 @@ package blockchain
import ( import (
"fmt" "fmt"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/database"
@ -182,14 +183,13 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// used to eat memory, and ensuring expected (versus claimed) proof of // used to eat memory, and ensuring expected (versus claimed) proof of
// work requirements since the previous checkpoint are met. // work requirements since the previous checkpoint are met.
blockHeader := &block.MsgBlock().Header blockHeader := &block.MsgBlock().Header
checkpointBlock, err := b.findPreviousCheckpoint() checkpointNode, err := b.findPreviousCheckpoint()
if err != nil { if err != nil {
return false, false, err return false, false, err
} }
if checkpointBlock != nil { if checkpointNode != nil {
// Ensure the block timestamp is after the checkpoint timestamp. // Ensure the block timestamp is after the checkpoint timestamp.
checkpointHeader := &checkpointBlock.MsgBlock().Header checkpointTime := time.Unix(checkpointNode.timestamp, 0)
checkpointTime := checkpointHeader.Timestamp
if blockHeader.Timestamp.Before(checkpointTime) { if blockHeader.Timestamp.Before(checkpointTime) {
str := fmt.Sprintf("block %v has timestamp %v before "+ str := fmt.Sprintf("block %v has timestamp %v before "+
"last checkpoint timestamp %v", blockHash, "last checkpoint timestamp %v", blockHash,
@ -205,7 +205,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// maximum adjustment allowed by the retarget rules. // maximum adjustment allowed by the retarget rules.
duration := blockHeader.Timestamp.Sub(checkpointTime) duration := blockHeader.Timestamp.Sub(checkpointTime)
requiredTarget := CompactToBig(b.calcEasiestDifficulty( requiredTarget := CompactToBig(b.calcEasiestDifficulty(
checkpointHeader.Bits, duration)) checkpointNode.bits, duration))
currentTarget := CompactToBig(blockHeader.Bits) currentTarget := CompactToBig(blockHeader.Bits)
if currentTarget.Cmp(requiredTarget) > 0 { if currentTarget.Cmp(requiredTarget) > 0 {
str := fmt.Sprintf("block target difficulty of %064x "+ str := fmt.Sprintf("block target difficulty of %064x "+

View file

@ -692,14 +692,14 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
// chain before it. This prevents storage of new, otherwise valid, // chain before it. This prevents storage of new, otherwise valid,
// blocks which build off of old blocks that are likely at a much easier // 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. // difficulty and therefore could be used to waste cache and disk space.
checkpointBlock, err := b.findPreviousCheckpoint() checkpointNode, err := b.findPreviousCheckpoint()
if err != nil { if err != nil {
return err return err
} }
if checkpointBlock != nil && blockHeight < checkpointBlock.Height() { if checkpointNode != nil && blockHeight < checkpointNode.height {
str := fmt.Sprintf("block at height %d forks the main chain "+ str := fmt.Sprintf("block at height %d forks the main chain "+
"before the previous checkpoint at height %d", "before the previous checkpoint at height %d",
blockHeight, checkpointBlock.Height()) blockHeight, checkpointNode.height)
return ruleError(ErrForkTooOld, str) return ruleError(ErrForkTooOld, str)
} }