blockchain: Optimize block locator generation.
This significantly optimizes and simplifies the generation of block locators by making use of the fact that all block nodes are now in memory and therefore it is no longer necessary to consult the database for the hashes or worry about issues related to dynamic loading of nodes. Also, it slightly modifies the algorithm so that the doubling doesn't start for one additional iteration in order to mirror other prominent clients on the network. Due to the way block locators are used, this does not change any semantics in terms of requesting and locating blocks. Finally, the semantics of BlockLocatorFromHash have been changed to return a locator for the current tip in the case the hash is unknown. This is far preferable since only including the passed block hash, when it isn't known, could end up leading to causing a redownload of the entire chain under certain circumstances.
This commit is contained in:
parent
e02fbcf5a1
commit
93d8dfc760
1 changed files with 77 additions and 118 deletions
|
@ -6,17 +6,34 @@ package blockchain
|
|||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// log2FloorMasks defines the masks to use when quickly calculating
|
||||
// floor(log2(x)) in a constant log2(32) = 5 steps, where x is a uint32, using
|
||||
// shifts. They are derived from (2^(2^x) - 1) * (2^(2^x)), for x in 4..0.
|
||||
var log2FloorMasks = []uint32{0xffff0000, 0xff00, 0xf0, 0xc, 0x2}
|
||||
|
||||
// fastLog2Floor calculates and returns floor(log2(x)) in a constant 5 steps.
|
||||
func fastLog2Floor(n uint32) uint8 {
|
||||
rv := uint8(0)
|
||||
exponent := uint8(16)
|
||||
for i := 0; i < 5; i++ {
|
||||
if n&log2FloorMasks[i] != 0 {
|
||||
rv += exponent
|
||||
n >>= exponent
|
||||
}
|
||||
exponent >>= 1
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// 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.
|
||||
// to a reasonable number of entries, first the most recent 12 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.
|
||||
//
|
||||
// For example, assume you have a block chain with a side chain as depicted
|
||||
// below:
|
||||
|
@ -24,121 +41,59 @@ import (
|
|||
// \-> 16a -> 17a
|
||||
//
|
||||
// The block locator for block 17a would be the hashes of blocks:
|
||||
// [17a 16a 15 14 13 12 11 10 9 8 6 2 genesis]
|
||||
// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis]
|
||||
type BlockLocator []*chainhash.Hash
|
||||
|
||||
// blockLocatorFromHash returns a block locator for the passed block hash.
|
||||
// blockLocator returns a block locator for the passed block node.
|
||||
//
|
||||
// See BlockLocator for details on the algorithm 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
|
||||
//
|
||||
// This function MUST be called with the block index lock held (for reads).
|
||||
func (bi *blockIndex) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
|
||||
// The locator contains the requested hash at the very least.
|
||||
locator := make(BlockLocator, 0, wire.MaxBlockLocatorsPerMsg)
|
||||
locator = append(locator, hash)
|
||||
|
||||
// Nothing more to do if a locator for the genesis hash was requested.
|
||||
if hash.IsEqual(bi.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, also find the height at
|
||||
// which it forks from the main chain.
|
||||
blockHeight := int32(-1)
|
||||
forkHeight := int32(-1)
|
||||
node, exists := bi.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.
|
||||
var height int32
|
||||
err := bi.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
height, err = dbFetchHeightByHash(dbTx, hash)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return locator
|
||||
}
|
||||
|
||||
blockHeight = 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.
|
||||
//
|
||||
// The error is intentionally ignored here since the only way the code
|
||||
// could fail is if there is something wrong with the database which
|
||||
// will be caught in short order anyways and it's also safe to ignore
|
||||
// block locators.
|
||||
_ = bi.db.View(func(dbTx database.Tx) error {
|
||||
iterNode := node
|
||||
increment := int32(1)
|
||||
for len(locator) < wire.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 {
|
||||
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 := dbFetchHashByHeight(dbTx, 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)
|
||||
}
|
||||
|
||||
func blockLocator(node *blockNode) BlockLocator {
|
||||
if node == nil {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Calculate the max number of entries that will ultimately be in the
|
||||
// block locator. See the description of the algorithm for how these
|
||||
// numbers are derived.
|
||||
var maxEntries uint8
|
||||
if node.height <= 12 {
|
||||
maxEntries = uint8(node.height) + 1
|
||||
} else {
|
||||
// Requested hash itself + previous 10 entries + genesis block.
|
||||
// Then floor(log2(height-10)) entries for the skip portion.
|
||||
adjustedHeight := uint32(node.height) - 10
|
||||
maxEntries = 12 + fastLog2Floor(adjustedHeight)
|
||||
}
|
||||
locator := make(BlockLocator, 0, maxEntries)
|
||||
|
||||
step := int32(1)
|
||||
for node != nil {
|
||||
locator = append(locator, &node.hash)
|
||||
|
||||
// Nothing more to add once the genesis block has been added.
|
||||
if node.height == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Calculate height of previous node to include ensuring the
|
||||
// final node is the genesis block.
|
||||
height := node.height - step
|
||||
if height < 0 {
|
||||
height = 0
|
||||
}
|
||||
|
||||
// Walk backwards through the nodes to the correct ancestor.
|
||||
node = node.Ancestor(height)
|
||||
|
||||
// Once 11 entries have been included, start doubling the
|
||||
// distance between included hashes.
|
||||
if len(locator) > 10 {
|
||||
step *= 2
|
||||
}
|
||||
}
|
||||
|
||||
// Append the appropriate genesis block.
|
||||
locator = append(locator, bi.chainParams.GenesisHash)
|
||||
return locator
|
||||
}
|
||||
|
||||
|
@ -150,14 +105,18 @@ func (bi *blockIndex) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
|
|||
//
|
||||
// - 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
|
||||
// - If the passed hash is not currently known, the block locator will be for
|
||||
// the latest known tip of the main (best) chain.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
|
||||
b.chainLock.RLock()
|
||||
b.index.RLock()
|
||||
locator := b.index.blockLocatorFromHash(hash)
|
||||
node, exists := b.index.index[*hash]
|
||||
if !exists {
|
||||
node = b.bestNode
|
||||
}
|
||||
locator := blockLocator(node)
|
||||
b.index.RUnlock()
|
||||
b.chainLock.RUnlock()
|
||||
return locator
|
||||
|
@ -170,7 +129,7 @@ func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
|
|||
func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) {
|
||||
b.chainLock.RLock()
|
||||
b.index.RLock()
|
||||
locator := b.index.blockLocatorFromHash(&b.bestNode.hash)
|
||||
locator := blockLocator(b.bestNode)
|
||||
b.index.RUnlock()
|
||||
b.chainLock.RUnlock()
|
||||
return locator, nil
|
||||
|
|
Loading…
Add table
Reference in a new issue