296fa0a5a0
This reworks the block index code such that it loads all of the headers in the main chain at startup and constructs the full block index accordingly. Since the full index from the current best tip all the way back to the genesis block is now guaranteed to be in memory, this also removes all code related to dynamically loading the nodes and updates some of the logic to take advantage of the fact traversing the block index can longer potentially fail. There are also more optimizations and simplifications that can be made in the future as a result of this. Due to removing all of the extra overhead of tracking the dynamic state, and ensuring the block node structs are aligned to eliminate extra padding, the end result of a fully populated block index now takes quite a bit less memory than the previous dynamically loaded version. The main downside is that it now takes a while to start whereas it was nearly instant before, however, it is much better to provide more efficient runtime operation since that is its ultimate purpose and the benefits far outweigh this downside. Some benefits are: - Since every block node is in memory, the recent code which reconstructs headers from block nodes means that all headers can always be served from memory which is important since the majority of the network has moved to header-based semantics - Several of the error paths can be removed since they are no longer necessary - It is no longer expensive to calculate CSV sequence locks or median times of blocks way in the past - It will be possible to create much more efficient iteration and simplified views of the overall index - The entire threshold state database cache can be removed since it is cheap to construct it from the full block index as needed An overview of the logic changes are as follows: - Move AncestorNode from blockIndex to blockNode and greatly simplify since it no longer has to deal with the possibility of dynamically loading nodes and related failures - Rename RelativeNode to RelativeAncestor, move to blockNode, and redefine in terms of AncestorNode - Move CalcPastMedianTime from blockIndex to blockNode and remove no longer necessary test for nil - Change calcSequenceLock to use Ancestor instead of RelativeAncestor since it reads more clearly
177 lines
5.8 KiB
Go
177 lines
5.8 KiB
Go
// Copyright (c) 2013-2017 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/database"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
// 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.
|
|
//
|
|
// For example, assume you have a block chain with a side chain as depicted
|
|
// below:
|
|
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
|
// \-> 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]
|
|
type BlockLocator []*chainhash.Hash
|
|
|
|
// blockLocatorFromHash returns a block locator for the passed block hash.
|
|
// 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)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
// Append the appropriate genesis block.
|
|
locator = append(locator, bi.chainParams.GenesisHash)
|
|
return locator
|
|
}
|
|
|
|
// BlockLocatorFromHash returns a block locator for the passed block hash.
|
|
// 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 is safe for concurrent access.
|
|
func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
|
|
b.chainLock.RLock()
|
|
b.index.RLock()
|
|
locator := b.index.blockLocatorFromHash(hash)
|
|
b.index.RUnlock()
|
|
b.chainLock.RUnlock()
|
|
return locator
|
|
}
|
|
|
|
// LatestBlockLocator returns a block locator for the latest known tip of the
|
|
// main (best) chain.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) {
|
|
b.chainLock.RLock()
|
|
b.index.RLock()
|
|
locator := b.index.blockLocatorFromHash(&b.bestNode.hash)
|
|
b.index.RUnlock()
|
|
b.chainLock.RUnlock()
|
|
return locator, nil
|
|
}
|