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.
This commit is contained in:
Dave Collins 2014-01-17 00:07:02 -06:00
parent 4c13410e62
commit 1b54badde1
2 changed files with 110 additions and 26 deletions

View file

@ -156,6 +156,8 @@ type BlockChain struct {
blockCache map[btcwire.ShaHash]*btcutil.Block blockCache map[btcwire.ShaHash]*btcutil.Block
noVerify bool noVerify bool
noCheckpoints bool noCheckpoints bool
nextCheckpoint *Checkpoint
checkpointBlock *btcutil.Block
} }
// DisableVerify provides a mechanism to disable transaction script validation // DisableVerify provides a mechanism to disable transaction script validation

View file

@ -125,10 +125,16 @@ func (b *BlockChain) verifyCheckpoint(height int64, hash *btcwire.ShaHash) bool
return true return true
} }
return checkpoint.Hash.IsEqual(hash) if !checkpoint.Hash.IsEqual(hash) {
return false
} }
// findClosestKnownCheckpoint finds the most recent checkpoint that is already log.Infof("Verfied checkpoint at height %d/block %s", checkpoint.Height,
checkpoint.Hash)
return true
}
// findLatestKnownCheckpoint 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. It returns nil if a checkpoint can't be found (this should
// really only happen for blocks before the first checkpoint). // really only happen for blocks before the first checkpoint).
@ -137,20 +143,96 @@ func (b *BlockChain) findLatestKnownCheckpoint() (*btcutil.Block, error) {
return nil, nil return nil, nil
} }
// Loop backwards through the available checkpoints to find one that // No checkpoints.
// we already have.
checkpoints := b.checkpointData().checkpoints checkpoints := b.checkpointData().checkpoints
clen := len(checkpoints) numCheckpoints := len(checkpoints)
for i := clen - 1; i >= 0; i-- { 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) { if b.db.ExistsSha(checkpoints[i].Hash) {
block, err := b.db.FetchBlockBySha(checkpoints[i].Hash) checkpointIndex = i
break
}
}
// 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 { if err != nil {
return nil, err 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 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
} }
return nil, 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
}
}
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 // isNonstandardTransaction determines whether a transaction contains any