lbcd/blocklocator.go
Dave Collins fc804aaec0 Add block locator infastructure.
This commit adds a new type, BlockLocator, and supporting infrastructure
to support generating block locators from a provided hash and getting
a full block locator for the latest known block.  This will likely be
expanded in the future to provide the opposite functionality as well.
That is to say the ability to lookup a block using a block locator.
2013-08-28 11:45:21 -05:00

140 lines
4.6 KiB
Go

// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcchain
import (
"github.com/conformal/btcwire"
)
// BlockLocator is used to help locate a specific block. The algorithm for
// building the block locator is to add the hashes in reverse order until
// the genesis block is reached. In order to keep the list of locator hashes
// to a reasonable number of entries, first the most recent previous 10 block
// hashes are added, then the step is doubled each loop iteration to
// exponentially decrease the number of hashes as a function of the distance
// from the block being located.
type BlockLocator []*btcwire.ShaHash
// BlockLocatorFromHash returns a block locator for the passed block hash.
// See BlockLocator for details on the algotirhm used to create a block locator.
//
// In addition to the general algorithm referenced above, there are a couple of
// special cases which are handled:
//
// - If the genesis hash is passed, there are no previous hashes to add and
// therefore the block locator will only consist of the genesis hash
// - If the passed hash is not currently known, the block locator will only
// consist of the passed hash
func (b *BlockChain) BlockLocatorFromHash(hash *btcwire.ShaHash) BlockLocator {
// The locator contains the requested hash at the very least.
locator := BlockLocator([]*btcwire.ShaHash{hash})
// Nothing more to do if a locator for the genesis hash was requested.
if hash.IsEqual(b.chainParams().GenesisHash) {
return locator
}
// Attempt to find the height of the block that corresponds to the
// passed hash, and if it's on a side chain, find the height at which
// it forks from the main chain.
blockHeight := int64(-1)
forkHeight := int64(-1)
node, exists := b.index[*hash]
if !exists {
// Try to look up the height for passed block hash. Assume an
// error means it doesn't exist and just return the locator for
// the block itself.
block, err := b.db.FetchBlockBySha(hash)
if err != nil {
return locator
}
blockHeight = block.Height()
} else {
blockHeight = node.height
// Find the height at which this node forks from the main chain
// if the node is on a side chain.
if !node.inMainChain {
for n := node; n.parent != nil; n = n.parent {
if n.inMainChain {
forkHeight = n.height
break
}
}
}
}
// Generate the block locators according to the algorithm described in
// in the BlockLocator comment and make sure to leave room for the
// final genesis hash.
iterNode := node
increment := int64(1)
for len(locator) < btcwire.MaxBlockLocatorsPerMsg-1 {
// Once there are 10 locators, exponentially increase the
// distance between each block locator.
if len(locator) > 10 {
increment *= 2
}
blockHeight -= increment
if blockHeight < 1 {
break
}
// As long as this is still on the side chain, walk backwards
// along the side chain nodes to each block height.
if forkHeight != -1 && blockHeight > forkHeight {
// Intentionally use parent field instead of the
// getPrevNodeFromNode function since we don't want to
// dynamically load nodes when building block locators.
// Side chain blocks should always be in memory already,
// and if they aren't for some reason it's ok to skip
// them.
for iterNode != nil && blockHeight > iterNode.height {
iterNode = iterNode.parent
}
if iterNode != nil && iterNode.height == blockHeight {
locator = append(locator, iterNode.hash)
}
continue
}
// The desired block height is in the main chain, so look it up
// from the main chain database.
h, err := b.db.FetchBlockShaByHeight(blockHeight)
if err != nil {
// This shouldn't happen and it's ok to ignore block
// locators, so just continue to the next one.
log.Warnf("Lookup of known valid height failed %v",
blockHeight)
continue
}
locator = append(locator, h)
}
// Append the appropriate genesis block.
locator = append(locator, b.chainParams().GenesisHash)
return locator
}
// LatestBlockLocator returns a block locator for the latest known tip of the
// main (best) chain.
func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) {
// Lookup the latest main chain hash if the best chain hasn't been set
// yet.
if b.bestChain == nil {
// Get the latest block hash for the main chain from the
// database.
hash, _, err := b.db.NewestSha()
if err != nil {
return nil, err
}
return b.BlockLocatorFromHash(hash), nil
}
// The best chain is set, so use its hash.
return b.BlockLocatorFromHash(b.bestChain.hash), nil
}