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
|
// 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 "+
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue