blockchain: Refactor to use new chain view.

- Remove inMainChain from block nodes since that can now be efficiently
  determined by using the chain view
- Track the best chain via a chain view instead of a single block node
  - Use the tip of the best chain view everywhere bestNode was used
  - Update chain view tip instead of updating best node
- Change reorg logic to use more efficient chain view fork finding logic
- Change block locator code over to use more efficient chain view logic
  - Remove now unused block-index-based block locator code
  - Move BlockLocator definition to chain.go
  - Move BlockLocatorFromHash and LatestBlockLocator to chain.go
    - Update both to use more efficient chain view logic
- Rework IsCheckpointCandidate to use block index and chain view
- Optimize MainChainHasBlock to use chain view instead of hitting db
  - Move to chain.go since it no longer involves database I/O
  - Removed error return since it can no longer fail
- Optimize BlockHeightByHash to use chain view instead of hitting db
  - Move to chain.go since it no longer involves database I/O
  - Removed error return since it can no longer fail
- Optimize BlockHashByHeight to use chain view instead of hitting db
  - Move to chain.go since it no longer involves database I/O
  - Removed error return since it can no longer fail
- Optimize HeightRange to use chain view instead of hitting db
  - Move to chain.go since it no longer involves database I/O
- Optimize BlockByHeight to use chain view for main chain check
- Optimize BlockByHash to use chain view for main chain check
This commit is contained in:
Dave Collins 2017-08-18 07:25:54 -05:00
parent 08955805d5
commit 20910511e9
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
13 changed files with 295 additions and 446 deletions

View file

@ -40,11 +40,6 @@ type blockNode struct {
// height is the position in the block chain.
height int32
// inMainChain denotes whether the block node is currently on the
// the main chain or not. This is used to help find the common
// ancestor when switching chains.
inMainChain bool
// Some fields from block headers to aid in best chain selection and
// reconstructing headers from memory. These must be treated as
// immutable and are intentionally ordered to avoid padding on 64-bit

View file

@ -1,136 +0,0 @@
// 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"
)
// 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 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:
// 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 7 6 4 genesis]
type BlockLocator []*chainhash.Hash
// blockLocator returns a block locator for the passed block node.
//
// See BlockLocator for details on the algorithm used to create a block locator.
//
// This function MUST be called with the block index lock held (for reads).
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
}
}
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 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()
node, exists := b.index.index[*hash]
if !exists {
node = b.bestNode
}
locator := blockLocator(node)
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 := blockLocator(b.bestNode)
b.index.RUnlock()
b.chainLock.RUnlock()
return locator, nil
}

View file

@ -24,6 +24,22 @@ const (
maxOrphanBlocks = 100
)
// 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 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 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 7 6 4 genesis]
type BlockLocator []*chainhash.Hash
// orphanBlock represents a block that we don't yet have the parent for. It
// is a normal block plus an expiration time to prevent caching the orphan
// forever.
@ -105,10 +121,17 @@ type BlockChain struct {
// fields in this struct below this point.
chainLock sync.RWMutex
// These fields are related to the memory block index. The best node
// is protected by the chain lock and the index has its own locks.
bestNode *blockNode
index *blockIndex
// These fields are related to the memory block index. They both have
// their own locks, however they are often also protected by the chain
// lock to help prevent logic races when blocks are being processed.
//
// index houses the entire block index in memory. The block index is
// a tree-shaped structure.
//
// bestChain tracks the current active chain by making use of an
// efficient chain view into the block index.
index *blockIndex
bestChain *chainView
// These fields are related to handling of orphan blocks. They are
// protected by a combination of the chain lock and the orphan lock.
@ -336,7 +359,7 @@ func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint, m
b.chainLock.Lock()
defer b.chainLock.Unlock()
return b.calcSequenceLock(b.bestNode, tx, utxoView, mempool)
return b.calcSequenceLock(b.bestChain.Tip(), tx, utxoView, mempool)
}
// calcSequenceLock computes the relative lock-times for the passed
@ -493,21 +516,15 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
// to attach to the main tree. Push them onto the list in reverse order
// so they are attached in the appropriate order when iterating the list
// later.
ancestor := node
for ; ancestor.parent != nil; ancestor = ancestor.parent {
if ancestor.inMainChain {
break
}
attachNodes.PushFront(ancestor)
forkNode := b.bestChain.FindFork(node)
for n := node; n != nil && n != forkNode; n = n.parent {
attachNodes.PushFront(n)
}
// Start from the end of the main chain and work backwards until the
// common ancestor adding each block to the list of nodes to detach from
// the main chain.
for n := b.bestNode; n != nil && n.parent != nil; n = n.parent {
if n.hash.IsEqual(&ancestor.hash) {
break
}
for n := b.bestChain.Tip(); n != nil && n != forkNode; n = n.parent {
detachNodes.PushBack(n)
}
@ -542,7 +559,7 @@ func dbMaybeStoreBlock(dbTx database.Tx, block *btcutil.Block) error {
func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos []spentTxOut) error {
// Make sure it's extending the end of the best chain.
prevHash := &block.MsgBlock().Header.PrevBlock
if !prevHash.IsEqual(&b.bestNode.hash) {
if !prevHash.IsEqual(&b.bestChain.Tip().hash) {
return AssertError("connectBlock must be called with a block " +
"that extends the main chain")
}
@ -630,12 +647,9 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
// now that the modifications have been committed to the database.
view.commit()
// Add the new node to the memory main chain indices for faster lookups.
node.inMainChain = true
b.index.AddNode(node)
// This node is now the end of the best chain.
b.bestNode = node
b.index.AddNode(node)
b.bestChain.SetTip(node)
// Update the state for the best block. Notice how this replaces the
// entire struct instead of updating the existing one. This effectively
@ -662,7 +676,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error {
// Make sure the node being disconnected is the end of the best chain.
if !node.hash.IsEqual(&b.bestNode.hash) {
if !node.hash.IsEqual(&b.bestChain.Tip().hash) {
return AssertError("disconnectBlock must be called with the " +
"block at the end of the main chain")
}
@ -672,7 +686,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
var prevBlock *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
prevBlock, err = dbFetchBlockByHash(dbTx, &prevNode.hash)
prevBlock, err = dbFetchBlockByNode(dbTx, prevNode)
return err
})
if err != nil {
@ -740,11 +754,8 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
// now that the modifications have been committed to the database.
view.commit()
// Mark block as being in a side chain.
node.inMainChain = false
// This node's parent is now the end of the best chain.
b.bestNode = node.parent
b.bestChain.SetTip(node.parent)
// Update the state for the best block. Notice how this replaces the
// entire struct instead of updating the existing one. This effectively
@ -803,13 +814,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// database and using that information to unspend all of the spent txos
// and remove the utxos created by the blocks.
view := NewUtxoViewpoint()
view.SetBestHash(&b.bestNode.hash)
view.SetBestHash(&b.bestChain.Tip().hash)
for e := detachNodes.Front(); e != nil; e = e.Next() {
n := e.Value.(*blockNode)
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByHash(dbTx, &n.hash)
block, err = dbFetchBlockByNode(dbTx, n)
return err
})
if err != nil {
@ -860,20 +871,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
n := e.Value.(*blockNode)
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
// NOTE: This block is not in the main chain, so the
// block has to be loaded directly from the database
// instead of using the dbFetchBlockByHash function.
blockBytes, err := dbTx.FetchBlock(&n.hash)
if err != nil {
return err
}
block, err = btcutil.NewBlockFromBytes(blockBytes)
if err != nil {
return err
}
block.SetHeight(n.height)
return nil
var err error
block, err = dbFetchBlockByNode(dbTx, n)
return err
})
if err != nil {
return err
@ -904,7 +904,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// view to be valid from the viewpoint of each block being connected or
// disconnected.
view = NewUtxoViewpoint()
view.SetBestHash(&b.bestNode.hash)
view.SetBestHash(&b.bestChain.Tip().hash)
// Disconnect blocks from the main chain.
for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() {
@ -997,7 +997,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
// We are extending the main (best) chain with a new block. This is the
// most common case.
parentHash := &block.MsgBlock().Header.PrevBlock
if parentHash.IsEqual(&b.bestNode.hash) {
if parentHash.IsEqual(&b.bestChain.Tip().hash) {
// Perform several checks to verify the block can be connected
// to the main chain without violating any rules and without
// actually connecting the block.
@ -1051,9 +1051,6 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
b.index.index[node.hash] = node
b.index.Unlock()
// Mark node as in a side chain.
node.inMainChain = false
// Disconnect it from the parent node when the function returns when
// running in dry run mode.
if dryRun {
@ -1066,21 +1063,14 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
// We're extending (or creating) a side chain, but the cumulative
// work for this new side chain is not enough to make it the new chain.
if node.workSum.Cmp(b.bestNode.workSum) <= 0 {
if node.workSum.Cmp(b.bestChain.Tip().workSum) <= 0 {
// Skip Logging info when the dry run flag is set.
if dryRun {
return false, nil
}
// Find the fork point.
fork := node
for ; fork.parent != nil; fork = fork.parent {
if fork.inMainChain {
break
}
}
// Log information about how the block is forking the chain.
fork := b.bestChain.FindFork(node)
if fork.hash.IsEqual(parentHash) {
log.Infof("FORK: Block %v forks the chain at height %d"+
"/block %v, but does not cause a reorganize",
@ -1127,7 +1117,7 @@ func (b *BlockChain) isCurrent() bool {
// Not current if the latest main (best) chain height is before the
// latest known good checkpoint (when checkpoints are enabled).
checkpoint := b.LatestCheckpoint()
if checkpoint != nil && b.bestNode.height < checkpoint.Height {
if checkpoint != nil && b.bestChain.Tip().height < checkpoint.Height {
return false
}
@ -1137,7 +1127,7 @@ func (b *BlockChain) isCurrent() bool {
// The chain appears to be current if none of the checks reported
// otherwise.
minus24Hours := b.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix()
return b.bestNode.timestamp >= minus24Hours
return b.bestChain.Tip().timestamp >= minus24Hours
}
// IsCurrent returns whether or not the chain believes it is current. Several
@ -1187,6 +1177,119 @@ func (b *BlockChain) FetchHeader(hash *chainhash.Hash) (wire.BlockHeader, error)
return *header, nil
}
// MainChainHasBlock returns whether or not the block with the given hash is in
// the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool {
node := b.index.LookupNode(hash)
return node != nil && b.bestChain.Contains(node)
}
// 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, this function will
// return the block locator for the latest known tip of the main (best) chain if
// the passed hash is not currently known.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
b.chainLock.RLock()
node := b.index.LookupNode(hash)
locator := b.bestChain.blockLocator(node)
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()
locator := b.bestChain.BlockLocator(nil)
b.chainLock.RUnlock()
return locator, nil
}
// BlockHeightByHash returns the height of the block with the given hash in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int32, error) {
node := b.index.LookupNode(hash)
if node == nil || !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", hash)
return 0, errNotInMainChain(str)
}
return node.height, nil
}
// BlockHashByHeight returns the hash of the block at the given height in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, error) {
node := b.bestChain.NodeByHeight(blockHeight)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", blockHeight)
return nil, errNotInMainChain(str)
}
return &node.hash, nil
}
// HeightRange returns a range of block hashes for the given start and end
// heights. It is inclusive of the start height and exclusive of the end
// height. The end height will be limited to the current main chain height.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash, error) {
// Ensure requested heights are sane.
if startHeight < 0 {
return nil, fmt.Errorf("start height of fetch range must not "+
"be less than zero - got %d", startHeight)
}
if endHeight < startHeight {
return nil, fmt.Errorf("end height of fetch range must not "+
"be less than the start height - got start %d, end %d",
startHeight, endHeight)
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid the chain view lock.
if startHeight == endHeight {
return nil, nil
}
// Grab a lock on the chain view to prevent it from changing due to a
// reorg while building the hashes.
b.bestChain.mtx.Lock()
defer b.bestChain.mtx.Unlock()
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
latestHeight := b.bestChain.tip().height
if startHeight > latestHeight {
return nil, nil
}
// Limit the ending height to the latest height of the chain.
if endHeight > latestHeight+1 {
endHeight = latestHeight + 1
}
// Fetch as many as are available within the specified range.
hashes := make([]chainhash.Hash, 0, endHeight-startHeight)
for i := startHeight; i < endHeight; i++ {
hashes = append(hashes, b.bestChain.nodeByHeight(i).hash)
}
return hashes, nil
}
// IndexManager provides a generic interface that the is called when blocks are
// connected and disconnected to and from the tip of the main chain for the
// purpose of supporting optional indexes.
@ -1309,6 +1412,7 @@ func New(config *Config) (*BlockChain, error) {
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
index: newBlockIndex(config.DB, params),
hashCache: config.HashCache,
bestChain: newChainView(nil),
orphans: make(map[chainhash.Hash]*orphanBlock),
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
warningCaches: newThresholdCaches(vbNumBits),
@ -1335,9 +1439,10 @@ func New(config *Config) (*BlockChain, error) {
return nil, err
}
bestNode := b.bestChain.Tip()
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
b.bestNode.height, b.bestNode.hash, b.stateSnapshot.TotalTxns,
b.bestNode.workSum)
bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns,
bestNode.workSum)
return &b, nil
}

View file

@ -125,14 +125,14 @@ func TestCalcSequenceLock(t *testing.T) {
// Generate enough synthetic blocks to activate CSV.
chain := newFakeChain(netParams)
node := chain.bestNode
node := chain.bestChain.Tip()
blockTime := node.Header().Timestamp
numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
for i := uint32(0); i < numBlocksToActivate; i++ {
blockTime = blockTime.Add(time.Second)
node = newFakeNode(node, blockVersion, 0, blockTime)
chain.index.AddNode(node)
chain.bestNode = node
chain.bestChain.SetTip(node)
}
// Create a utxo view with a fake utxo for the inputs used in the

View file

@ -1060,8 +1060,7 @@ func (b *BlockChain) createChainState() error {
genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock)
header := &genesisBlock.MsgBlock().Header
node := newBlockNode(header, 0)
node.inMainChain = true
b.bestNode = node
b.bestChain.SetTip(node)
// Add the new node to the index which is used for faster lookups.
b.index.AddNode(node)
@ -1071,8 +1070,8 @@ func (b *BlockChain) createChainState() error {
numTxns := uint64(len(genesisBlock.MsgBlock().Transactions))
blockSize := uint64(genesisBlock.MsgBlock().SerializeSize())
blockWeight := uint64(GetBlockWeight(genesisBlock))
b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight,
numTxns, numTxns, time.Unix(b.bestNode.timestamp, 0))
b.stateSnapshot = newBestState(node, blockSize, blockWeight, numTxns,
numTxns, time.Unix(node.timestamp, 0))
// Create the initial the database chain state including creating the
// necessary index buckets and inserting the genesis block.
@ -1108,13 +1107,13 @@ func (b *BlockChain) createChainState() error {
// Add the genesis block hash to height and height to hash
// mappings to the index.
err = dbPutBlockIndex(dbTx, &b.bestNode.hash, b.bestNode.height)
err = dbPutBlockIndex(dbTx, &node.hash, node.height)
if err != nil {
return err
}
// Store the current best chain state into the database.
err = dbPutBestState(dbTx, b.stateSnapshot, b.bestNode.workSum)
err = dbPutBestState(dbTx, b.stateSnapshot, node.workSum)
if err != nil {
return err
}
@ -1154,6 +1153,7 @@ func (b *BlockChain) initChainState() error {
log.Infof("Loading block index. This might take a while...")
bestHeight := int32(state.height)
blockNodes := make([]blockNode, bestHeight+1)
var tip *blockNode
for height := int32(0); height <= bestHeight; height++ {
header, err := dbFetchHeaderByHeight(dbTx, height)
if err != nil {
@ -1164,25 +1164,29 @@ func (b *BlockChain) initChainState() error {
// and add it to the block index.
node := &blockNodes[height]
initBlockNode(node, header, height)
if parent := b.bestNode; parent != nil {
node.parent = parent
node.workSum = node.workSum.Add(parent.workSum,
if tip != nil {
node.parent = tip
node.workSum = node.workSum.Add(tip.workSum,
node.workSum)
}
node.inMainChain = true
b.index.AddNode(node)
// This node is now the end of the best chain.
b.bestNode = node
tip = node
}
// Ensure the resulting best node matches the stored best state
// hash.
if b.bestNode.hash != state.hash {
// Ensure the resulting best chain matches the stored best state
// hash and set the best chain view accordingly.
if tip == nil || tip.hash != state.hash {
var tipHash chainhash.Hash
if tip != nil {
tipHash = tip.hash
}
return AssertError(fmt.Sprintf("initChainState: block "+
"index chain tip %s does not match stored "+
"best state %s", b.bestNode.hash, state.hash))
"best state %s", tipHash, state.hash))
}
b.bestChain.SetTip(tip)
// Load the raw block bytes for the best block.
blockBytes, err := dbTx.FetchBlock(&state.hash)
@ -1199,8 +1203,8 @@ func (b *BlockChain) initChainState() error {
blockSize := uint64(len(blockBytes))
blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block)))
numTxns := uint64(len(block.Transactions))
b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight,
numTxns, state.totalTxns, b.bestNode.CalcPastMedianTime())
b.stateSnapshot = newBestState(tip, blockSize, blockWeight,
numTxns, state.totalTxns, tip.CalcPastMedianTime())
isStateInitialized = true
return nil
@ -1247,44 +1251,12 @@ func dbFetchHeaderByHeight(dbTx database.Tx, height int32) (*wire.BlockHeader, e
return dbFetchHeaderByHash(dbTx, hash)
}
// dbFetchBlockByHash uses an existing database transaction to retrieve the raw
// block for the provided hash, deserialize it, retrieve the appropriate height
// from the index, and return a btcutil.Block with the height set.
func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*btcutil.Block, error) {
// First find the height associated with the provided hash in the index.
blockHeight, err := dbFetchHeightByHash(dbTx, hash)
if err != nil {
return nil, err
}
// Load the raw block bytes from the database.
blockBytes, err := dbTx.FetchBlock(hash)
if err != nil {
return nil, err
}
// Create the encapsulated block and set the height appropriately.
block, err := btcutil.NewBlockFromBytes(blockBytes)
if err != nil {
return nil, err
}
block.SetHeight(blockHeight)
return block, nil
}
// dbFetchBlockByHeight uses an existing database transaction to retrieve the
// raw block for the provided height, deserialize it, and return a btcutil.Block
// dbFetchBlockByNode uses an existing database transaction to retrieve the
// raw block for the provided node, deserialize it, and return a btcutil.Block
// with the height set.
func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error) {
// First find the hash associated with the provided height in the index.
hash, err := dbFetchHashByHeight(dbTx, height)
if err != nil {
return nil, err
}
func dbFetchBlockByNode(dbTx database.Tx, node *blockNode) (*btcutil.Block, error) {
// Load the raw block bytes from the database.
blockBytes, err := dbTx.FetchBlock(hash)
blockBytes, err := dbTx.FetchBlock(&node.hash)
if err != nil {
return nil, err
}
@ -1294,67 +1266,27 @@ func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error
if err != nil {
return nil, err
}
block.SetHeight(height)
block.SetHeight(node.height)
return block, nil
}
// dbMainChainHasBlock uses an existing database transaction to return whether
// or not the main chain contains the block identified by the provided hash.
func dbMainChainHasBlock(dbTx database.Tx, hash *chainhash.Hash) bool {
hashIndex := dbTx.Metadata().Bucket(hashIndexBucketName)
return hashIndex.Get(hash[:]) != nil
}
// MainChainHasBlock returns whether or not the block with the given hash is in
// the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) (bool, error) {
var exists bool
err := b.db.View(func(dbTx database.Tx) error {
exists = dbMainChainHasBlock(dbTx, hash)
return nil
})
return exists, err
}
// BlockHeightByHash returns the height of the block with the given hash in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int32, error) {
var height int32
err := b.db.View(func(dbTx database.Tx) error {
var err error
height, err = dbFetchHeightByHash(dbTx, hash)
return err
})
return height, err
}
// BlockHashByHeight returns the hash of the block at the given height in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, error) {
var hash *chainhash.Hash
err := b.db.View(func(dbTx database.Tx) error {
var err error
hash, err = dbFetchHashByHeight(dbTx, blockHeight)
return err
})
return hash, err
}
// BlockByHeight returns the block at the given height in the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) {
// Lookup the block height in the best chain.
node := b.bestChain.NodeByHeight(blockHeight)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", blockHeight)
return nil, errNotInMainChain(str)
}
// Load the block from the database and return it.
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByHeight(dbTx, blockHeight)
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
@ -1365,70 +1297,20 @@ func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) {
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*btcutil.Block, error) {
// Lookup the block hash in block index and ensure it is in the best
// chain.
node := b.index.LookupNode(hash)
if node == nil || !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", hash)
return nil, errNotInMainChain(str)
}
// Load the block from the database and return it.
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByHash(dbTx, hash)
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
}
// HeightRange returns a range of block hashes for the given start and end
// heights. It is inclusive of the start height and exclusive of the end
// height. The end height will be limited to the current main chain height.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash, error) {
// Ensure requested heights are sane.
if startHeight < 0 {
return nil, fmt.Errorf("start height of fetch range must not "+
"be less than zero - got %d", startHeight)
}
if endHeight < startHeight {
return nil, fmt.Errorf("end height of fetch range must not "+
"be less than the start height - got start %d, end %d",
startHeight, endHeight)
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid the chain lock and a database transaction.
if startHeight == endHeight {
return nil, nil
}
// Grab a lock on the chain to prevent it from changing due to a reorg
// while building the hashes.
b.chainLock.RLock()
defer b.chainLock.RUnlock()
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
latestHeight := b.bestNode.height
if startHeight > latestHeight {
return nil, nil
}
// Limit the ending height to the latest height of the chain.
if endHeight > latestHeight+1 {
endHeight = latestHeight + 1
}
// Fetch as many as are available within the specified range.
var hashList []chainhash.Hash
err := b.db.View(func(dbTx database.Tx) error {
hashes := make([]chainhash.Hash, 0, endHeight-startHeight)
for i := startHeight; i < endHeight; i++ {
hash, err := dbFetchHashByHeight(dbTx, i)
if err != nil {
return err
}
hashes = append(hashes, *hash)
}
// Set the list to be returned to the constructed list.
hashList = hashes
return nil
})
return hashList, err
}

View file

@ -12,6 +12,25 @@ import (
// in a week on average.
const approxNodesPerWeek = 6 * 24 * 7
// 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
}
// chainView provides a flat view of a specific branch of the block chain from
// its tip back to the genesis block and provides various convenience functions
// for comparing chains.

View file

@ -6,10 +6,10 @@ package blockchain
import (
"fmt"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
)
@ -99,7 +99,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
// that is already available.
for i := numCheckpoints - 1; i >= 0; i-- {
node := b.index.LookupNode(checkpoints[i].Hash)
if node == nil || !node.inMainChain {
if node == nil || !b.bestChain.Contains(node) {
continue
}
@ -130,7 +130,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
// 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.bestNode.height < b.nextCheckpoint.Height {
if b.bestChain.Tip().height < b.nextCheckpoint.Height {
return b.checkpointNode, nil
}
@ -201,69 +201,61 @@ func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) {
b.chainLock.RLock()
defer b.chainLock.RUnlock()
var isCandidate bool
err := b.db.View(func(dbTx database.Tx) error {
// A checkpoint must be in the main chain.
blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash())
if err != nil {
// Only return an error if it's not due to the block not
// being in the main chain.
if !isNotInMainChainErr(err) {
return err
}
return nil
}
// A checkpoint must be in the main chain.
node := b.index.LookupNode(block.Hash())
if node == nil || !b.bestChain.Contains(node) {
return false, nil
}
// Ensure the height of the passed block and the entry for the
// block in the main chain match. This should always be the
// case unless the caller provided an invalid block.
if blockHeight != block.Height() {
return fmt.Errorf("passed block height of %d does not "+
"match the main chain height of %d",
block.Height(), blockHeight)
}
// Ensure the height of the passed block and the entry for the block in
// the main chain match. This should always be the case unless the
// caller provided an invalid block.
if node.height != block.Height() {
return false, fmt.Errorf("passed block height of %d does not "+
"match the main chain height of %d", block.Height(),
node.height)
}
// A checkpoint must be at least CheckpointConfirmations blocks
// before the end of the main chain.
mainChainHeight := b.bestNode.height
if blockHeight > (mainChainHeight - CheckpointConfirmations) {
return nil
}
// A checkpoint must be at least CheckpointConfirmations blocks
// before the end of the main chain.
mainChainHeight := b.bestChain.Tip().height
if node.height > (mainChainHeight - CheckpointConfirmations) {
return false, nil
}
// Get the previous block header.
prevHash := &block.MsgBlock().Header.PrevBlock
prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash)
if err != nil {
return err
}
// A checkpoint must be have at least one block after it.
//
// This should always succeed since the check above already made sure it
// is CheckpointConfirmations back, but be safe in case the constant
// changes.
nextNode := b.bestChain.Next(node)
if nextNode == nil {
return false, nil
}
// Get the next block header.
nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1)
if err != nil {
return err
}
// A checkpoint must be have at least one block before it.
if node.parent == nil {
return false, nil
}
// A checkpoint must have timestamps for the block and the
// blocks on either side of it in order (due to the median time
// allowance this is not always the case).
prevTime := prevHeader.Timestamp
curTime := block.MsgBlock().Header.Timestamp
nextTime := nextHeader.Timestamp
if prevTime.After(curTime) || nextTime.Before(curTime) {
return nil
}
// A checkpoint must have timestamps for the block and the blocks on
// either side of it in order (due to the median time allowance this is
// not always the case).
prevTime := time.Unix(node.parent.timestamp, 0)
curTime := block.MsgBlock().Header.Timestamp
nextTime := time.Unix(nextNode.timestamp, 0)
if prevTime.After(curTime) || nextTime.Before(curTime) {
return false, nil
}
// A checkpoint must have transactions that only contain
// standard scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return nil
}
// A checkpoint must have transactions that only contain standard
// scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return false, nil
}
}
// All of the checks passed, so the block is a candidate.
isCandidate = true
return nil
})
return isCandidate, err
// All of the checks passed, so the block is a candidate.
return true, nil
}

