mirror of
https://github.com/LBRYFoundation/lbcd.git
synced 2025-08-23 17:47:24 +00:00
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:
parent
08955805d5
commit
20910511e9
13 changed files with 295 additions and 446 deletions
|
@ -40,11 +40,6 @@ type blockNode struct {
|
||||||
// height is the position in the block chain.
|
// height is the position in the block chain.
|
||||||
height int32
|
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
|
// Some fields from block headers to aid in best chain selection and
|
||||||
// reconstructing headers from memory. These must be treated as
|
// reconstructing headers from memory. These must be treated as
|
||||||
// immutable and are intentionally ordered to avoid padding on 64-bit
|
// immutable and are intentionally ordered to avoid padding on 64-bit
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -24,6 +24,22 @@ const (
|
||||||
maxOrphanBlocks = 100
|
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
|
// 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
|
// is a normal block plus an expiration time to prevent caching the orphan
|
||||||
// forever.
|
// forever.
|
||||||
|
@ -105,10 +121,17 @@ type BlockChain struct {
|
||||||
// fields in this struct below this point.
|
// fields in this struct below this point.
|
||||||
chainLock sync.RWMutex
|
chainLock sync.RWMutex
|
||||||
|
|
||||||
// These fields are related to the memory block index. The best node
|
// These fields are related to the memory block index. They both have
|
||||||
// is protected by the chain lock and the index has its own locks.
|
// their own locks, however they are often also protected by the chain
|
||||||
bestNode *blockNode
|
// 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
|
index *blockIndex
|
||||||
|
bestChain *chainView
|
||||||
|
|
||||||
// These fields are related to handling of orphan blocks. They are
|
// These fields are related to handling of orphan blocks. They are
|
||||||
// protected by a combination of the chain lock and the orphan lock.
|
// 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()
|
b.chainLock.Lock()
|
||||||
defer b.chainLock.Unlock()
|
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
|
// 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
|
// 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
|
// so they are attached in the appropriate order when iterating the list
|
||||||
// later.
|
// later.
|
||||||
ancestor := node
|
forkNode := b.bestChain.FindFork(node)
|
||||||
for ; ancestor.parent != nil; ancestor = ancestor.parent {
|
for n := node; n != nil && n != forkNode; n = n.parent {
|
||||||
if ancestor.inMainChain {
|
attachNodes.PushFront(n)
|
||||||
break
|
|
||||||
}
|
|
||||||
attachNodes.PushFront(ancestor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start from the end of the main chain and work backwards until the
|
// 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
|
// common ancestor adding each block to the list of nodes to detach from
|
||||||
// the main chain.
|
// the main chain.
|
||||||
for n := b.bestNode; n != nil && n.parent != nil; n = n.parent {
|
for n := b.bestChain.Tip(); n != nil && n != forkNode; n = n.parent {
|
||||||
if n.hash.IsEqual(&ancestor.hash) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
detachNodes.PushBack(n)
|
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 {
|
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.
|
// Make sure it's extending the end of the best chain.
|
||||||
prevHash := &block.MsgBlock().Header.PrevBlock
|
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 " +
|
return AssertError("connectBlock must be called with a block " +
|
||||||
"that extends the main chain")
|
"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.
|
// now that the modifications have been committed to the database.
|
||||||
view.commit()
|
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.
|
// 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
|
// Update the state for the best block. Notice how this replaces the
|
||||||
// entire struct instead of updating the existing one. This effectively
|
// 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).
|
// 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 {
|
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.
|
// 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 " +
|
return AssertError("disconnectBlock must be called with the " +
|
||||||
"block at the end of the main chain")
|
"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
|
var prevBlock *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
prevBlock, err = dbFetchBlockByHash(dbTx, &prevNode.hash)
|
prevBlock, err = dbFetchBlockByNode(dbTx, prevNode)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
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.
|
// now that the modifications have been committed to the database.
|
||||||
view.commit()
|
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.
|
// 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
|
// Update the state for the best block. Notice how this replaces the
|
||||||
// entire struct instead of updating the existing one. This effectively
|
// 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
|
// database and using that information to unspend all of the spent txos
|
||||||
// and remove the utxos created by the blocks.
|
// and remove the utxos created by the blocks.
|
||||||
view := NewUtxoViewpoint()
|
view := NewUtxoViewpoint()
|
||||||
view.SetBestHash(&b.bestNode.hash)
|
view.SetBestHash(&b.bestChain.Tip().hash)
|
||||||
for e := detachNodes.Front(); e != nil; e = e.Next() {
|
for e := detachNodes.Front(); e != nil; e = e.Next() {
|
||||||
n := e.Value.(*blockNode)
|
n := e.Value.(*blockNode)
|
||||||
var block *btcutil.Block
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
block, err = dbFetchBlockByHash(dbTx, &n.hash)
|
block, err = dbFetchBlockByNode(dbTx, n)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -860,20 +871,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
|
||||||
n := e.Value.(*blockNode)
|
n := e.Value.(*blockNode)
|
||||||
var block *btcutil.Block
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
// NOTE: This block is not in the main chain, so the
|
var err error
|
||||||
// block has to be loaded directly from the database
|
block, err = dbFetchBlockByNode(dbTx, n)
|
||||||
// instead of using the dbFetchBlockByHash function.
|
|
||||||
blockBytes, err := dbTx.FetchBlock(&n.hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
block, err = btcutil.NewBlockFromBytes(blockBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
block.SetHeight(n.height)
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// view to be valid from the viewpoint of each block being connected or
|
||||||
// disconnected.
|
// disconnected.
|
||||||
view = NewUtxoViewpoint()
|
view = NewUtxoViewpoint()
|
||||||
view.SetBestHash(&b.bestNode.hash)
|
view.SetBestHash(&b.bestChain.Tip().hash)
|
||||||
|
|
||||||
// Disconnect blocks from the main chain.
|
// Disconnect blocks from the main chain.
|
||||||
for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() {
|
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
|
// We are extending the main (best) chain with a new block. This is the
|
||||||
// most common case.
|
// most common case.
|
||||||
parentHash := &block.MsgBlock().Header.PrevBlock
|
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
|
// Perform several checks to verify the block can be connected
|
||||||
// to the main chain without violating any rules and without
|
// to the main chain without violating any rules and without
|
||||||
// actually connecting the block.
|
// 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.index[node.hash] = node
|
||||||
b.index.Unlock()
|
b.index.Unlock()
|
||||||
|
|
||||||
// Mark node as in a side chain.
|
|
||||||
node.inMainChain = false
|
|
||||||
|
|
||||||
// Disconnect it from the parent node when the function returns when
|
// Disconnect it from the parent node when the function returns when
|
||||||
// running in dry run mode.
|
// running in dry run mode.
|
||||||
if dryRun {
|
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
|
// 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.
|
// 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.
|
// Skip Logging info when the dry run flag is set.
|
||||||
if dryRun {
|
if dryRun {
|
||||||
return false, nil
|
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.
|
// Log information about how the block is forking the chain.
|
||||||
|
fork := b.bestChain.FindFork(node)
|
||||||
if fork.hash.IsEqual(parentHash) {
|
if fork.hash.IsEqual(parentHash) {
|
||||||
log.Infof("FORK: Block %v forks the chain at height %d"+
|
log.Infof("FORK: Block %v forks the chain at height %d"+
|
||||||
"/block %v, but does not cause a reorganize",
|
"/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
|
// Not current if the latest main (best) chain height is before the
|
||||||
// latest known good checkpoint (when checkpoints are enabled).
|
// latest known good checkpoint (when checkpoints are enabled).
|
||||||
checkpoint := b.LatestCheckpoint()
|
checkpoint := b.LatestCheckpoint()
|
||||||
if checkpoint != nil && b.bestNode.height < checkpoint.Height {
|
if checkpoint != nil && b.bestChain.Tip().height < checkpoint.Height {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,7 +1127,7 @@ func (b *BlockChain) isCurrent() bool {
|
||||||
// The chain appears to be current if none of the checks reported
|
// The chain appears to be current if none of the checks reported
|
||||||
// otherwise.
|
// otherwise.
|
||||||
minus24Hours := b.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix()
|
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
|
// 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
|
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
|
// 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
|
// connected and disconnected to and from the tip of the main chain for the
|
||||||
// purpose of supporting optional indexes.
|
// purpose of supporting optional indexes.
|
||||||
|
@ -1309,6 +1412,7 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
||||||
index: newBlockIndex(config.DB, params),
|
index: newBlockIndex(config.DB, params),
|
||||||
hashCache: config.HashCache,
|
hashCache: config.HashCache,
|
||||||
|
bestChain: newChainView(nil),
|
||||||
orphans: make(map[chainhash.Hash]*orphanBlock),
|
orphans: make(map[chainhash.Hash]*orphanBlock),
|
||||||
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
|
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
|
||||||
warningCaches: newThresholdCaches(vbNumBits),
|
warningCaches: newThresholdCaches(vbNumBits),
|
||||||
|
@ -1335,9 +1439,10 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bestNode := b.bestChain.Tip()
|
||||||
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
|
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
|
||||||
b.bestNode.height, b.bestNode.hash, b.stateSnapshot.TotalTxns,
|
bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns,
|
||||||
b.bestNode.workSum)
|
bestNode.workSum)
|
||||||
|
|
||||||
return &b, nil
|
return &b, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,14 +125,14 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||||
|
|
||||||
// Generate enough synthetic blocks to activate CSV.
|
// Generate enough synthetic blocks to activate CSV.
|
||||||
chain := newFakeChain(netParams)
|
chain := newFakeChain(netParams)
|
||||||
node := chain.bestNode
|
node := chain.bestChain.Tip()
|
||||||
blockTime := node.Header().Timestamp
|
blockTime := node.Header().Timestamp
|
||||||
numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
|
numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
|
||||||
for i := uint32(0); i < numBlocksToActivate; i++ {
|
for i := uint32(0); i < numBlocksToActivate; i++ {
|
||||||
blockTime = blockTime.Add(time.Second)
|
blockTime = blockTime.Add(time.Second)
|
||||||
node = newFakeNode(node, blockVersion, 0, blockTime)
|
node = newFakeNode(node, blockVersion, 0, blockTime)
|
||||||
chain.index.AddNode(node)
|
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
|
// Create a utxo view with a fake utxo for the inputs used in the
|
||||||
|
|
|
@ -1060,8 +1060,7 @@ func (b *BlockChain) createChainState() error {
|
||||||
genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock)
|
genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock)
|
||||||
header := &genesisBlock.MsgBlock().Header
|
header := &genesisBlock.MsgBlock().Header
|
||||||
node := newBlockNode(header, 0)
|
node := newBlockNode(header, 0)
|
||||||
node.inMainChain = true
|
b.bestChain.SetTip(node)
|
||||||
b.bestNode = node
|
|
||||||
|
|
||||||
// Add the new node to the index which is used for faster lookups.
|
// Add the new node to the index which is used for faster lookups.
|
||||||
b.index.AddNode(node)
|
b.index.AddNode(node)
|
||||||
|
@ -1071,8 +1070,8 @@ func (b *BlockChain) createChainState() error {
|
||||||
numTxns := uint64(len(genesisBlock.MsgBlock().Transactions))
|
numTxns := uint64(len(genesisBlock.MsgBlock().Transactions))
|
||||||
blockSize := uint64(genesisBlock.MsgBlock().SerializeSize())
|
blockSize := uint64(genesisBlock.MsgBlock().SerializeSize())
|
||||||
blockWeight := uint64(GetBlockWeight(genesisBlock))
|
blockWeight := uint64(GetBlockWeight(genesisBlock))
|
||||||
b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight,
|
b.stateSnapshot = newBestState(node, blockSize, blockWeight, numTxns,
|
||||||
numTxns, numTxns, time.Unix(b.bestNode.timestamp, 0))
|
numTxns, time.Unix(node.timestamp, 0))
|
||||||
|
|
||||||
// Create the initial the database chain state including creating the
|
// Create the initial the database chain state including creating the
|
||||||
// necessary index buckets and inserting the genesis block.
|
// 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
|
// Add the genesis block hash to height and height to hash
|
||||||
// mappings to the index.
|
// mappings to the index.
|
||||||
err = dbPutBlockIndex(dbTx, &b.bestNode.hash, b.bestNode.height)
|
err = dbPutBlockIndex(dbTx, &node.hash, node.height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current best chain state into the database.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1154,6 +1153,7 @@ func (b *BlockChain) initChainState() error {
|
||||||
log.Infof("Loading block index. This might take a while...")
|
log.Infof("Loading block index. This might take a while...")
|
||||||
bestHeight := int32(state.height)
|
bestHeight := int32(state.height)
|
||||||
blockNodes := make([]blockNode, bestHeight+1)
|
blockNodes := make([]blockNode, bestHeight+1)
|
||||||
|
var tip *blockNode
|
||||||
for height := int32(0); height <= bestHeight; height++ {
|
for height := int32(0); height <= bestHeight; height++ {
|
||||||
header, err := dbFetchHeaderByHeight(dbTx, height)
|
header, err := dbFetchHeaderByHeight(dbTx, height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1164,25 +1164,29 @@ func (b *BlockChain) initChainState() error {
|
||||||
// and add it to the block index.
|
// and add it to the block index.
|
||||||
node := &blockNodes[height]
|
node := &blockNodes[height]
|
||||||
initBlockNode(node, header, height)
|
initBlockNode(node, header, height)
|
||||||
if parent := b.bestNode; parent != nil {
|
if tip != nil {
|
||||||
node.parent = parent
|
node.parent = tip
|
||||||
node.workSum = node.workSum.Add(parent.workSum,
|
node.workSum = node.workSum.Add(tip.workSum,
|
||||||
node.workSum)
|
node.workSum)
|
||||||
}
|
}
|
||||||
node.inMainChain = true
|
|
||||||
b.index.AddNode(node)
|
b.index.AddNode(node)
|
||||||
|
|
||||||
// This node is now the end of the best chain.
|
// 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
|
// Ensure the resulting best chain matches the stored best state
|
||||||
// hash.
|
// hash and set the best chain view accordingly.
|
||||||
if b.bestNode.hash != state.hash {
|
if tip == nil || tip.hash != state.hash {
|
||||||
|
var tipHash chainhash.Hash
|
||||||
|
if tip != nil {
|
||||||
|
tipHash = tip.hash
|
||||||
|
}
|
||||||
return AssertError(fmt.Sprintf("initChainState: block "+
|
return AssertError(fmt.Sprintf("initChainState: block "+
|
||||||
"index chain tip %s does not match stored "+
|
"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.
|
// Load the raw block bytes for the best block.
|
||||||
blockBytes, err := dbTx.FetchBlock(&state.hash)
|
blockBytes, err := dbTx.FetchBlock(&state.hash)
|
||||||
|
@ -1199,8 +1203,8 @@ func (b *BlockChain) initChainState() error {
|
||||||
blockSize := uint64(len(blockBytes))
|
blockSize := uint64(len(blockBytes))
|
||||||
blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block)))
|
blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block)))
|
||||||
numTxns := uint64(len(block.Transactions))
|
numTxns := uint64(len(block.Transactions))
|
||||||
b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight,
|
b.stateSnapshot = newBestState(tip, blockSize, blockWeight,
|
||||||
numTxns, state.totalTxns, b.bestNode.CalcPastMedianTime())
|
numTxns, state.totalTxns, tip.CalcPastMedianTime())
|
||||||
isStateInitialized = true
|
isStateInitialized = true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1247,44 +1251,12 @@ func dbFetchHeaderByHeight(dbTx database.Tx, height int32) (*wire.BlockHeader, e
|
||||||
return dbFetchHeaderByHash(dbTx, hash)
|
return dbFetchHeaderByHash(dbTx, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbFetchBlockByHash uses an existing database transaction to retrieve the raw
|
// dbFetchBlockByNode uses an existing database transaction to retrieve the
|
||||||
// block for the provided hash, deserialize it, retrieve the appropriate height
|
// raw block for the provided node, deserialize it, and return a btcutil.Block
|
||||||
// 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
|
|
||||||
// with the height set.
|
// with the height set.
|
||||||
func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error) {
|
func dbFetchBlockByNode(dbTx database.Tx, node *blockNode) (*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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the raw block bytes from the database.
|
// Load the raw block bytes from the database.
|
||||||
blockBytes, err := dbTx.FetchBlock(hash)
|
blockBytes, err := dbTx.FetchBlock(&node.hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1294,67 +1266,27 @@ func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
block.SetHeight(height)
|
block.SetHeight(node.height)
|
||||||
|
|
||||||
return block, nil
|
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.
|
// BlockByHeight returns the block at the given height in the main chain.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) {
|
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
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
block, err = dbFetchBlockByHeight(dbTx, blockHeight)
|
block, err = dbFetchBlockByNode(dbTx, node)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return block, err
|
return block, err
|
||||||
|
@ -1365,70 +1297,20 @@ func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) {
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*btcutil.Block, error) {
|
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
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
block, err = dbFetchBlockByHash(dbTx, hash)
|
block, err = dbFetchBlockByNode(dbTx, node)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return block, 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,6 +12,25 @@ import (
|
||||||
// in a week on average.
|
// in a week on average.
|
||||||
const approxNodesPerWeek = 6 * 24 * 7
|
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
|
// 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
|
// its tip back to the genesis block and provides various convenience functions
|
||||||
// for comparing chains.
|
// for comparing chains.
|
||||||
|
|
|
@ -6,10 +6,10 @@ package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
)
|
)
|
||||||
|
@ -99,7 +99,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
|
||||||
// that is already available.
|
// that is already available.
|
||||||
for i := numCheckpoints - 1; i >= 0; i-- {
|
for i := numCheckpoints - 1; i >= 0; i-- {
|
||||||
node := b.index.LookupNode(checkpoints[i].Hash)
|
node := b.index.LookupNode(checkpoints[i].Hash)
|
||||||
if node == nil || !node.inMainChain {
|
if node == nil || !b.bestChain.Contains(node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
|
||||||
// When there is a next checkpoint and the height of the current best
|
// When there is a next checkpoint and the height of the current best
|
||||||
// chain does not exceed it, the current checkpoint lockin is still
|
// chain does not exceed it, the current checkpoint lockin is still
|
||||||
// the latest known checkpoint.
|
// the latest known checkpoint.
|
||||||
if b.bestNode.height < b.nextCheckpoint.Height {
|
if b.bestChain.Tip().height < b.nextCheckpoint.Height {
|
||||||
return b.checkpointNode, nil
|
return b.checkpointNode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,69 +201,61 @@ func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) {
|
||||||
b.chainLock.RLock()
|
b.chainLock.RLock()
|
||||||
defer b.chainLock.RUnlock()
|
defer b.chainLock.RUnlock()
|
||||||
|
|
||||||
var isCandidate bool
|
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
|
||||||
// A checkpoint must be in the main chain.
|
// A checkpoint must be in the main chain.
|
||||||
blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash())
|
node := b.index.LookupNode(block.Hash())
|
||||||
if err != nil {
|
if node == nil || !b.bestChain.Contains(node) {
|
||||||
// Only return an error if it's not due to the block not
|
return false, nil
|
||||||
// being in the main chain.
|
|
||||||
if !isNotInMainChainErr(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the height of the passed block and the entry for the
|
// Ensure the height of the passed block and the entry for the block in
|
||||||
// block in the main chain match. This should always be the
|
// the main chain match. This should always be the case unless the
|
||||||
// case unless the caller provided an invalid block.
|
// caller provided an invalid block.
|
||||||
if blockHeight != block.Height() {
|
if node.height != block.Height() {
|
||||||
return fmt.Errorf("passed block height of %d does not "+
|
return false, fmt.Errorf("passed block height of %d does not "+
|
||||||
"match the main chain height of %d",
|
"match the main chain height of %d", block.Height(),
|
||||||
block.Height(), blockHeight)
|
node.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A checkpoint must be at least CheckpointConfirmations blocks
|
// A checkpoint must be at least CheckpointConfirmations blocks
|
||||||
// before the end of the main chain.
|
// before the end of the main chain.
|
||||||
mainChainHeight := b.bestNode.height
|
mainChainHeight := b.bestChain.Tip().height
|
||||||
if blockHeight > (mainChainHeight - CheckpointConfirmations) {
|
if node.height > (mainChainHeight - CheckpointConfirmations) {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the previous block header.
|
// A checkpoint must be have at least one block after it.
|
||||||
prevHash := &block.MsgBlock().Header.PrevBlock
|
//
|
||||||
prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash)
|
// This should always succeed since the check above already made sure it
|
||||||
if err != nil {
|
// is CheckpointConfirmations back, but be safe in case the constant
|
||||||
return err
|
// changes.
|
||||||
|
nextNode := b.bestChain.Next(node)
|
||||||
|
if nextNode == nil {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next block header.
|
// A checkpoint must be have at least one block before it.
|
||||||
nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1)
|
if node.parent == nil {
|
||||||
if err != nil {
|
return false, nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A checkpoint must have timestamps for the block and the
|
// A checkpoint must have timestamps for the block and the blocks on
|
||||||
// blocks on either side of it in order (due to the median time
|
// either side of it in order (due to the median time allowance this is
|
||||||
// allowance this is not always the case).
|
// not always the case).
|
||||||
prevTime := prevHeader.Timestamp
|
prevTime := time.Unix(node.parent.timestamp, 0)
|
||||||
curTime := block.MsgBlock().Header.Timestamp
|
curTime := block.MsgBlock().Header.Timestamp
|
||||||
nextTime := nextHeader.Timestamp
|
nextTime := time.Unix(nextNode.timestamp, 0)
|
||||||
if prevTime.After(curTime) || nextTime.Before(curTime) {
|
if prevTime.After(curTime) || nextTime.Before(curTime) {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// A checkpoint must have transactions that only contain
|
// A checkpoint must have transactions that only contain standard
|
||||||
// standard scripts.
|
// scripts.
|
||||||
for _, tx := range block.Transactions() {
|
for _, tx := range block.Transactions() {
|
||||||
if isNonstandardTransaction(tx) {
|
if isNonstandardTransaction(tx) {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All of the checks passed, so the block is a candidate.
|
// All of the checks passed, so the block is a candidate.
|
||||||
isCandidate = true
|
return true, nil
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return isCandidate, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,7 +262,6 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
|
||||||
// Create a genesis block node and block index index populated with it
|
// Create a genesis block node and block index index populated with it
|
||||||
// for use when creating the fake chain below.
|
// for use when creating the fake chain below.
|
||||||
node := newBlockNode(¶ms.GenesisBlock.Header, 0)
|
node := newBlockNode(¶ms.GenesisBlock.Header, 0)
|
||||||
node.inMainChain = true
|
|
||||||
index := newBlockIndex(nil, params)
|
index := newBlockIndex(nil, params)
|
||||||
index.AddNode(node)
|
index.AddNode(node)
|
||||||
|
|
||||||
|
@ -276,9 +275,9 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
|
||||||
maxRetargetTimespan: targetTimespan * adjustmentFactor,
|
maxRetargetTimespan: targetTimespan * adjustmentFactor,
|
||||||
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
||||||
index: index,
|
index: index,
|
||||||
|
bestChain: newChainView(node),
|
||||||
warningCaches: newThresholdCaches(vbNumBits),
|
warningCaches: newThresholdCaches(vbNumBits),
|
||||||
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
||||||
bestNode: node,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) {
|
func (b *BlockChain) CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) {
|
||||||
b.chainLock.Lock()
|
b.chainLock.Lock()
|
||||||
difficulty, err := b.calcNextRequiredDifficulty(b.bestNode, timestamp)
|
difficulty, err := b.calcNextRequiredDifficulty(b.bestChain.Tip(), timestamp)
|
||||||
b.chainLock.Unlock()
|
b.chainLock.Unlock()
|
||||||
return difficulty, err
|
return difficulty, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// Loop until the tip is a block that exists in the main chain.
|
||||||
initialHeight := height
|
initialHeight := height
|
||||||
for {
|
for !chain.MainChainHasBlock(hash) {
|
||||||
exists, err := chain.MainChainHasBlock(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point the index tip is orphaned, so load the
|
// At this point the index tip is orphaned, so load the
|
||||||
// orphaned block from the database directly and
|
// orphaned block from the database directly and
|
||||||
// disconnect it from the index. The block has to be
|
// disconnect it from the index. The block has to be
|
||||||
|
|
|
@ -265,7 +265,7 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
||||||
b.chainLock.Lock()
|
b.chainLock.Lock()
|
||||||
state, err := b.deploymentState(b.bestNode, deploymentID)
|
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
|
||||||
b.chainLock.Unlock()
|
b.chainLock.Unlock()
|
||||||
|
|
||||||
return state, err
|
return state, err
|
||||||
|
@ -277,7 +277,7 @@ func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error)
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
|
func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
|
||||||
b.chainLock.Lock()
|
b.chainLock.Lock()
|
||||||
state, err := b.deploymentState(b.bestNode, deploymentID)
|
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
|
||||||
b.chainLock.Unlock()
|
b.chainLock.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -316,7 +316,7 @@ func (b *BlockChain) initThresholdCaches() error {
|
||||||
// threshold state for each of them. This will ensure the caches are
|
// threshold state for each of them. This will ensure the caches are
|
||||||
// populated and any states that needed to be recalculated due to
|
// populated and any states that needed to be recalculated due to
|
||||||
// definition changes is done now.
|
// definition changes is done now.
|
||||||
prevNode := b.bestNode.parent
|
prevNode := b.bestChain.Tip().parent
|
||||||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||||||
checker := bitConditionChecker{bit: bit, chain: b}
|
checker := bitConditionChecker{bit: bit, chain: b}
|
||||||
cache := &b.warningCaches[bit]
|
cache := &b.warningCaches[bit]
|
||||||
|
@ -340,13 +340,14 @@ func (b *BlockChain) initThresholdCaches() error {
|
||||||
if b.isCurrent() {
|
if b.isCurrent() {
|
||||||
// Warn if a high enough percentage of the last blocks have
|
// Warn if a high enough percentage of the last blocks have
|
||||||
// unexpected versions.
|
// unexpected versions.
|
||||||
if err := b.warnUnknownVersions(b.bestNode); err != nil {
|
bestNode := b.bestChain.Tip()
|
||||||
|
if err := b.warnUnknownVersions(bestNode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn if any unknown new rules are either about to activate or
|
// Warn if any unknown new rules are either about to activate or
|
||||||
// have already been activated.
|
// have already been activated.
|
||||||
if err := b.warnUnknownRuleActivations(b.bestNode); err != nil {
|
if err := b.warnUnknownRuleActivations(bestNode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1256,7 +1256,7 @@ func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error {
|
||||||
b.chainLock.Lock()
|
b.chainLock.Lock()
|
||||||
defer b.chainLock.Unlock()
|
defer b.chainLock.Unlock()
|
||||||
|
|
||||||
prevNode := b.bestNode
|
prevNode := b.bestChain.Tip()
|
||||||
newNode := newBlockNode(&block.MsgBlock().Header, prevNode.height+1)
|
newNode := newBlockNode(&block.MsgBlock().Header, prevNode.height+1)
|
||||||
newNode.parent = prevNode
|
newNode.parent = prevNode
|
||||||
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
|
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
|
||||||
|
|
|
@ -224,7 +224,7 @@ func (b *BlockChain) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) CalcNextBlockVersion() (int32, error) {
|
func (b *BlockChain) CalcNextBlockVersion() (int32, error) {
|
||||||
b.chainLock.Lock()
|
b.chainLock.Lock()
|
||||||
version, err := b.calcNextBlockVersion(b.bestNode)
|
version, err := b.calcNextBlockVersion(b.bestChain.Tip())
|
||||||
b.chainLock.Unlock()
|
b.chainLock.Unlock()
|
||||||
return version, err
|
return version, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue