From bc96019ad3ee4fa0cc63ed22f1ba473ba2ce73dd Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 May 2018 07:34:38 -0500 Subject: [PATCH] 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. --- blockchain/chain.go | 4 +- blockchain/checkpoints.go | 99 +++++++++++++-------------------------- blockchain/process.go | 9 ++-- blockchain/validate.go | 11 ++--- 4 files changed, 44 insertions(+), 79 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 1a8ba63696..3de8b2297b 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -150,8 +150,8 @@ type BlockChain struct { // These fields are related to checkpoint handling. They are protected // by the chain lock. - nextCheckpoint *chaincfg.Checkpoint - checkpointBlock *dcrutil.Block + nextCheckpoint *chaincfg.Checkpoint + 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 diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index 8ecee27e09..b03c9e7748 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -99,87 +99,58 @@ func (b *BlockChain) verifyCheckpoint(height int64, 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() (*dcrutil.Block, error) { +func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) { if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 { return nil, nil } - // No checkpoints. - checkpoints := b.chainParams.Checkpoints - numCheckpoints := len(checkpoints) - if numCheckpoints == 0 { - 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.chainParams.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 - } + for i := numCheckpoints - 1; i >= 0; i-- { + node := b.index.LookupNode(checkpoints[i].Hash) + if node == nil || !node.inMainChain { + continue } - return nil - }) - if err != nil { - return nil, err + + // 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 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 + b.nextCheckpoint = &checkpoints[0] + return nil, 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 @@ -187,21 +158,17 @@ func (b *BlockChain) findPreviousCheckpoint() (*dcrutil.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 @@ -216,7 +183,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*dcrutil.Block, error) { b.nextCheckpoint = &checkpoints[checkpointIndex+1] } - return b.checkpointBlock, nil + return b.checkpointNode, nil } // isNonstandardTransaction determines whether a transaction contains any diff --git a/blockchain/process.go b/blockchain/process.go index 1a3e03f0ba..693b4b8fab 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -145,14 +145,13 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in // 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 0, 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, @@ -169,7 +168,7 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in // 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 "+ diff --git a/blockchain/validate.go b/blockchain/validate.go index 10098ea79f..a6d15361ac 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -912,17 +912,16 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // Find the previous checkpoint and prevent blocks which fork the main // 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() + // 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() 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) }