From 1b54badde1b8ef67a3963230ec70a77e7a2c34bc Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 17 Jan 2014 00:07:02 -0600 Subject: [PATCH] Improve efficiency of checkpoint tracking. Previously the code performed a database query for every checkpoint (going backwards) to find the latest known checkpoint on every block. This was particularly noticabled near the beginning of the block chain when there are still several checkpoints that haven't been reached yet. This commit changes the logic to cache the latest known checkpoint while keeping track of when it needs to be updated once a new later known checkpoint has been reached. While here, also add a log message when a checkpoint has been reached and verified. --- chain.go | 30 +++++++------- checkpoints.go | 106 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/chain.go b/chain.go index cf6dc532..52a005ce 100644 --- a/chain.go +++ b/chain.go @@ -142,20 +142,22 @@ func removeChildNode(children []*blockNode, node *blockNode) []*blockNode { // follow all rules, orphan handling, checkpoint handling, and best chain // selection with reorganization. type BlockChain struct { - db btcdb.Db - btcnet btcwire.BitcoinNet - notifications NotificationCallback - root *blockNode - bestChain *blockNode - index map[btcwire.ShaHash]*blockNode - depNodes map[btcwire.ShaHash][]*blockNode - orphans map[btcwire.ShaHash]*orphanBlock - prevOrphans map[btcwire.ShaHash][]*orphanBlock - oldestOrphan *orphanBlock - orphanLock sync.RWMutex - blockCache map[btcwire.ShaHash]*btcutil.Block - noVerify bool - noCheckpoints bool + db btcdb.Db + btcnet btcwire.BitcoinNet + notifications NotificationCallback + root *blockNode + bestChain *blockNode + index map[btcwire.ShaHash]*blockNode + depNodes map[btcwire.ShaHash][]*blockNode + orphans map[btcwire.ShaHash]*orphanBlock + prevOrphans map[btcwire.ShaHash][]*orphanBlock + oldestOrphan *orphanBlock + orphanLock sync.RWMutex + blockCache map[btcwire.ShaHash]*btcutil.Block + noVerify bool + noCheckpoints bool + nextCheckpoint *Checkpoint + checkpointBlock *btcutil.Block } // DisableVerify provides a mechanism to disable transaction script validation diff --git a/checkpoints.go b/checkpoints.go index 47951dc1..f6ff4577 100644 --- a/checkpoints.go +++ b/checkpoints.go @@ -125,10 +125,16 @@ func (b *BlockChain) verifyCheckpoint(height int64, hash *btcwire.ShaHash) bool return true } - return checkpoint.Hash.IsEqual(hash) + if !checkpoint.Hash.IsEqual(hash) { + return false + } + + log.Infof("Verfied checkpoint at height %d/block %s", checkpoint.Height, + checkpoint.Hash) + return true } -// findClosestKnownCheckpoint finds the most recent checkpoint that is already +// findLatestKnownCheckpoint 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). @@ -137,20 +143,96 @@ func (b *BlockChain) findLatestKnownCheckpoint() (*btcutil.Block, error) { return nil, nil } - // Loop backwards through the available checkpoints to find one that - // we already have. + // No checkpoints. checkpoints := b.checkpointData().checkpoints - clen := len(checkpoints) - for i := clen - 1; i >= 0; i-- { - if b.db.ExistsSha(checkpoints[i].Hash) { - block, err := b.db.FetchBlockBySha(checkpoints[i].Hash) - if err != nil { - return nil, err + 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.bestChain == nil || (b.checkpointBlock == nil && b.nextCheckpoint == nil) { + // Loop backwards through the available checkpoints to find one + // that we already have. + checkpointIndex := -1 + for i := numCheckpoints - 1; i >= 0; i-- { + if b.db.ExistsSha(checkpoints[i].Hash) { + checkpointIndex = i + break } - return block, 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] + block, err := b.db.FetchBlockBySha(checkpoint.Hash) + if err != nil { + return nil, err + } + b.checkpointBlock = block + + // Set the next expected checkpoint block accordingly. + b.nextCheckpoint = nil + if checkpointIndex < numCheckpoints-1 { + b.nextCheckpoint = &checkpoints[checkpointIndex+1] + } + + return block, 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 + } + + // 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.bestChain.height < b.nextCheckpoint.Height { + return b.checkpointBlock, nil + } + + // We've reached or exceeded the next checkpoint height. Note that + // once a checkpoint lockin has been reached, forks are prevented from + // 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. + block, err := b.db.FetchBlockBySha(b.nextCheckpoint.Hash) + if err != nil { + return nil, err + } + b.checkpointBlock = block + + // Set the next expected checkpoint. + checkpointIndex := -1 + for i := numCheckpoints - 1; i >= 0; i-- { + if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) { + checkpointIndex = i + break } } - return nil, nil + b.nextCheckpoint = nil + if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 { + b.nextCheckpoint = &checkpoints[checkpointIndex+1] + } + + return b.checkpointBlock, nil } // isNonstandardTransaction determines whether a transaction contains any