View file

@ -262,7 +262,6 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
// Create a genesis block node and block index index populated with it
// for use when creating the fake chain below.
node := newBlockNode(&params.GenesisBlock.Header, 0)
node.inMainChain = true
index := newBlockIndex(nil, params)
index.AddNode(node)
@ -276,9 +275,9 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
maxRetargetTimespan: targetTimespan * adjustmentFactor,
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
index: index,
bestChain: newChainView(node),
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
bestNode: node,
}
}

View file

@ -306,7 +306,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// This function is safe for concurrent access.
func (b *BlockChain) CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) {
b.chainLock.Lock()
difficulty, err := b.calcNextRequiredDifficulty(b.bestNode, timestamp)
difficulty, err := b.calcNextRequiredDifficulty(b.bestChain.Tip(), timestamp)
b.chainLock.Unlock()
return difficulty, err
}

View file

@ -284,15 +284,7 @@ func (m *Manager) Init(chain *blockchain.BlockChain) error {
// Loop until the tip is a block that exists in the main chain.
initialHeight := height
for {
exists, err := chain.MainChainHasBlock(hash)
if err != nil {
return err
}
if exists {
break
}
for !chain.MainChainHasBlock(hash) {
// At this point the index tip is orphaned, so load the
// orphaned block from the database directly and
// disconnect it from the index. The block has to be

View file

@ -265,7 +265,7 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit
// This function is safe for concurrent access.
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
b.chainLock.Lock()
state, err := b.deploymentState(b.bestNode, deploymentID)
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
b.chainLock.Unlock()
return state, err
@ -277,7 +277,7 @@ func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error)
// This function is safe for concurrent access.
func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
b.chainLock.Lock()
state, err := b.deploymentState(b.bestNode, deploymentID)
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
b.chainLock.Unlock()
if err != nil {
return false, err
@ -316,7 +316,7 @@ func (b *BlockChain) initThresholdCaches() error {
// threshold state for each of them. This will ensure the caches are
// populated and any states that needed to be recalculated due to
// definition changes is done now.
prevNode := b.bestNode.parent
prevNode := b.bestChain.Tip().parent
for bit := uint32(0); bit < vbNumBits; bit++ {
checker := bitConditionChecker{bit: bit, chain: b}
cache := &b.warningCaches[bit]
@ -340,13 +340,14 @@ func (b *BlockChain) initThresholdCaches() error {
if b.isCurrent() {
// Warn if a high enough percentage of the last blocks have
// unexpected versions.
if err := b.warnUnknownVersions(b.bestNode); err != nil {
bestNode := b.bestChain.Tip()
if err := b.warnUnknownVersions(bestNode); err != nil {
return err
}
// Warn if any unknown new rules are either about to activate or
// have already been activated.
if err := b.warnUnknownRuleActivations(b.bestNode); err != nil {
if err := b.warnUnknownRuleActivations(bestNode); err != nil {
return err
}
}

View file

@ -1256,7 +1256,7 @@ func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error {
b.chainLock.Lock()
defer b.chainLock.Unlock()
prevNode := b.bestNode
prevNode := b.bestChain.Tip()
newNode := newBlockNode(&block.MsgBlock().Header, prevNode.height+1)
newNode.parent = prevNode
newNode.workSum.Add(prevNode.workSum, newNode.workSum)

View file

@ -224,7 +224,7 @@ func (b *BlockChain) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
// This function is safe for concurrent access.
func (b *BlockChain) CalcNextBlockVersion() (int32, error) {
b.chainLock.Lock()
version, err := b.calcNextBlockVersion(b.bestNode)
version, err := b.calcNextBlockVersion(b.bestChain.Tip())
b.chainLock.Unlock()
return version, err
}