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:
parent
349450379f
commit
72f2a3fe49
4 changed files with 43 additions and 76 deletions
|
@ -124,7 +124,7 @@ type BlockChain struct {
|
|||
// These fields are related to checkpoint handling. They are protected
|
||||
// by the chain lock.
|
||||
nextCheckpoint *chaincfg.Checkpoint
|
||||
checkpointBlock *btcutil.Block
|
||||
checkpointNode *blockNode
|
||||
|
||||
// The state is used as a fairly efficient way to cache information
|
||||
// about the current best chain state that is returned to callers when
|
||||
|
|
|
@ -80,87 +80,58 @@ func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
|
|||
|
||||
// findPreviousCheckpoint finds the most recent checkpoint that is already
|
||||
// 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
|
||||
// really only happen for blocks before the first checkpoint).
|
||||
// associated block node. It returns nil if a checkpoint can't be found (this
|
||||
// should really only happen for blocks before the first checkpoint).
|
||||
//
|
||||
// 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() {
|
||||
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
|
||||
// checkpoint if the best chain is not known yet or we haven't already
|
||||
// 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
|
||||
// that is already available.
|
||||
checkpointIndex := -1
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
for i := numCheckpoints - 1; i >= 0; i-- {
|
||||
if dbMainChainHasBlock(dbTx, checkpoints[i].Hash) {
|
||||
checkpointIndex = i
|
||||
break
|
||||
node := b.index.LookupNode(checkpoints[i].Hash)
|
||||
if node == nil || !node.inMainChain {
|
||||
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
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return b.checkpointNode, nil
|
||||
}
|
||||
|
||||
// No known latest checkpoint. This will only happen on blocks
|
||||
// before the first known checkpoint. So, set the next expected
|
||||
// checkpoint to the first checkpoint and return the fact there
|
||||
// is no latest known checkpoint block.
|
||||
if checkpointIndex == -1 {
|
||||
b.nextCheckpoint = &checkpoints[0]
|
||||
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,
|
||||
// so when there is no next checkpoint, the current checkpoint lockin
|
||||
// will always be the latest known checkpoint.
|
||||
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
|
||||
// chain does not exceed it, the current checkpoint lockin is still
|
||||
// the latest known checkpoint.
|
||||
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
|
||||
|
@ -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
|
||||
// checkpoint going away out from under us due to a chain reorganize.
|
||||
|
||||
// Cache the latest known checkpoint block for future lookups. Note
|
||||
// that if this lookup fails something is very wrong since the chain
|
||||
// has already passed the checkpoint which was verified as accurate
|
||||
// before inserting it.
|
||||
err := b.db.View(func(tx database.Tx) error {
|
||||
block, err := dbFetchBlockByHash(tx, b.nextCheckpoint.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.checkpointBlock = block
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Cache the latest known checkpoint for future lookups. Note that if
|
||||
// this lookup fails something is very wrong since the chain has already
|
||||
// passed the checkpoint which was verified as accurate before inserting
|
||||
// it.
|
||||
checkpointNode := b.index.LookupNode(b.nextCheckpoint.Hash)
|
||||
if checkpointNode == nil {
|
||||
return nil, AssertError(fmt.Sprintf("findPreviousCheckpoint "+
|
||||
"failed lookup of known good block node %s",
|
||||
b.nextCheckpoint.Hash))
|
||||
}
|
||||
b.checkpointNode = checkpointNode
|
||||
|
||||
// Set the next expected checkpoint.
|
||||
checkpointIndex := -1
|
||||
|
@ -197,7 +164,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) {
|
|||
b.nextCheckpoint = &checkpoints[checkpointIndex+1]
|
||||
}
|
||||
|
||||
return b.checkpointBlock, nil
|
||||
return b.checkpointNode, nil
|
||||
}
|
||||
|
||||
// isNonstandardTransaction determines whether a transaction contains any
|
||||
|
|
|
@ -6,6 +6,7 @@ package blockchain
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"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
|
||||
// work requirements since the previous checkpoint are met.
|
||||
blockHeader := &block.MsgBlock().Header
|
||||
checkpointBlock, err := b.findPreviousCheckpoint()
|
||||
checkpointNode, err := b.findPreviousCheckpoint()
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if checkpointBlock != nil {
|
||||
if checkpointNode != nil {
|
||||
// Ensure the block timestamp is after the checkpoint timestamp.
|
||||
checkpointHeader := &checkpointBlock.MsgBlock().Header
|
||||
checkpointTime := checkpointHeader.Timestamp
|
||||
checkpointTime := time.Unix(checkpointNode.timestamp, 0)
|
||||
if blockHeader.Timestamp.Before(checkpointTime) {
|
||||
str := fmt.Sprintf("block %v has timestamp %v before "+
|
||||
"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.
|
||||
duration := blockHeader.Timestamp.Sub(checkpointTime)
|
||||
requiredTarget := CompactToBig(b.calcEasiestDifficulty(
|
||||
checkpointHeader.Bits, duration))
|
||||
checkpointNode.bits, duration))
|
||||
currentTarget := CompactToBig(blockHeader.Bits)
|
||||
if currentTarget.Cmp(requiredTarget) > 0 {
|
||||
str := fmt.Sprintf("block target difficulty of %064x "+
|
||||
|
|
|
@ -692,14 +692,14 @@ 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.
|
||||
checkpointBlock, err := b.findPreviousCheckpoint()
|
||||
checkpointNode, err := b.findPreviousCheckpoint()
|
||||
if err != nil {
|
||||
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 "+
|
||||
"before the previous checkpoint at height %d",
|
||||
blockHeight, checkpointBlock.Height())
|
||||
blockHeight, checkpointNode.height)
|
||||
return ruleError(ErrForkTooOld, str)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue