diff --git a/blockchain/accept.go b/blockchain/accept.go index 79adac6521..c228e4d7ac 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -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, diff --git a/blockchain/agendas_test.go b/blockchain/agendas_test.go index 272cf2b131..4b410933b3 100644 --- a/blockchain/agendas_test.go +++ b/blockchain/agendas_test.go @@ -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) } diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index 79500d51b0..3f2239ca51 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -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 diff --git a/blockchain/blockindex_test.go b/blockchain/blockindex_test.go index 57b080eafc..8387bce05e 100644 --- a/blockchain/blockindex_test.go +++ b/blockchain/blockindex_test.go @@ -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 diff --git a/blockchain/blocklocator.go b/blockchain/blocklocator.go deleted file mode 100644 index f58ae9e7fd..0000000000 --- a/blockchain/blocklocator.go +++ /dev/null @@ -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 -} diff --git a/blockchain/chain.go b/blockchain/chain.go index cc8e079531..798ead83a3 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -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 } diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 6b7269fe95..dd0b909021 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -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) - - // 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 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 } + chain.bestChain.SetTip(tip(branch0Nodes)) + + // 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 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), diff --git a/blockchain/chainio.go b/blockchain/chainio.go index b27348f088..a1183d7550 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -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)) diff --git a/blockchain/chainquery.go b/blockchain/chainquery.go index 709fc71e64..335155dab0 100644 --- a/blockchain/chainquery.go +++ b/blockchain/chainquery.go @@ -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 } diff --git a/blockchain/chainview.go b/blockchain/chainview.go index 2ae006be3e..ce730ba06d 100644 --- a/blockchain/chainview.go +++ b/blockchain/chainview.go @@ -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. diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index ed10fec360..97cce475f2 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -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 diff --git a/blockchain/common_test.go b/blockchain/common_test.go index 636ac81266..acc4e67ceb 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -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(¶ms.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), diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index 7113494069..ecc2ae0ed5 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -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 } diff --git a/blockchain/difficulty_test.go b/blockchain/difficulty_test.go index ca664ae250..a272c6248c 100644 --- a/blockchain/difficulty_test.go +++ b/blockchain/difficulty_test.go @@ -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): "+ diff --git a/blockchain/sequencelock.go b/blockchain/sequencelock.go index 3a2ffa62b6..a62deaa9c1 100644 --- a/blockchain/sequencelock.go +++ b/blockchain/sequencelock.go @@ -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 } diff --git a/blockchain/sequencelock_test.go b/blockchain/sequencelock_test.go index b3887e9bd6..56d0e35630 100644 --- a/blockchain/sequencelock_test.go +++ b/blockchain/sequencelock_test.go @@ -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 diff --git a/blockchain/stakeext.go b/blockchain/stakeext.go index 167f9c99e5..769a84eb4a 100644 --- a/blockchain/stakeext.go +++ b/blockchain/stakeext.go @@ -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 diff --git a/blockchain/stakenode.go b/blockchain/stakenode.go index 1703b2fce2..6640d248a0 100644 --- a/blockchain/stakenode.go +++ b/blockchain/stakenode.go @@ -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 diff --git a/blockchain/stakeversion_test.go b/blockchain/stakeversion_test.go index 776838d152..02f9569860 100644 --- a/blockchain/stakeversion_test.go +++ b/blockchain/stakeversion_test.go @@ -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++ { diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index 9657d504e4..965abb4f85 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -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. diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 88549c0f00..25360ed9a4 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -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) diff --git a/blockchain/validate.go b/blockchain/validate.go index 41e50880a7..e34fc23f8d 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -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 { diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index daf54e11d8..af7544e4d1 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -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) diff --git a/blockchain/votebits_test.go b/blockchain/votebits_test.go index 0c8a0c1cf0..7d6ff6d4f5 100644 --- a/blockchain/votebits_test.go +++ b/blockchain/votebits_test.go @@ -115,14 +115,14 @@ func defaultParams(vote chaincfg.Vote) chaincfg.Params { func TestNoQuorum(t *testing.T) { params := defaultParams(pedro) bc := newFakeChain(¶ms) - 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(¶ms) - 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(¶ms) - 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(¶ms) - 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) }