blockchain: Refactor to use new chain view.

This refactors and simplifies the code in blockchain to use the new more
efficient chain views.

An overview of the logic changes are as follows:

- 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
- Remove height map and associated lock in favor of chain view
  - Use chain view NodeByHeight everywhere height map was used
- 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 several functions to use chain view for main chain detection
  - fetchMainChainBlockByNode
  - BlockByHeight
  - MainChainHasBlock
  - findPreviousCheckpoint
  - IsCheckpointCandidate
This commit is contained in:
Dave Collins 2018-07-09 15:00:15 -05:00
parent 6254feaeb8
commit 9ef7db9b23
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
24 changed files with 262 additions and 415 deletions

View File

@ -179,7 +179,7 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags)
// Fetching a stake node could enable a new DoS vector, so restrict
// this only to blocks that are recent in history.
if newNode.height < b.bestNode.height-minMemoryNodes {
if newNode.height < b.bestChain.Tip().height-minMemoryNodes {
newNode.stakeNode, err = b.fetchStakeNode(newNode)
if err != nil {
return 0, err
@ -205,7 +205,7 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags)
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers.
bestHeight := b.bestNode.height
bestHeight := b.bestChain.Tip().height
b.chainLock.Unlock()
b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{
BestHeight: bestHeight,

View File

@ -108,7 +108,7 @@ func testLNFeaturesDeployment(t *testing.T, params *chaincfg.Params, deploymentV
curTimestamp := time.Now()
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
for _, test := range tests {
for i := uint32(0); i < test.numNodes; i++ {
node = newFakeNode(node, int32(deploymentVer),
@ -122,7 +122,7 @@ func testLNFeaturesDeployment(t *testing.T, params *chaincfg.Params, deploymentV
Bits: yesChoice.Bits | 0x01,
})
}
bc.bestNode = node
bc.bestChain.SetTip(node)
curTimestamp = curTimestamp.Add(time.Second)
}

View File

@ -113,11 +113,6 @@ type blockNode struct {
// methods on blockIndex once the node has been added to the index.
status blockStatus
// 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
// stakeNode contains all the consensus information required for the
// staking system. The node also caches information required to add or
// remove stake nodes, so that the stake node itself may be pruneable

View File

@ -34,9 +34,10 @@ func TestBlockNodeHeader(t *testing.T) {
// values.
params := &chaincfg.SimNetParams
bc := newFakeChain(params)
tip := bc.bestChain.Tip()
testHeader := wire.BlockHeader{
Version: 1,
PrevBlock: bc.bestNode.hash,
PrevBlock: tip.hash,
MerkleRoot: *mustParseHash("09876543210987654321"),
StakeRoot: *mustParseHash("43210987654321098765"),
VoteBits: 0x03,
@ -54,7 +55,7 @@ func TestBlockNodeHeader(t *testing.T) {
ExtraData: [32]byte{0xbb},
StakeVersion: 5,
}
node := newBlockNode(&testHeader, bc.bestNode)
node := newBlockNode(&testHeader, tip)
bc.index.AddNode(node)
// Ensure reconstructing the header for the node produces the same header
@ -154,11 +155,11 @@ func TestCalcPastMedianTime(t *testing.T) {
// Create a synthetic chain with the correct number of nodes and the
// timestamps as specified by the test.
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
for _, timestamp := range test.timestamps {
node = newFakeNode(node, 0, 0, 0, time.Unix(timestamp, 0))
bc.index.AddNode(node)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Ensure the median time is the expected value.
@ -177,7 +178,7 @@ func TestCalcPastMedianTime(t *testing.T) {
func TestChainTips(t *testing.T) {
params := &chaincfg.SimNetParams
bc := newFakeChain(params)
genesis := bc.bestNode
genesis := bc.bestChain.NodeByHeight(0)
// Construct a synthetic simnet chain consisting of the following structure.
// 0 -> 1 -> 2 -> 3 -> 4

View File

@ -1,137 +0,0 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2018 The Decred 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/decred/dcrd/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 := int64(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

@ -52,6 +52,22 @@ func panicf(format string, args ...interface{}) {
panic(str)
}
// 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.
@ -124,15 +140,17 @@ type BlockChain struct {
noVerify bool
noCheckpoints bool
// These fields are related to the memory block index. They are
// protected by the chain lock.
bestNode *blockNode
index *blockIndex
// This field allows efficient lookup of nodes in the main chain by
// height. It is protected by the height lock.
heightLock sync.RWMutex
mainNodesByHeight map[int64]*blockNode
// 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.
@ -462,7 +480,7 @@ func (b *BlockChain) addOrphanBlock(block *dcrutil.Block) {
func (b *BlockChain) TipGeneration() ([]chainhash.Hash, error) {
b.chainLock.Lock()
b.index.RLock()
nodes := b.index.chainTips[b.bestNode.height]
nodes := b.index.chainTips[b.bestChain.Tip().height]
nodeHashes := make([]chainhash.Hash, len(nodes))
for i, n := range nodes {
nodeHashes[i] = n.hash
@ -481,6 +499,12 @@ func (b *BlockChain) TipGeneration() ([]chainhash.Hash, error) {
//
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) fetchMainChainBlockByNode(node *blockNode) (*dcrutil.Block, error) {
// Ensure the block is in the main chain.
if !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", node.hash)
return nil, errNotInMainChain(str)
}
b.mainchainBlockCacheLock.RLock()
block, ok := b.mainchainBlockCache[node.hash]
b.mainchainBlockCacheLock.RUnlock()
@ -488,12 +512,6 @@ func (b *BlockChain) fetchMainChainBlockByNode(node *blockNode) (*dcrutil.Block,
return block, nil
}
// Ensure the block in the main chain.
if !node.inMainChain {
str := fmt.Sprintf("block %s is not in the main chain", node.hash)
return nil, errNotInMainChain(str)
}
// Load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
var err error
@ -543,7 +561,7 @@ func (b *BlockChain) fetchBlockByNode(node *blockNode) (*dcrutil.Block, error) {
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) pruneStakeNodes() {
// Find the height to prune to.
pruneToNode := b.bestNode
pruneToNode := b.bestChain.Tip()
for i := int64(0); i < minMemoryStakeNodes-1 && pruneToNode != nil; i++ {
pruneToNode = pruneToNode.parent
}
@ -570,7 +588,7 @@ func (b *BlockChain) pruneStakeNodes() {
node := e.Value.(*blockNode)
// Do not attempt to prune if the node should already have been pruned,
// for example if you're adding an old side chain block.
if node.height > b.bestNode.height-minMemoryNodes {
if node.height > b.bestChain.Tip().height-minMemoryNodes {
node.stakeNode = nil
node.stakeUndoData = nil
node.newTickets = nil
@ -588,8 +606,9 @@ func (b *BlockChain) BestPrevHash() chainhash.Hash {
defer b.chainLock.Unlock()
var prevHash chainhash.Hash
if b.bestNode.parent != nil {
prevHash = b.bestNode.parent.hash
tip := b.bestChain.Tip()
if tip.parent != nil {
prevHash = tip.parent.hash
}
return prevHash
}
@ -642,21 +661,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 = n.parent {
if n.hash == ancestor.hash {
break
}
for n := b.bestChain.Tip(); n != nil && n != forkNode; n = n.parent {
detachNodes.PushBack(n)
}
@ -692,10 +705,11 @@ func (b *BlockChain) pushMainChainBlockCache(block *dcrutil.Block) {
func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block, view *UtxoViewpoint, stxos []spentTxOut) error {
// Make sure it's extending the end of the best chain.
prevHash := block.MsgBlock().Header.PrevBlock
if prevHash != b.bestNode.hash {
tip := b.bestChain.Tip()
if prevHash != tip.hash {
panicf("block %v (height %v) connects to block %v instead of "+
"extending the best chain (hash %v, height %v)", node.hash,
node.height, prevHash, b.bestNode.hash, b.bestNode.height)
node.height, prevHash, tip.hash, tip.height)
}
// Sanity check the correct number of stxos are provided.
@ -791,14 +805,8 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block,
// now that the modifications have been committed to the database.
view.commit()
// Mark block as being in the main chain.
node.inMainChain = true
b.heightLock.Lock()
b.mainNodesByHeight[node.height] = node
b.heightLock.Unlock()
// This node is now the end of the best chain.
b.bestNode = 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
@ -851,11 +859,12 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block,
// Optimization: Before checkpoints, immediately dump the parent's stake
// node because we no longer need it.
if node.height < b.chainParams.LatestCheckpointHeight() {
b.bestNode.parent.stakeNode = nil
b.bestNode.parent.stakeUndoData = nil
b.bestNode.parent.newTickets = nil
b.bestNode.parent.ticketsVoted = nil
b.bestNode.parent.ticketsRevoked = nil
parent := b.bestChain.Tip().parent
parent.stakeNode = nil
parent.stakeUndoData = nil
parent.newTickets = nil
parent.ticketsVoted = nil
parent.ticketsRevoked = nil
}
b.pushMainChainBlockCache(block)
@ -877,10 +886,11 @@ func (b *BlockChain) dropMainChainBlockCache(block *dcrutil.Block) {
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Block, view *UtxoViewpoint) error {
// Make sure the node being disconnected is the end of the best chain.
if node.hash != b.bestNode.hash {
tip := b.bestChain.Tip()
if node.hash != tip.hash {
panicf("block %v (height %v) is not the end of the best chain "+
"(hash %v, height %v)", node.hash, node.height, b.bestNode.hash,
b.bestNode.height)
"(hash %v, height %v)", node.hash, node.height, tip.hash,
tip.height)
}
// Generate a new best state snapshot that will be used to update the
@ -963,14 +973,8 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Blo
// now that the modifications have been committed to the database.
view.commit()
// Mark block as being in a side chain.
node.inMainChain = false
b.heightLock.Lock()
delete(b.mainNodesByHeight, node.height)
b.heightLock.Unlock()
// 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
@ -1047,12 +1051,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
}
// Ensure the provided nodes match the current best chain.
tip := b.bestChain.Tip()
if detachNodes.Len() != 0 {
firstDetachNode := detachNodes.Front().Value.(*blockNode)
if firstDetachNode.hash != b.bestNode.hash {
if firstDetachNode.hash != tip.hash {
panicf("reorganize nodes to detach are not for the current best "+
"chain -- first detach node %v, current chain %v",
&firstDetachNode.hash, &b.bestNode.hash)
&firstDetachNode.hash, &tip.hash)
}
}
@ -1068,8 +1073,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
}
// Track the old and new best chains heads.
oldBest := b.bestNode
newBest := b.bestNode
oldBest := tip
newBest := tip
// All of the blocks to detach and related spend journal entries needed
// to unspend transaction outputs in the blocks being disconnected must
@ -1328,7 +1333,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest
if formerBest.IsEqual(&newBest) {
return fmt.Errorf("can't reorganize to the same block")
}
formerBestNode := b.bestNode
formerBestNode := b.bestChain.Tip()
// We can't reorganize the chain unless our head block matches up with
// b.bestChain.
@ -1351,7 +1356,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest
// Check to make sure our forced-in node validates correctly.
view := NewUtxoViewpoint()
view.SetBestHash(&b.bestNode.parent.hash)
view.SetBestHash(&formerBestNode.parent.hash)
view.SetStakeViewpoint(ViewpointPrevValidInitial)
formerBestBlock, err := b.fetchBlockByNode(formerBestNode)
@ -1443,7 +1448,8 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl
// We are extending the main (best) chain with a new block. This is the
// most common case.
parentHash := &block.MsgBlock().Header.PrevBlock
if *parentHash == b.bestNode.hash {
tip := b.bestChain.Tip()
if *parentHash == tip.hash {
// Skip expensive checks if the block has already been fully
// validated.
fastAdd = fastAdd || b.index.NodeStatus(node).KnownValid()
@ -1506,30 +1512,19 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl
block.Hash())
}
// We're extending (or creating) a side chain which may or may not
// become the main chain.
node.inMainChain = false
// 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 {
// Find the fork point.
fork := node
for ; fork.parent != nil; fork = fork.parent {
if fork.inMainChain {
break
}
}
if node.workSum.Cmp(tip.workSum) <= 0 {
// Log information about how the block is forking the chain.
fork := b.bestChain.FindFork(node)
if fork.hash == *parentHash {
log.Infof("FORK: Block %v (height %v) forks the chain at height "+
"%d/block %v, but does not cause a reorganize",
node.hash, node.height, fork.height, fork.hash)
} else {
log.Infof("EXTEND FORK: Block %v (height %v) extends a side chain "+
"which forks the chain at height %d/block %v",
node.hash, node.height, fork.height, fork.hash)
"which forks the chain at height %d/block %v", node.hash,
node.height, fork.height, fork.hash)
}
forkLen := node.height - fork.height
@ -1567,8 +1562,9 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl
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).
tip := b.bestChain.Tip()
checkpoint := b.latestCheckpoint()
if checkpoint != nil && b.bestNode.height < checkpoint.Height {
if checkpoint != nil && tip.height < checkpoint.Height {
return false
}
@ -1578,7 +1574,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 tip.timestamp >= minus24Hours
}
// IsCurrent returns whether or not the chain believes it is current. Several
@ -1643,7 +1639,7 @@ func (b *BlockChain) maxBlockSize(prevNode *blockNode) (int64, error) {
// This function is safe for concurrent access.
func (b *BlockChain) MaxBlockSize() (int64, error) {
b.chainLock.Lock()
maxSize, err := b.maxBlockSize(b.bestNode)
maxSize, err := b.maxBlockSize(b.bestChain.Tip())
b.chainLock.Unlock()
return maxSize, err
}
@ -1667,9 +1663,7 @@ func (b *BlockChain) HeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error
//
// This function is safe for concurrent access.
func (b *BlockChain) HeaderByHeight(height int64) (wire.BlockHeader, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return wire.BlockHeader{}, errNotInMainChain(str)
@ -1697,9 +1691,8 @@ func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHeight(height int64) (*dcrutil.Block, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
// Lookup the block height in the best chain.
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
@ -1717,10 +1710,7 @@ func (b *BlockChain) BlockByHeight(height int64) (*dcrutil.Block, error) {
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool {
node := b.index.LookupNode(hash)
b.chainLock.RLock()
hasBlock := node != nil && node.inMainChain
b.chainLock.RUnlock()
return hasBlock
return node != nil && b.bestChain.Contains(node)
}
// BlockHeightByHash returns the height of the block with the given hash in the
@ -1729,13 +1719,10 @@ func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool {
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
node := b.index.LookupNode(hash)
b.chainLock.RLock()
if node == nil || !node.inMainChain {
b.chainLock.RUnlock()
if node == nil || !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", hash)
return 0, errNotInMainChain(str)
}
b.chainLock.RUnlock()
return node.height, nil
}
@ -1745,9 +1732,7 @@ func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(height int64) (*chainhash.Hash, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
@ -1776,17 +1761,14 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Hash
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid the chain lock.
// so return now to avoid extra work.
if startHeight == endHeight {
return nil, nil
}
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
b.chainLock.RLock()
tip := b.bestNode
b.chainLock.RUnlock()
latestHeight := tip.height
latestHeight := b.bestChain.Tip().height
if startHeight > latestHeight {
return nil, nil
}
@ -1796,11 +1778,9 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Hash
endHeight = latestHeight + 1
}
// Fetch requested hashes.
// Fetch as many as are available within the specified range.
hashes := make([]chainhash.Hash, endHeight-startHeight)
b.heightLock.RLock()
iterNode := b.mainNodesByHeight[endHeight-1]
b.heightLock.RUnlock()
iterNode := b.bestChain.NodeByHeight(endHeight - 1)
for i := startHeight; i < endHeight; i++ {
// Since the desired result is from the starting node to the
// ending node in forward order, but they are iterated in
@ -1843,12 +1823,10 @@ func (b *BlockChain) locateInventory(locator BlockLocator, hashStop *chainhash.H
// Find the most recent locator block hash in the main chain. In the
// case none of the hashes in the locator are in the main chain, fall
// back to the genesis block.
b.heightLock.RLock()
startNode := b.mainNodesByHeight[0]
b.heightLock.RUnlock()
startNode := b.bestChain.Genesis()
for _, hash := range locator {
node := b.index.LookupNode(hash)
if node != nil && node.inMainChain {
if node != nil && b.bestChain.Contains(node) {
startNode = node
break
}
@ -1857,19 +1835,15 @@ func (b *BlockChain) locateInventory(locator BlockLocator, hashStop *chainhash.H
// Start at the block after the most recently known block. When there
// is no next block it means the most recently known block is the tip of
// the best chain, so there is nothing more to do.
if startNode != nil {
b.heightLock.RLock()
startNode = b.mainNodesByHeight[startNode.height+1]
b.heightLock.RUnlock()
}
startNode = b.bestChain.Next(startNode)
if startNode == nil {
return nil, 0
}
// Calculate how many entries are needed.
total := uint32((b.bestNode.height - startNode.height) + 1)
if stopNode != nil && stopNode.inMainChain && stopNode.height >=
startNode.height {
total := uint32((b.bestChain.Tip().height - startNode.height) + 1)
if stopNode != nil && b.bestChain.Contains(stopNode) &&
stopNode.height >= startNode.height {
total = uint32((stopNode.height - startNode.height) + 1)
}
@ -1898,12 +1872,10 @@ func (b *BlockChain) locateBlocks(locator BlockLocator, hashStop *chainhash.Hash
// Populate and return the found hashes.
hashes := make([]chainhash.Hash, 0, total)
b.heightLock.RLock()
for i := uint32(0); i < total; i++ {
hashes = append(hashes, node.hash)
node = b.mainNodesByHeight[node.height+1]
node = b.bestChain.Next(node)
}
b.heightLock.RUnlock()
return hashes
}
@ -1947,9 +1919,7 @@ func (b *BlockChain) locateHeaders(locator BlockLocator, hashStop *chainhash.Has
headers := make([]wire.BlockHeader, 0, total)
for i := uint32(0); i < total; i++ {
headers = append(headers, node.Header())
b.heightLock.RLock()
node = b.mainNodesByHeight[node.height+1]
b.heightLock.RUnlock()
node = b.bestChain.Next(node)
}
return headers
}
@ -1974,6 +1944,33 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has
return headers
}
// 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
}
// 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.
@ -2079,7 +2076,7 @@ func New(config *Config) (*BlockChain, error) {
sigCache: config.SigCache,
indexManager: config.IndexManager,
index: newBlockIndex(config.DB, params),
mainNodesByHeight: make(map[int64]*blockNode),
bestChain: newChainView(nil),
orphans: make(map[chainhash.Hash]*orphanBlock),
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
mainchainBlockCache: make(map[chainhash.Hash]*dcrutil.Block),
@ -2108,7 +2105,8 @@ func New(config *Config) (*BlockChain, error) {
}
}
b.subsidyCache = NewSubsidyCache(b.bestNode.height, b.chainParams)
tip := b.bestChain.Tip()
b.subsidyCache = NewSubsidyCache(tip.height, b.chainParams)
b.pruner = newChainPruner(&b)
log.Infof("Blockchain database version info: chain: %d, compression: "+
@ -2116,9 +2114,8 @@ func New(config *Config) (*BlockChain, error) {
b.dbInfo.bidxVer)
log.Infof("Chain state: height %d, hash %v, total transactions %d, "+
"work %v, stake version %v", b.bestNode.height, b.bestNode.hash,
b.stateSnapshot.TotalTxns, b.bestNode.workSum,
0)
"work %v, stake version %v", tip.height, tip.hash,
b.stateSnapshot.TotalTxns, tip.workSum, 0)
return &b, nil
}

View File

@ -643,32 +643,25 @@ func TestLocateInventory(t *testing.T) {
// \-> 16a -> 17a
tip := branchTip
chain := newFakeChain(&chaincfg.MainNetParams)
branch0Nodes := chainedFakeNodes(chain.bestNode, 18)
branch0Nodes := chainedFakeNodes(chain.bestChain.Genesis(), 18)
branch1Nodes := chainedFakeNodes(branch0Nodes[14], 2)
for _, node := range branch0Nodes {
chain.index.AddNode(node)
node.inMainChain = true
chain.mainNodesByHeight[node.height] = node
}
for _, node := range branch1Nodes {
chain.index.AddNode(node)
node.inMainChain = false
}
chain.bestNode = tip(branch0Nodes)
chain.bestChain.SetTip(tip(branch0Nodes))
// NOTE: These tests simulate a local and remote node on different parts of
// the chain by treating the branch0Nodes as the local node and the
// branch1Nodes as the remote node.
// Create chain views for different branches of the overall chain to
// simulate a local and remote node on different parts of the chain.
localView := newChainView(tip(branch0Nodes))
remoteView := newChainView(tip(branch1Nodes))
// Create a completely unrelated block chain to simulate a remote node on a
// totally different chain.
unrelatedChain := newFakeChain(&chaincfg.MainNetParams)
unrelatedBranchNodes := chainedFakeNodes(unrelatedChain.bestNode, 5)
for _, node := range unrelatedBranchNodes {
unrelatedChain.index.AddNode(node)
node.inMainChain = true
unrelatedChain.mainNodesByHeight[node.height] = node
}
// Create a chain view for a completely unrelated block chain to
// simulate a remote node on a totally different chain.
unrelatedBranchNodes := chainedFakeNodes(nil, 5)
unrelatedView := newChainView(tip(unrelatedBranchNodes))
tests := []struct {
name string
@ -711,7 +704,7 @@ func TestLocateInventory(t *testing.T) {
// expected result is the blocks after the fork point in
// the main chain and the stop hash has no effect.
name: "remote side chain, unknown stop",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: chainhash.Hash{0x01},
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
@ -722,7 +715,7 @@ func TestLocateInventory(t *testing.T) {
// blocks after the fork point in the main chain and the
// stop hash has no effect.
name: "remote side chain, stop in side",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: tip(branch1Nodes).hash,
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
@ -733,7 +726,7 @@ func TestLocateInventory(t *testing.T) {
// expected result is the blocks after the fork point in
// the main chain and the stop hash has no effect.
name: "remote side chain, stop in main before",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: branch0Nodes[13].hash,
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
@ -745,7 +738,7 @@ func TestLocateInventory(t *testing.T) {
// fork point in the main chain and the stop hash has no
// effect.
name: "remote side chain, stop in main exact",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: branch0Nodes[14].hash,
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
@ -757,7 +750,7 @@ func TestLocateInventory(t *testing.T) {
// point in the main chain up to and including the stop
// hash.
name: "remote side chain, stop in main after",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: branch0Nodes[15].hash,
headers: nodeHeaders(branch0Nodes, 15),
hashes: nodeHashes(branch0Nodes, 15),
@ -769,7 +762,7 @@ func TestLocateInventory(t *testing.T) {
// fork point in the main chain up to and including the
// stop hash.
name: "remote side chain, stop in main after more",
locator: blockLocator(tip(branch1Nodes)),
locator: remoteView.BlockLocator(nil),
hashStop: branch0Nodes[16].hash,
headers: nodeHeaders(branch0Nodes, 15, 16),
hashes: nodeHashes(branch0Nodes, 15, 16),
@ -781,7 +774,7 @@ func TestLocateInventory(t *testing.T) {
// point in the main chain and the stop hash has no
// effect.
name: "remote main chain past, unknown stop",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: chainhash.Hash{0x01},
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
@ -792,7 +785,7 @@ func TestLocateInventory(t *testing.T) {
// result is the blocks after the known point in the
// main chain and the stop hash has no effect.
name: "remote main chain past, stop in side",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: tip(branch1Nodes).hash,
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
@ -804,7 +797,7 @@ func TestLocateInventory(t *testing.T) {
// known point in the main chain and the stop hash has
// no effect.
name: "remote main chain past, stop in main before",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: branch0Nodes[11].hash,
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
@ -816,7 +809,7 @@ func TestLocateInventory(t *testing.T) {
// known point in the main chain and the stop hash has
// no effect.
name: "remote main chain past, stop in main exact",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: branch0Nodes[12].hash,
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
@ -828,7 +821,7 @@ func TestLocateInventory(t *testing.T) {
// the known point in the main chain and the stop hash
// has no effect.
name: "remote main chain past, stop in main after",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: branch0Nodes[13].hash,
headers: nodeHeaders(branch0Nodes, 13),
hashes: nodeHashes(branch0Nodes, 13),
@ -840,7 +833,7 @@ func TestLocateInventory(t *testing.T) {
// after the known point in the main chain and the stop
// hash has no effect.
name: "remote main chain past, stop in main after more",
locator: blockLocator(branch0Nodes[12]),
locator: localView.BlockLocator(branch0Nodes[12]),
hashStop: branch0Nodes[15].hash,
headers: nodeHeaders(branch0Nodes, 13, 14, 15),
hashes: nodeHashes(branch0Nodes, 13, 14, 15),
@ -851,7 +844,7 @@ func TestLocateInventory(t *testing.T) {
// doesn't know about. The expected result is no
// located inventory.
name: "remote main chain same, unknown stop",
locator: blockLocator(tip(branch0Nodes)),
locator: localView.BlockLocator(nil),
hashStop: chainhash.Hash{0x01},
headers: nil,
hashes: nil,
@ -862,7 +855,7 @@ func TestLocateInventory(t *testing.T) {
// the same point. The expected result is no located
// inventory.
name: "remote main chain same, stop same point",
locator: blockLocator(tip(branch0Nodes)),
locator: localView.BlockLocator(nil),
hashStop: tip(branch0Nodes).hash,
headers: nil,
hashes: nil,
@ -875,7 +868,7 @@ func TestLocateInventory(t *testing.T) {
// expected result is the blocks after the genesis
// block.
name: "remote unrelated chain",
locator: blockLocator(tip(unrelatedBranchNodes)),
locator: unrelatedView.BlockLocator(nil),
hashStop: chainhash.Hash{},
headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),

View File

@ -1458,7 +1458,6 @@ func (b *BlockChain) createChainState() error {
header := &genesisBlock.MsgBlock().Header
node := newBlockNode(header, nil)
node.status = statusDataStored | statusValid
node.inMainChain = true
// Initialize the state related to the best block. Since it is the
// genesis block, use its timestamp for the median time.
@ -1721,14 +1720,7 @@ func (b *BlockChain) initChainState(interrupt <-chan struct{}) error {
return AssertError(fmt.Sprintf("initChainState: cannot find "+
"chain tip %s in block index", state.hash))
}
b.bestNode = tip
// Mark all of the nodes from the tip back to the genesis block
// as part of the main chain and build the by height map.
for n := tip; n != nil; n = n.parent {
n.inMainChain = true
b.mainNodesByHeight[n.height] = n
}
b.bestChain.SetTip(tip)
log.Debugf("Block index loaded in %v", time.Since(bidxStart))

View File

@ -51,19 +51,12 @@ func (b *BlockChain) ChainTips() []dcrjson.GetChainTipsResult {
// Generate the results sorted by descending height.
sort.Sort(sort.Reverse(nodeHeightSorter(chainTips)))
results := make([]dcrjson.GetChainTipsResult, len(chainTips))
b.chainLock.RLock()
bestTip := b.bestNode
bestTip := b.bestChain.Tip()
for i, tip := range chainTips {
// Find the fork point in order calculate the branch length later.
fork := tip
for fork != nil && !fork.inMainChain {
fork = fork.parent
}
result := &results[i]
result.Height = tip.height
result.Hash = tip.hash.String()
result.BranchLen = tip.height - fork.height
result.BranchLen = tip.height - b.bestChain.FindFork(tip).height
// Determine the status of the chain tip.
//
@ -99,6 +92,5 @@ func (b *BlockChain) ChainTips() []dcrjson.GetChainTipsResult {
result.Status = "valid-headers"
}
}
b.chainLock.RUnlock()
return results
}

View File

@ -13,6 +13,25 @@ import (
// in a week on average.
const approxNodesPerWeek = 12 * 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

@ -118,7 +118,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
}
@ -149,7 +149,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
}
@ -227,7 +227,7 @@ func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
// A checkpoint must be in the main chain.
node := b.index.LookupNode(block.Hash())
if node == nil || !node.inMainChain {
if node == nil || !b.bestChain.Contains(node) {
return false, nil
}
@ -242,8 +242,7 @@ func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
// A checkpoint must be at least CheckpointConfirmations blocks before
// the end of the main chain.
tip := b.bestNode
if node.height > (tip.height - CheckpointConfirmations) {
if node.height > (b.bestChain.Tip().height - CheckpointConfirmations) {
return false, nil
}
@ -252,9 +251,7 @@ func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
// This should always succeed since the check above already made sure it
// is CheckpointConfirmations back, but be safe in case the constant
// changes.
b.heightLock.RLock()
nextNode := b.mainNodesByHeight[node.height+1]
b.heightLock.RUnlock()
nextNode := b.bestChain.Next(node)
if nextNode == nil {
return false, nil
}
@ -274,8 +271,8 @@ func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
return false, nil
}
// A checkpoint must have transactions that only contain
// standard scripts.
// A checkpoint must have transactions that only contain standard
// scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return false, nil

View File

@ -136,18 +136,14 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
// Create a genesis block node and block index populated with it for use
// when creating the fake chain below.
node := newBlockNode(&params.GenesisBlock.Header, nil)
node.inMainChain = true
index := newBlockIndex(nil, params)
index.AddNode(node)
mainNodesByHeight := make(map[int64]*blockNode)
mainNodesByHeight[node.height] = node
return &BlockChain{
chainParams: params,
deploymentCaches: newThresholdCaches(params),
bestNode: node,
index: index,
mainNodesByHeight: mainNodesByHeight,
bestChain: newChainView(node),
isVoterMajorityVersionCache: make(map[[stakeMajorityCacheKeySize]byte]bool),
isStakeMajorityVersionCache: make(map[[stakeMajorityCacheKeySize]byte]bool),
calcPriorStakeVersionCache: make(map[[chainhash.HashSize]byte]uint32),

View File

@ -426,7 +426,7 @@ func (b *BlockChain) CalcNextRequiredDiffFromNode(hash *chainhash.Hash, timestam
// 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
}
@ -936,7 +936,7 @@ func (b *BlockChain) calcNextRequiredStakeDifficulty(curNode *blockNode) (int64,
// This function is safe for concurrent access.
func (b *BlockChain) CalcNextRequiredStakeDifficulty() (int64, error) {
b.chainLock.Lock()
nextDiff, err := b.calcNextRequiredStakeDifficulty(b.bestNode)
nextDiff, err := b.calcNextRequiredStakeDifficulty(b.bestChain.Tip())
b.chainLock.Unlock()
return nextDiff, err
}
@ -1423,8 +1423,8 @@ func (b *BlockChain) estimateNextStakeDifficulty(curNode *blockNode, newTickets
// This function is safe for concurrent access.
func (b *BlockChain) EstimateNextStakeDifficulty(newTickets int64, useMaxTickets bool) (int64, error) {
b.chainLock.Lock()
estimate, err := b.estimateNextStakeDifficulty(b.bestNode, newTickets,
useMaxTickets)
estimate, err := b.estimateNextStakeDifficulty(b.bestChain.Tip(),
newTickets, useMaxTickets)
b.chainLock.Unlock()
return estimate, err
}

View File

@ -435,7 +435,8 @@ nextTest:
for _, ticketInfo := range test.ticketInfo {
// Ensure the test data isn't faking ticket purchases at
// an incorrect difficulty.
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
tip := bc.bestChain.Tip()
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(tip)
if err != nil {
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
"unexpected error: %v", test.name, err)
@ -451,7 +452,7 @@ nextTest:
for i := uint32(0); i < ticketInfo.numNodes; i++ {
// Make up a header.
nextHeight := uint32(bc.bestNode.height) + 1
nextHeight := uint32(tip.height) + 1
header := &wire.BlockHeader{
Version: 4,
SBits: ticketInfo.stakeDiff,
@ -459,7 +460,7 @@ nextTest:
FreshStake: ticketInfo.newTickets,
PoolSize: poolSize,
}
node := newBlockNode(header, bc.bestNode)
tip = newBlockNode(header, tip)
// Update the pool size for the next header.
// Notice how tickets that mature for this block
@ -478,12 +479,12 @@ nextTest:
// Update the chain to use the new fake node as
// the new best node.
bc.bestNode = node
bc.bestChain.SetTip(tip)
}
}
// Ensure the calculated difficulty matches the expected value.
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestChain.Tip())
if err != nil {
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
"unexpected error: %v", test.name, err)
@ -985,7 +986,8 @@ nextTest:
for _, ticketInfo := range test.ticketInfo {
// Ensure the test data isn't faking ticket purchases at
// an incorrect difficulty.
reqDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
tip := bc.bestChain.Tip()
reqDiff, err := bc.calcNextRequiredStakeDifficultyV2(tip)
if err != nil {
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
"unexpected error: %v", test.name, err)
@ -1001,7 +1003,7 @@ nextTest:
for i := uint32(0); i < ticketInfo.numNodes; i++ {
// Make up a header.
nextHeight := uint32(bc.bestNode.height) + 1
nextHeight := uint32(tip.height) + 1
header := &wire.BlockHeader{
Version: 4,
SBits: ticketInfo.stakeDiff,
@ -1009,7 +1011,7 @@ nextTest:
FreshStake: ticketInfo.newTickets,
PoolSize: poolSize,
}
node := newBlockNode(header, bc.bestNode)
tip = newBlockNode(header, tip)
// Update the pool size for the next header.
// Notice how tickets that mature for this block
@ -1028,12 +1030,12 @@ nextTest:
// Update the chain to use the new fake node as
// the new best node.
bc.bestNode = node
bc.bestChain.SetTip(tip)
}
}
// Ensure the calculated difficulty matches the expected value.
gotDiff, err := bc.estimateNextStakeDifficultyV2(bc.bestNode,
gotDiff, err := bc.estimateNextStakeDifficultyV2(bc.bestChain.Tip(),
test.newTickets, test.useMaxTickets)
if err != nil {
t.Errorf("estimateNextStakeDifficultyV2 (%s): "+

View File

@ -150,7 +150,7 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *dcrutil.Tx, view *Utx
// This function is safe for concurrent access.
func (b *BlockChain) CalcSequenceLock(tx *dcrutil.Tx, view *UtxoViewpoint) (*SequenceLock, error) {
b.chainLock.Lock()
seqLock, err := b.calcSequenceLock(b.bestNode, tx, view, true)
seqLock, err := b.calcSequenceLock(b.bestChain.Tip(), tx, view, true)
b.chainLock.Unlock()
return seqLock, err
}

View File

@ -36,13 +36,13 @@ func TestCalcSequenceLock(t *testing.T) {
numBlocks := uint32(20)
params := &chaincfg.SimNetParams
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
blockTime := time.Unix(node.timestamp, 0)
for i := uint32(0); i < numBlocks; i++ {
blockTime = blockTime.Add(time.Second)
node = newFakeNode(node, 1, 1, 0, blockTime)
bc.index.AddNode(node)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Create a utxo view with a fake utxo for the inputs used in the

View File

@ -23,8 +23,9 @@ func (b *BlockChain) NextLotteryData() ([]chainhash.Hash, int, [6]byte, error) {
b.chainLock.RLock()
defer b.chainLock.RUnlock()
return b.bestNode.stakeNode.Winners(), b.bestNode.stakeNode.PoolSize(),
b.bestNode.stakeNode.FinalState(), nil
tipStakeNode := b.bestChain.Tip().stakeNode
return tipStakeNode.Winners(), tipStakeNode.PoolSize(),
tipStakeNode.FinalState(), nil
}
// lotteryDataForNode is a helper function that returns winning tickets
@ -41,8 +42,7 @@ func (b *BlockChain) lotteryDataForNode(node *blockNode) ([]chainhash.Hash, int,
return []chainhash.Hash{}, 0, [6]byte{}, err
}
return stakeNode.Winners(), b.bestNode.stakeNode.PoolSize(),
b.bestNode.stakeNode.FinalState(), nil
return stakeNode.Winners(), stakeNode.PoolSize(), stakeNode.FinalState(), nil
}
// lotteryDataForBlock takes a node block hash and returns the next tickets
@ -85,7 +85,7 @@ func (b *BlockChain) LotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash
// This function is NOT safe for concurrent access.
func (b *BlockChain) LiveTickets() ([]chainhash.Hash, error) {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
return sn.LiveTickets(), nil
@ -96,7 +96,7 @@ func (b *BlockChain) LiveTickets() ([]chainhash.Hash, error) {
// This function is NOT safe for concurrent access.
func (b *BlockChain) MissedTickets() ([]chainhash.Hash, error) {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
return sn.MissedTickets(), nil
@ -108,7 +108,7 @@ func (b *BlockChain) MissedTickets() ([]chainhash.Hash, error) {
// This function is safe for concurrent access.
func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Hash, error) {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
tickets := sn.LiveTickets()
@ -146,7 +146,7 @@ func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Ha
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTicket(hash chainhash.Hash) bool {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
return sn.ExistsLiveTicket(hash)
@ -158,7 +158,7 @@ func (b *BlockChain) CheckLiveTicket(hash chainhash.Hash) bool {
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTickets(hashes []chainhash.Hash) []bool {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
existsSlice := make([]bool, len(hashes))
@ -175,7 +175,7 @@ func (b *BlockChain) CheckLiveTickets(hashes []chainhash.Hash) []bool {
// This function is safe for concurrent access.
func (b *BlockChain) CheckMissedTickets(hashes []chainhash.Hash) []bool {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
existsSlice := make([]bool, len(hashes))
@ -191,7 +191,7 @@ func (b *BlockChain) CheckMissedTickets(hashes []chainhash.Hash) []bool {
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTicket(hash chainhash.Hash) bool {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
return sn.ExistsExpiredTicket(hash)
@ -203,7 +203,7 @@ func (b *BlockChain) CheckExpiredTicket(hash chainhash.Hash) bool {
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTickets(hashes []chainhash.Hash) []bool {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
existsSlice := make([]bool, len(hashes))
@ -222,7 +222,7 @@ func (b *BlockChain) CheckExpiredTickets(hashes []chainhash.Hash) []bool {
// the asked for transactions.
func (b *BlockChain) TicketPoolValue() (dcrutil.Amount, error) {
b.chainLock.RLock()
sn := b.bestNode.stakeNode
sn := b.bestChain.Tip().stakeNode
b.chainLock.RUnlock()
var amt int64

View File

@ -106,7 +106,7 @@ func (b *BlockChain) fetchStakeNode(node *blockNode) (*stake.Node, error) {
// always be filled in, so assume it is safe to begin working
// backwards from there.
detachNodes, attachNodes := b.getReorganizeNodes(node)
current := b.bestNode
current := b.bestChain.Tip()
// Move backwards through the main chain, undoing the ticket
// treaps for each block. The database is passed because the

View File

@ -99,10 +99,10 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Generate enough nodes to reach stake validation height with stake
// versions set to 0.
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
for i := int64(1); i <= svh; i++ {
node = newFakeNode(node, 0, 0, 0, time.Now())
bc.bestNode = node
bc.bestChain.SetTip(node)
}
if node.height != svh {
t.Fatalf("invalid height got %v expected %v", node.height, svh)
@ -115,7 +115,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set vote and stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 2, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Versions 0 and 2 should now be considered the majority version, but
@ -137,7 +137,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set vote and stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Versions up to and including v4 should now be considered the majority
@ -160,7 +160,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set vote and stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 2, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Versions up to and including v4 should still be considered the
@ -185,7 +185,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set vote and stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 5, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
// Versions up to and including v5 should now be considered the majority
@ -208,7 +208,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set vote and stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
@ -234,7 +234,7 @@ func TestCalcStakeVersionCorners(t *testing.T) {
// Set stake versions.
node = newFakeNode(node, 3, sv, 0, time.Now())
appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
@ -296,15 +296,15 @@ func TestCalcStakeVersion(t *testing.T) {
for _, test := range tests {
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
for i := int64(1); i <= test.numNodes; i++ {
node = newFakeNode(node, 1, 0, 0, time.Now())
test.set(node)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
version := bc.calcStakeVersion(bc.bestNode)
version := bc.calcStakeVersion(bc.bestChain.Tip())
if version != test.expectVersion {
t.Fatalf("version mismatch: got %v expected %v",
version, test.expectVersion)
@ -625,7 +625,7 @@ func TestIsStakeMajorityVersion(t *testing.T) {
for _, test := range tests {
// Create new BlockChain in order to blow away cache.
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
ticketCount = 0
@ -642,7 +642,7 @@ func TestIsStakeMajorityVersion(t *testing.T) {
test.startStakeVersion, 0)
}
bc.bestNode = node
bc.bestChain.SetTip(node)
}
res := bc.isVoterMajorityVersion(test.expectedStakeVersion, node)
@ -695,7 +695,7 @@ func TestLarge(t *testing.T) {
for _, test := range tests {
// Create new BlockChain in order to blow away cache.
bc := newFakeChain(params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
for i := int64(1); i <= test.numNodes; i++ {
@ -705,7 +705,7 @@ func TestLarge(t *testing.T) {
// Override version.
appendFakeVotes(node, params.TicketsPerBlock,
test.startStakeVersion, 0)
bc.bestNode = node
bc.bestChain.SetTip(node)
}
for x := 0; x < numRuns; x++ {

View File

@ -523,7 +523,7 @@ func (b *BlockChain) isLNFeaturesAgendaActive(prevNode *blockNode) (bool, error)
// This function is safe for concurrent access.
func (b *BlockChain) IsLNFeaturesAgendaActive() (bool, error) {
b.chainLock.Lock()
isActive, err := b.isLNFeaturesAgendaActive(b.bestNode)
isActive, err := b.isLNFeaturesAgendaActive(b.bestChain.Tip())
b.chainLock.Unlock()
return isActive, err
}
@ -593,7 +593,7 @@ func (b *BlockChain) GetVoteCounts(version uint32, deploymentID string) (VoteCou
deployment := &b.chainParams.Deployments[version][k]
if deployment.Vote.Id == deploymentID {
b.chainLock.Lock()
counts, err := b.getVoteCounts(b.bestNode, version, deployment)
counts, err := b.getVoteCounts(b.bestChain.Tip(), version, deployment)
b.chainLock.Unlock()
return counts, err
}
@ -608,7 +608,7 @@ func (b *BlockChain) GetVoteCounts(version uint32, deploymentID string) (VoteCou
func (b *BlockChain) CountVoteVersion(version uint32) (uint32, error) {
b.chainLock.Lock()
defer b.chainLock.Unlock()
countNode := b.bestNode
countNode := b.bestChain.Tip()
// Don't try to count votes before the stake validation height since there
// could not possibly have been any.

View File

@ -1084,7 +1084,7 @@ func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoi
// can't possibly be any details about it. This is also necessary
// because the code below requires the parent block and the genesis
// block doesn't have one.
tip := b.bestNode
tip := b.bestChain.Tip()
view := NewUtxoViewpoint()
if tip.height == 0 {
view.SetBestHash(&tip.hash)

View File

@ -2564,7 +2564,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
// The block template must build off the current tip of the main chain
// or its parent.
tip := b.bestNode
tip := b.bestChain.Tip()
var prevNode *blockNode
parentHash := block.MsgBlock().Header.PrevBlock
if parentHash == tip.hash {

View File

@ -87,7 +87,7 @@ func TestBlockchainSpendJournal(t *testing.T) {
// Loop through all of the blocks and ensure the number of spent outputs
// matches up with the information loaded from the spend journal.
err = chain.db.View(func(dbTx database.Tx) error {
parentNode := chain.bestNode.parent
parentNode := chain.bestChain.NodeByHeight(1)
if parentNode == nil {
str := fmt.Sprintf("no block at height %d exists", 1)
return errNotInMainChain(str)
@ -97,8 +97,8 @@ func TestBlockchainSpendJournal(t *testing.T) {
return err
}
for i := int64(2); i <= chain.bestNode.height; i++ {
node := chain.bestNode.Ancestor(i)
for i := int64(2); i <= chain.bestChain.Tip().height; i++ {
node := chain.bestChain.NodeByHeight(i)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", i)
return errNotInMainChain(str)

View File

@ -115,14 +115,14 @@ func defaultParams(vote chaincfg.Vote) chaincfg.Params {
func TestNoQuorum(t *testing.T) {
params := defaultParams(pedro)
bc := newFakeChain(&params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = posVersion
// get to svi
curTimestamp := time.Now()
for i := uint32(0); i < uint32(params.StakeValidationHeight); i++ {
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -143,7 +143,7 @@ func TestNoQuorum(t *testing.T) {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
appendFakeVotes(node, params.TicketsPerBlock, posVersion, 0x01)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -174,7 +174,7 @@ func TestNoQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -211,7 +211,7 @@ func TestNoQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -248,7 +248,7 @@ func TestNoQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -271,7 +271,7 @@ func TestNoQuorum(t *testing.T) {
func TestYesQuorum(t *testing.T) {
params := defaultParams(pedro)
bc := newFakeChain(&params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = posVersion
// get to svi
@ -279,7 +279,7 @@ func TestYesQuorum(t *testing.T) {
for i := uint32(0); i < uint32(params.StakeValidationHeight); i++ {
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -300,7 +300,7 @@ func TestYesQuorum(t *testing.T) {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
appendFakeVotes(node, params.TicketsPerBlock, posVersion, 0x01)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -331,7 +331,7 @@ func TestYesQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -368,7 +368,7 @@ func TestYesQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -405,7 +405,7 @@ func TestYesQuorum(t *testing.T) {
voteCount++
}
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -1446,7 +1446,7 @@ func TestVoting(t *testing.T) {
params = defaultParams(test.vote)
// We have to reset the cache for every test.
bc := newFakeChain(&params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
t.Logf("running: %v", test.name)
@ -1474,7 +1474,7 @@ func TestVoting(t *testing.T) {
appendFakeVotes(node, params.TicketsPerBlock,
vote.Version, vote.Bits)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
@ -1627,7 +1627,7 @@ func TestParallelVoting(t *testing.T) {
params = defaultParallelParams()
// We have to reset the cache for every test.
bc := newFakeChain(&params)
node := bc.bestNode
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
curTimestamp := time.Now()
@ -1640,7 +1640,7 @@ func TestParallelVoting(t *testing.T) {
appendFakeVotes(node, params.TicketsPerBlock,
vote.Version, vote.Bits)
bc.bestNode = node
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}