diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index a804c7fd..d65619eb 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -40,11 +40,6 @@ type blockNode struct { // height is the position in the block chain. height int32 - // inMainChain denotes whether the block node is currently on the - // the main chain or not. This is used to help find the common - // ancestor when switching chains. - inMainChain bool - // Some fields from block headers to aid in best chain selection and // reconstructing headers from memory. These must be treated as // immutable and are intentionally ordered to avoid padding on 64-bit diff --git a/blockchain/blocklocator.go b/blockchain/blocklocator.go deleted file mode 100644 index 4a857b98..00000000 --- a/blockchain/blocklocator.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2013-2017 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package blockchain - -import ( - "github.com/btcsuite/btcd/chaincfg/chainhash" -) - -// log2FloorMasks defines the masks to use when quickly calculating -// floor(log2(x)) in a constant log2(32) = 5 steps, where x is a uint32, using -// shifts. They are derived from (2^(2^x) - 1) * (2^(2^x)), for x in 4..0. -var log2FloorMasks = []uint32{0xffff0000, 0xff00, 0xf0, 0xc, 0x2} - -// fastLog2Floor calculates and returns floor(log2(x)) in a constant 5 steps. -func fastLog2Floor(n uint32) uint8 { - rv := uint8(0) - exponent := uint8(16) - for i := 0; i < 5; i++ { - if n&log2FloorMasks[i] != 0 { - rv += exponent - n >>= exponent - } - exponent >>= 1 - } - return rv -} - -// BlockLocator is used to help locate a specific block. The algorithm for -// building the block locator is to add the hashes in reverse order until -// the genesis block is reached. In order to keep the list of locator hashes -// to a reasonable number of entries, first the most recent 12 block hashes are -// added, then the step is doubled each loop iteration to exponentially decrease -// the number of hashes as a function of the distance from the block being -// located. -// -// For example, assume you have a block chain with a side chain as depicted -// below: -// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 -// \-> 16a -> 17a -// -// The block locator for block 17a would be the hashes of blocks: -// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis] -type BlockLocator []*chainhash.Hash - -// blockLocator returns a block locator for the passed block node. -// -// See BlockLocator for details on the algorithm used to create a block locator. -// -// This function MUST be called with the block index lock held (for reads). -func blockLocator(node *blockNode) BlockLocator { - if node == nil { - return nil - } - - // Calculate the max number of entries that will ultimately be in the - // block locator. See the description of the algorithm for how these - // numbers are derived. - var maxEntries uint8 - if node.height <= 12 { - maxEntries = uint8(node.height) + 1 - } else { - // Requested hash itself + previous 10 entries + genesis block. - // Then floor(log2(height-10)) entries for the skip portion. - adjustedHeight := uint32(node.height) - 10 - maxEntries = 12 + fastLog2Floor(adjustedHeight) - } - locator := make(BlockLocator, 0, maxEntries) - - step := int32(1) - for node != nil { - locator = append(locator, &node.hash) - - // Nothing more to add once the genesis block has been added. - if node.height == 0 { - break - } - - // Calculate height of previous node to include ensuring the - // final node is the genesis block. - height := node.height - step - if height < 0 { - height = 0 - } - - // Walk backwards through the nodes to the correct ancestor. - node = node.Ancestor(height) - - // Once 11 entries have been included, start doubling the - // distance between included hashes. - if len(locator) > 10 { - step *= 2 - } - } - - return locator -} - -// BlockLocatorFromHash returns a block locator for the passed block hash. -// See BlockLocator for details on the algorithm used to create a block locator. -// -// In addition to the general algorithm referenced above, there are a couple of -// special cases which are handled: -// -// - If the genesis hash is passed, there are no previous hashes to add and -// therefore the block locator will only consist of the genesis hash -// - If the passed hash is not currently known, the block locator will be for -// the latest known tip of the main (best) chain. -// -// This function is safe for concurrent access. -func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { - b.chainLock.RLock() - b.index.RLock() - node, exists := b.index.index[*hash] - if !exists { - node = b.bestNode - } - locator := blockLocator(node) - b.index.RUnlock() - b.chainLock.RUnlock() - return locator -} - -// LatestBlockLocator returns a block locator for the latest known tip of the -// main (best) chain. -// -// This function is safe for concurrent access. -func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) { - b.chainLock.RLock() - b.index.RLock() - locator := blockLocator(b.bestNode) - b.index.RUnlock() - b.chainLock.RUnlock() - return locator, nil -} diff --git a/blockchain/chain.go b/blockchain/chain.go index 5826d100..b5fb6f89 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -24,6 +24,22 @@ const ( maxOrphanBlocks = 100 ) +// BlockLocator is used to help locate a specific block. The algorithm for +// building the block locator is to add the hashes in reverse order until +// the genesis block is reached. In order to keep the list of locator hashes +// to a reasonable number of entries, first the most recent previous 12 block +// hashes are added, then the step is doubled each loop iteration to +// exponentially decrease the number of hashes as a function of the distance +// from the block being located. +// +// For example, assume a block chain with a side chain as depicted below: +// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 +// \-> 16a -> 17a +// +// The block locator for block 17a would be the hashes of blocks: +// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis] +type BlockLocator []*chainhash.Hash + // orphanBlock represents a block that we don't yet have the parent for. It // is a normal block plus an expiration time to prevent caching the orphan // forever. @@ -105,10 +121,17 @@ type BlockChain struct { // fields in this struct below this point. chainLock sync.RWMutex - // These fields are related to the memory block index. The best node - // is protected by the chain lock and the index has its own locks. - bestNode *blockNode - index *blockIndex + // These fields are related to the memory block index. They both have + // their own locks, however they are often also protected by the chain + // lock to help prevent logic races when blocks are being processed. + // + // index houses the entire block index in memory. The block index is + // a tree-shaped structure. + // + // bestChain tracks the current active chain by making use of an + // efficient chain view into the block index. + index *blockIndex + bestChain *chainView // These fields are related to handling of orphan blocks. They are // protected by a combination of the chain lock and the orphan lock. @@ -336,7 +359,7 @@ func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint, m b.chainLock.Lock() defer b.chainLock.Unlock() - return b.calcSequenceLock(b.bestNode, tx, utxoView, mempool) + return b.calcSequenceLock(b.bestChain.Tip(), tx, utxoView, mempool) } // calcSequenceLock computes the relative lock-times for the passed @@ -493,21 +516,15 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List // to attach to the main tree. Push them onto the list in reverse order // so they are attached in the appropriate order when iterating the list // later. - ancestor := node - for ; ancestor.parent != nil; ancestor = ancestor.parent { - if ancestor.inMainChain { - break - } - attachNodes.PushFront(ancestor) + forkNode := b.bestChain.FindFork(node) + for n := node; n != nil && n != forkNode; n = n.parent { + attachNodes.PushFront(n) } // Start from the end of the main chain and work backwards until the // common ancestor adding each block to the list of nodes to detach from // the main chain. - for n := b.bestNode; n != nil && n.parent != nil; n = n.parent { - if n.hash.IsEqual(&ancestor.hash) { - break - } + for n := b.bestChain.Tip(); n != nil && n != forkNode; n = n.parent { detachNodes.PushBack(n) } @@ -542,7 +559,7 @@ func dbMaybeStoreBlock(dbTx database.Tx, block *btcutil.Block) error { func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos []spentTxOut) error { // Make sure it's extending the end of the best chain. prevHash := &block.MsgBlock().Header.PrevBlock - if !prevHash.IsEqual(&b.bestNode.hash) { + if !prevHash.IsEqual(&b.bestChain.Tip().hash) { return AssertError("connectBlock must be called with a block " + "that extends the main chain") } @@ -630,12 +647,9 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U // now that the modifications have been committed to the database. view.commit() - // Add the new node to the memory main chain indices for faster lookups. - node.inMainChain = true - b.index.AddNode(node) - // This node is now the end of the best chain. - b.bestNode = node + b.index.AddNode(node) + b.bestChain.SetTip(node) // Update the state for the best block. Notice how this replaces the // entire struct instead of updating the existing one. This effectively @@ -662,7 +676,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error { // Make sure the node being disconnected is the end of the best chain. - if !node.hash.IsEqual(&b.bestNode.hash) { + if !node.hash.IsEqual(&b.bestChain.Tip().hash) { return AssertError("disconnectBlock must be called with the " + "block at the end of the main chain") } @@ -672,7 +686,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view var prevBlock *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error - prevBlock, err = dbFetchBlockByHash(dbTx, &prevNode.hash) + prevBlock, err = dbFetchBlockByNode(dbTx, prevNode) return err }) if err != nil { @@ -740,11 +754,8 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view // now that the modifications have been committed to the database. view.commit() - // Mark block as being in a side chain. - node.inMainChain = false - // This node's parent is now the end of the best chain. - b.bestNode = node.parent + b.bestChain.SetTip(node.parent) // Update the state for the best block. Notice how this replaces the // entire struct instead of updating the existing one. This effectively @@ -803,13 +814,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags // database and using that information to unspend all of the spent txos // and remove the utxos created by the blocks. view := NewUtxoViewpoint() - view.SetBestHash(&b.bestNode.hash) + view.SetBestHash(&b.bestChain.Tip().hash) for e := detachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error - block, err = dbFetchBlockByHash(dbTx, &n.hash) + block, err = dbFetchBlockByNode(dbTx, n) return err }) if err != nil { @@ -860,20 +871,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags n := e.Value.(*blockNode) var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { - // NOTE: This block is not in the main chain, so the - // block has to be loaded directly from the database - // instead of using the dbFetchBlockByHash function. - blockBytes, err := dbTx.FetchBlock(&n.hash) - if err != nil { - return err - } - - block, err = btcutil.NewBlockFromBytes(blockBytes) - if err != nil { - return err - } - block.SetHeight(n.height) - return nil + var err error + block, err = dbFetchBlockByNode(dbTx, n) + return err }) if err != nil { return err @@ -904,7 +904,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags // view to be valid from the viewpoint of each block being connected or // disconnected. view = NewUtxoViewpoint() - view.SetBestHash(&b.bestNode.hash) + view.SetBestHash(&b.bestChain.Tip().hash) // Disconnect blocks from the main chain. for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() { @@ -997,7 +997,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // We are extending the main (best) chain with a new block. This is the // most common case. parentHash := &block.MsgBlock().Header.PrevBlock - if parentHash.IsEqual(&b.bestNode.hash) { + if parentHash.IsEqual(&b.bestChain.Tip().hash) { // Perform several checks to verify the block can be connected // to the main chain without violating any rules and without // actually connecting the block. @@ -1051,9 +1051,6 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla b.index.index[node.hash] = node b.index.Unlock() - // Mark node as in a side chain. - node.inMainChain = false - // Disconnect it from the parent node when the function returns when // running in dry run mode. if dryRun { @@ -1066,21 +1063,14 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // We're extending (or creating) a side chain, but the cumulative // work for this new side chain is not enough to make it the new chain. - if node.workSum.Cmp(b.bestNode.workSum) <= 0 { + if node.workSum.Cmp(b.bestChain.Tip().workSum) <= 0 { // Skip Logging info when the dry run flag is set. if dryRun { return false, nil } - // Find the fork point. - fork := node - for ; fork.parent != nil; fork = fork.parent { - if fork.inMainChain { - break - } - } - // Log information about how the block is forking the chain. + fork := b.bestChain.FindFork(node) if fork.hash.IsEqual(parentHash) { log.Infof("FORK: Block %v forks the chain at height %d"+ "/block %v, but does not cause a reorganize", @@ -1127,7 +1117,7 @@ func (b *BlockChain) isCurrent() bool { // Not current if the latest main (best) chain height is before the // latest known good checkpoint (when checkpoints are enabled). checkpoint := b.LatestCheckpoint() - if checkpoint != nil && b.bestNode.height < checkpoint.Height { + if checkpoint != nil && b.bestChain.Tip().height < checkpoint.Height { return false } @@ -1137,7 +1127,7 @@ func (b *BlockChain) isCurrent() bool { // The chain appears to be current if none of the checks reported // otherwise. minus24Hours := b.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix() - return b.bestNode.timestamp >= minus24Hours + return b.bestChain.Tip().timestamp >= minus24Hours } // IsCurrent returns whether or not the chain believes it is current. Several @@ -1187,6 +1177,119 @@ func (b *BlockChain) FetchHeader(hash *chainhash.Hash) (wire.BlockHeader, error) return *header, nil } +// MainChainHasBlock returns whether or not the block with the given hash is in +// the main chain. +// +// This function is safe for concurrent access. +func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool { + node := b.index.LookupNode(hash) + return node != nil && b.bestChain.Contains(node) +} + +// BlockLocatorFromHash returns a block locator for the passed block hash. +// See BlockLocator for details on the algorithm used to create a block locator. +// +// In addition to the general algorithm referenced above, this function will +// return the block locator for the latest known tip of the main (best) chain if +// the passed hash is not currently known. +// +// This function is safe for concurrent access. +func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { + b.chainLock.RLock() + node := b.index.LookupNode(hash) + locator := b.bestChain.blockLocator(node) + b.chainLock.RUnlock() + return locator +} + +// LatestBlockLocator returns a block locator for the latest known tip of the +// main (best) chain. +// +// This function is safe for concurrent access. +func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) { + b.chainLock.RLock() + locator := b.bestChain.BlockLocator(nil) + b.chainLock.RUnlock() + return locator, nil +} + +// BlockHeightByHash returns the height of the block with the given hash in the +// main chain. +// +// This function is safe for concurrent access. +func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int32, error) { + node := b.index.LookupNode(hash) + if node == nil || !b.bestChain.Contains(node) { + str := fmt.Sprintf("block %s is not in the main chain", hash) + return 0, errNotInMainChain(str) + } + + return node.height, nil +} + +// BlockHashByHeight returns the hash of the block at the given height in the +// main chain. +// +// This function is safe for concurrent access. +func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, error) { + node := b.bestChain.NodeByHeight(blockHeight) + if node == nil { + str := fmt.Sprintf("no block at height %d exists", blockHeight) + return nil, errNotInMainChain(str) + + } + + return &node.hash, nil +} + +// HeightRange returns a range of block hashes for the given start and end +// heights. It is inclusive of the start height and exclusive of the end +// height. The end height will be limited to the current main chain height. +// +// This function is safe for concurrent access. +func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash, error) { + // Ensure requested heights are sane. + if startHeight < 0 { + return nil, fmt.Errorf("start height of fetch range must not "+ + "be less than zero - got %d", startHeight) + } + if endHeight < startHeight { + return nil, fmt.Errorf("end height of fetch range must not "+ + "be less than the start height - got start %d, end %d", + startHeight, endHeight) + } + + // There is nothing to do when the start and end heights are the same, + // so return now to avoid the chain view lock. + if startHeight == endHeight { + return nil, nil + } + + // Grab a lock on the chain view to prevent it from changing due to a + // reorg while building the hashes. + b.bestChain.mtx.Lock() + defer b.bestChain.mtx.Unlock() + + // When the requested start height is after the most recent best chain + // height, there is nothing to do. + latestHeight := b.bestChain.tip().height + if startHeight > latestHeight { + return nil, nil + } + + // Limit the ending height to the latest height of the chain. + if endHeight > latestHeight+1 { + endHeight = latestHeight + 1 + } + + // Fetch as many as are available within the specified range. + hashes := make([]chainhash.Hash, 0, endHeight-startHeight) + for i := startHeight; i < endHeight; i++ { + hashes = append(hashes, b.bestChain.nodeByHeight(i).hash) + } + return hashes, nil +} + // IndexManager provides a generic interface that the is called when blocks are // connected and disconnected to and from the tip of the main chain for the // purpose of supporting optional indexes. @@ -1309,6 +1412,7 @@ func New(config *Config) (*BlockChain, error) { blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), index: newBlockIndex(config.DB, params), hashCache: config.HashCache, + bestChain: newChainView(nil), orphans: make(map[chainhash.Hash]*orphanBlock), prevOrphans: make(map[chainhash.Hash][]*orphanBlock), warningCaches: newThresholdCaches(vbNumBits), @@ -1335,9 +1439,10 @@ func New(config *Config) (*BlockChain, error) { return nil, err } + bestNode := b.bestChain.Tip() log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)", - b.bestNode.height, b.bestNode.hash, b.stateSnapshot.TotalTxns, - b.bestNode.workSum) + bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns, + bestNode.workSum) return &b, nil } diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index e5b554f1..1d6920e4 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -125,14 +125,14 @@ func TestCalcSequenceLock(t *testing.T) { // Generate enough synthetic blocks to activate CSV. chain := newFakeChain(netParams) - node := chain.bestNode + node := chain.bestChain.Tip() blockTime := node.Header().Timestamp numBlocksToActivate := (netParams.MinerConfirmationWindow * 3) for i := uint32(0); i < numBlocksToActivate; i++ { blockTime = blockTime.Add(time.Second) node = newFakeNode(node, blockVersion, 0, blockTime) chain.index.AddNode(node) - chain.bestNode = node + chain.bestChain.SetTip(node) } // Create a utxo view with a fake utxo for the inputs used in the diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 548107f1..e4c95666 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1060,8 +1060,7 @@ func (b *BlockChain) createChainState() error { genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock) header := &genesisBlock.MsgBlock().Header node := newBlockNode(header, 0) - node.inMainChain = true - b.bestNode = node + b.bestChain.SetTip(node) // Add the new node to the index which is used for faster lookups. b.index.AddNode(node) @@ -1071,8 +1070,8 @@ func (b *BlockChain) createChainState() error { numTxns := uint64(len(genesisBlock.MsgBlock().Transactions)) blockSize := uint64(genesisBlock.MsgBlock().SerializeSize()) blockWeight := uint64(GetBlockWeight(genesisBlock)) - b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight, - numTxns, numTxns, time.Unix(b.bestNode.timestamp, 0)) + b.stateSnapshot = newBestState(node, blockSize, blockWeight, numTxns, + numTxns, time.Unix(node.timestamp, 0)) // Create the initial the database chain state including creating the // necessary index buckets and inserting the genesis block. @@ -1108,13 +1107,13 @@ func (b *BlockChain) createChainState() error { // Add the genesis block hash to height and height to hash // mappings to the index. - err = dbPutBlockIndex(dbTx, &b.bestNode.hash, b.bestNode.height) + err = dbPutBlockIndex(dbTx, &node.hash, node.height) if err != nil { return err } // Store the current best chain state into the database. - err = dbPutBestState(dbTx, b.stateSnapshot, b.bestNode.workSum) + err = dbPutBestState(dbTx, b.stateSnapshot, node.workSum) if err != nil { return err } @@ -1154,6 +1153,7 @@ func (b *BlockChain) initChainState() error { log.Infof("Loading block index. This might take a while...") bestHeight := int32(state.height) blockNodes := make([]blockNode, bestHeight+1) + var tip *blockNode for height := int32(0); height <= bestHeight; height++ { header, err := dbFetchHeaderByHeight(dbTx, height) if err != nil { @@ -1164,25 +1164,29 @@ func (b *BlockChain) initChainState() error { // and add it to the block index. node := &blockNodes[height] initBlockNode(node, header, height) - if parent := b.bestNode; parent != nil { - node.parent = parent - node.workSum = node.workSum.Add(parent.workSum, + if tip != nil { + node.parent = tip + node.workSum = node.workSum.Add(tip.workSum, node.workSum) } - node.inMainChain = true b.index.AddNode(node) // This node is now the end of the best chain. - b.bestNode = node + tip = node } - // Ensure the resulting best node matches the stored best state - // hash. - if b.bestNode.hash != state.hash { + // Ensure the resulting best chain matches the stored best state + // hash and set the best chain view accordingly. + if tip == nil || tip.hash != state.hash { + var tipHash chainhash.Hash + if tip != nil { + tipHash = tip.hash + } return AssertError(fmt.Sprintf("initChainState: block "+ "index chain tip %s does not match stored "+ - "best state %s", b.bestNode.hash, state.hash)) + "best state %s", tipHash, state.hash)) } + b.bestChain.SetTip(tip) // Load the raw block bytes for the best block. blockBytes, err := dbTx.FetchBlock(&state.hash) @@ -1199,8 +1203,8 @@ func (b *BlockChain) initChainState() error { blockSize := uint64(len(blockBytes)) blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block))) numTxns := uint64(len(block.Transactions)) - b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight, - numTxns, state.totalTxns, b.bestNode.CalcPastMedianTime()) + b.stateSnapshot = newBestState(tip, blockSize, blockWeight, + numTxns, state.totalTxns, tip.CalcPastMedianTime()) isStateInitialized = true return nil @@ -1247,44 +1251,12 @@ func dbFetchHeaderByHeight(dbTx database.Tx, height int32) (*wire.BlockHeader, e return dbFetchHeaderByHash(dbTx, hash) } -// dbFetchBlockByHash uses an existing database transaction to retrieve the raw -// block for the provided hash, deserialize it, retrieve the appropriate height -// from the index, and return a btcutil.Block with the height set. -func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*btcutil.Block, error) { - // First find the height associated with the provided hash in the index. - blockHeight, err := dbFetchHeightByHash(dbTx, hash) - if err != nil { - return nil, err - } - - // Load the raw block bytes from the database. - blockBytes, err := dbTx.FetchBlock(hash) - if err != nil { - return nil, err - } - - // Create the encapsulated block and set the height appropriately. - block, err := btcutil.NewBlockFromBytes(blockBytes) - if err != nil { - return nil, err - } - block.SetHeight(blockHeight) - - return block, nil -} - -// dbFetchBlockByHeight uses an existing database transaction to retrieve the -// raw block for the provided height, deserialize it, and return a btcutil.Block +// dbFetchBlockByNode uses an existing database transaction to retrieve the +// raw block for the provided node, deserialize it, and return a btcutil.Block // with the height set. -func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error) { - // First find the hash associated with the provided height in the index. - hash, err := dbFetchHashByHeight(dbTx, height) - if err != nil { - return nil, err - } - +func dbFetchBlockByNode(dbTx database.Tx, node *blockNode) (*btcutil.Block, error) { // Load the raw block bytes from the database. - blockBytes, err := dbTx.FetchBlock(hash) + blockBytes, err := dbTx.FetchBlock(&node.hash) if err != nil { return nil, err } @@ -1294,67 +1266,27 @@ func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error if err != nil { return nil, err } - block.SetHeight(height) + block.SetHeight(node.height) return block, nil } -// dbMainChainHasBlock uses an existing database transaction to return whether -// or not the main chain contains the block identified by the provided hash. -func dbMainChainHasBlock(dbTx database.Tx, hash *chainhash.Hash) bool { - hashIndex := dbTx.Metadata().Bucket(hashIndexBucketName) - return hashIndex.Get(hash[:]) != nil -} - -// MainChainHasBlock returns whether or not the block with the given hash is in -// the main chain. -// -// This function is safe for concurrent access. -func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) (bool, error) { - var exists bool - err := b.db.View(func(dbTx database.Tx) error { - exists = dbMainChainHasBlock(dbTx, hash) - return nil - }) - return exists, err -} - -// BlockHeightByHash returns the height of the block with the given hash in the -// main chain. -// -// This function is safe for concurrent access. -func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int32, error) { - var height int32 - err := b.db.View(func(dbTx database.Tx) error { - var err error - height, err = dbFetchHeightByHash(dbTx, hash) - return err - }) - return height, err -} - -// BlockHashByHeight returns the hash of the block at the given height in the -// main chain. -// -// This function is safe for concurrent access. -func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, error) { - var hash *chainhash.Hash - err := b.db.View(func(dbTx database.Tx) error { - var err error - hash, err = dbFetchHashByHeight(dbTx, blockHeight) - return err - }) - return hash, err -} - // BlockByHeight returns the block at the given height in the main chain. // // This function is safe for concurrent access. func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) { + // Lookup the block height in the best chain. + node := b.bestChain.NodeByHeight(blockHeight) + if node == nil { + str := fmt.Sprintf("no block at height %d exists", blockHeight) + return nil, errNotInMainChain(str) + } + + // Load the block from the database and return it. var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error - block, err = dbFetchBlockByHeight(dbTx, blockHeight) + block, err = dbFetchBlockByNode(dbTx, node) return err }) return block, err @@ -1365,70 +1297,20 @@ func (b *BlockChain) BlockByHeight(blockHeight int32) (*btcutil.Block, error) { // // This function is safe for concurrent access. func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*btcutil.Block, error) { + // Lookup the block hash in block index and ensure it is in the best + // chain. + node := b.index.LookupNode(hash) + if node == nil || !b.bestChain.Contains(node) { + str := fmt.Sprintf("block %s is not in the main chain", hash) + return nil, errNotInMainChain(str) + } + + // Load the block from the database and return it. var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error - block, err = dbFetchBlockByHash(dbTx, hash) + block, err = dbFetchBlockByNode(dbTx, node) return err }) return block, err } - -// HeightRange returns a range of block hashes for the given start and end -// heights. It is inclusive of the start height and exclusive of the end -// height. The end height will be limited to the current main chain height. -// -// This function is safe for concurrent access. -func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash, error) { - // Ensure requested heights are sane. - if startHeight < 0 { - return nil, fmt.Errorf("start height of fetch range must not "+ - "be less than zero - got %d", startHeight) - } - if endHeight < startHeight { - return nil, fmt.Errorf("end height of fetch range must not "+ - "be less than the start height - got start %d, end %d", - startHeight, endHeight) - } - - // There is nothing to do when the start and end heights are the same, - // so return now to avoid the chain lock and a database transaction. - if startHeight == endHeight { - return nil, nil - } - - // Grab a lock on the chain to prevent it from changing due to a reorg - // while building the hashes. - b.chainLock.RLock() - defer b.chainLock.RUnlock() - - // When the requested start height is after the most recent best chain - // height, there is nothing to do. - latestHeight := b.bestNode.height - if startHeight > latestHeight { - return nil, nil - } - - // Limit the ending height to the latest height of the chain. - if endHeight > latestHeight+1 { - endHeight = latestHeight + 1 - } - - // Fetch as many as are available within the specified range. - var hashList []chainhash.Hash - err := b.db.View(func(dbTx database.Tx) error { - hashes := make([]chainhash.Hash, 0, endHeight-startHeight) - for i := startHeight; i < endHeight; i++ { - hash, err := dbFetchHashByHeight(dbTx, i) - if err != nil { - return err - } - hashes = append(hashes, *hash) - } - - // Set the list to be returned to the constructed list. - hashList = hashes - return nil - }) - return hashList, err -} diff --git a/blockchain/chainview.go b/blockchain/chainview.go index 5b7cf21e..a4c3692c 100644 --- a/blockchain/chainview.go +++ b/blockchain/chainview.go @@ -12,6 +12,25 @@ import ( // in a week on average. const approxNodesPerWeek = 6 * 24 * 7 +// log2FloorMasks defines the masks to use when quickly calculating +// floor(log2(x)) in a constant log2(32) = 5 steps, where x is a uint32, using +// shifts. They are derived from (2^(2^x) - 1) * (2^(2^x)), for x in 4..0. +var log2FloorMasks = []uint32{0xffff0000, 0xff00, 0xf0, 0xc, 0x2} + +// fastLog2Floor calculates and returns floor(log2(x)) in a constant 5 steps. +func fastLog2Floor(n uint32) uint8 { + rv := uint8(0) + exponent := uint8(16) + for i := 0; i < 5; i++ { + if n&log2FloorMasks[i] != 0 { + rv += exponent + n >>= exponent + } + exponent >>= 1 + } + return rv +} + // chainView provides a flat view of a specific branch of the block chain from // its tip back to the genesis block and provides various convenience functions // for comparing chains. diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index 12e023c8..af3b6d92 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -6,10 +6,10 @@ package blockchain import ( "fmt" + "time" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" ) @@ -99,7 +99,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) { // that is already available. for i := numCheckpoints - 1; i >= 0; i-- { node := b.index.LookupNode(checkpoints[i].Hash) - if node == nil || !node.inMainChain { + if node == nil || !b.bestChain.Contains(node) { continue } @@ -130,7 +130,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) { // When there is a next checkpoint and the height of the current best // chain does not exceed it, the current checkpoint lockin is still // the latest known checkpoint. - if b.bestNode.height < b.nextCheckpoint.Height { + if b.bestChain.Tip().height < b.nextCheckpoint.Height { return b.checkpointNode, nil } @@ -201,69 +201,61 @@ func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) { b.chainLock.RLock() defer b.chainLock.RUnlock() - var isCandidate bool - err := b.db.View(func(dbTx database.Tx) error { - // A checkpoint must be in the main chain. - blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash()) - if err != nil { - // Only return an error if it's not due to the block not - // being in the main chain. - if !isNotInMainChainErr(err) { - return err - } - return nil - } + // A checkpoint must be in the main chain. + node := b.index.LookupNode(block.Hash()) + if node == nil || !b.bestChain.Contains(node) { + return false, nil + } - // Ensure the height of the passed block and the entry for the - // block in the main chain match. This should always be the - // case unless the caller provided an invalid block. - if blockHeight != block.Height() { - return fmt.Errorf("passed block height of %d does not "+ - "match the main chain height of %d", - block.Height(), blockHeight) - } + // Ensure the height of the passed block and the entry for the block in + // the main chain match. This should always be the case unless the + // caller provided an invalid block. + if node.height != block.Height() { + return false, fmt.Errorf("passed block height of %d does not "+ + "match the main chain height of %d", block.Height(), + node.height) + } - // A checkpoint must be at least CheckpointConfirmations blocks - // before the end of the main chain. - mainChainHeight := b.bestNode.height - if blockHeight > (mainChainHeight - CheckpointConfirmations) { - return nil - } + // A checkpoint must be at least CheckpointConfirmations blocks + // before the end of the main chain. + mainChainHeight := b.bestChain.Tip().height + if node.height > (mainChainHeight - CheckpointConfirmations) { + return false, nil + } - // Get the previous block header. - prevHash := &block.MsgBlock().Header.PrevBlock - prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash) - if err != nil { - return err - } + // A checkpoint must be have at least one block after it. + // + // This should always succeed since the check above already made sure it + // is CheckpointConfirmations back, but be safe in case the constant + // changes. + nextNode := b.bestChain.Next(node) + if nextNode == nil { + return false, nil + } - // Get the next block header. - nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1) - if err != nil { - return err - } + // A checkpoint must be have at least one block before it. + if node.parent == nil { + return false, nil + } - // A checkpoint must have timestamps for the block and the - // blocks on either side of it in order (due to the median time - // allowance this is not always the case). - prevTime := prevHeader.Timestamp - curTime := block.MsgBlock().Header.Timestamp - nextTime := nextHeader.Timestamp - if prevTime.After(curTime) || nextTime.Before(curTime) { - return nil - } + // A checkpoint must have timestamps for the block and the blocks on + // either side of it in order (due to the median time allowance this is + // not always the case). + prevTime := time.Unix(node.parent.timestamp, 0) + curTime := block.MsgBlock().Header.Timestamp + nextTime := time.Unix(nextNode.timestamp, 0) + if prevTime.After(curTime) || nextTime.Before(curTime) { + return false, nil + } - // A checkpoint must have transactions that only contain - // standard scripts. - for _, tx := range block.Transactions() { - if isNonstandardTransaction(tx) { - return nil - } + // A checkpoint must have transactions that only contain standard + // scripts. + for _, tx := range block.Transactions() { + if isNonstandardTransaction(tx) { + return false, nil } + } - // All of the checks passed, so the block is a candidate. - isCandidate = true - return nil - }) - return isCandidate, err + // All of the checks passed, so the block is a candidate. + return true, nil } diff --git a/blockchain/common_test.go b/blockchain/common_test.go index 24e0388c..38191c25 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -262,7 +262,6 @@ func newFakeChain(params *chaincfg.Params) *BlockChain { // Create a genesis block node and block index index populated with it // for use when creating the fake chain below. node := newBlockNode(¶ms.GenesisBlock.Header, 0) - node.inMainChain = true index := newBlockIndex(nil, params) index.AddNode(node) @@ -276,9 +275,9 @@ func newFakeChain(params *chaincfg.Params) *BlockChain { maxRetargetTimespan: targetTimespan * adjustmentFactor, blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), index: index, + bestChain: newChainView(node), warningCaches: newThresholdCaches(vbNumBits), deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments), - bestNode: node, } } diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index aa8b3768..05f78a3e 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -306,7 +306,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim // This function is safe for concurrent access. func (b *BlockChain) CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) { b.chainLock.Lock() - difficulty, err := b.calcNextRequiredDifficulty(b.bestNode, timestamp) + difficulty, err := b.calcNextRequiredDifficulty(b.bestChain.Tip(), timestamp) b.chainLock.Unlock() return difficulty, err } diff --git a/blockchain/indexers/manager.go b/blockchain/indexers/manager.go index da0ef38f..7de6f5f5 100644 --- a/blockchain/indexers/manager.go +++ b/blockchain/indexers/manager.go @@ -284,15 +284,7 @@ func (m *Manager) Init(chain *blockchain.BlockChain) error { // Loop until the tip is a block that exists in the main chain. initialHeight := height - for { - exists, err := chain.MainChainHasBlock(hash) - if err != nil { - return err - } - if exists { - break - } - + for !chain.MainChainHasBlock(hash) { // At this point the index tip is orphaned, so load the // orphaned block from the database directly and // disconnect it from the index. The block has to be diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index d3da5792..b9f39294 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -265,7 +265,7 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit // This function is safe for concurrent access. func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) { b.chainLock.Lock() - state, err := b.deploymentState(b.bestNode, deploymentID) + state, err := b.deploymentState(b.bestChain.Tip(), deploymentID) b.chainLock.Unlock() return state, err @@ -277,7 +277,7 @@ func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) // This function is safe for concurrent access. func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) { b.chainLock.Lock() - state, err := b.deploymentState(b.bestNode, deploymentID) + state, err := b.deploymentState(b.bestChain.Tip(), deploymentID) b.chainLock.Unlock() if err != nil { return false, err @@ -316,7 +316,7 @@ func (b *BlockChain) initThresholdCaches() error { // threshold state for each of them. This will ensure the caches are // populated and any states that needed to be recalculated due to // definition changes is done now. - prevNode := b.bestNode.parent + prevNode := b.bestChain.Tip().parent for bit := uint32(0); bit < vbNumBits; bit++ { checker := bitConditionChecker{bit: bit, chain: b} cache := &b.warningCaches[bit] @@ -340,13 +340,14 @@ func (b *BlockChain) initThresholdCaches() error { if b.isCurrent() { // Warn if a high enough percentage of the last blocks have // unexpected versions. - if err := b.warnUnknownVersions(b.bestNode); err != nil { + bestNode := b.bestChain.Tip() + if err := b.warnUnknownVersions(bestNode); err != nil { return err } // Warn if any unknown new rules are either about to activate or // have already been activated. - if err := b.warnUnknownRuleActivations(b.bestNode); err != nil { + if err := b.warnUnknownRuleActivations(bestNode); err != nil { return err } } diff --git a/blockchain/validate.go b/blockchain/validate.go index 9ad2d2c4..3eef85cc 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -1256,7 +1256,7 @@ func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error { b.chainLock.Lock() defer b.chainLock.Unlock() - prevNode := b.bestNode + prevNode := b.bestChain.Tip() newNode := newBlockNode(&block.MsgBlock().Header, prevNode.height+1) newNode.parent = prevNode newNode.workSum.Add(prevNode.workSum, newNode.workSum) diff --git a/blockchain/versionbits.go b/blockchain/versionbits.go index 3ddf11c4..ef2a0802 100644 --- a/blockchain/versionbits.go +++ b/blockchain/versionbits.go @@ -224,7 +224,7 @@ func (b *BlockChain) calcNextBlockVersion(prevNode *blockNode) (int32, error) { // This function is safe for concurrent access. func (b *BlockChain) CalcNextBlockVersion() (int32, error) { b.chainLock.Lock() - version, err := b.calcNextBlockVersion(b.bestNode) + version, err := b.calcNextBlockVersion(b.bestChain.Tip()) b.chainLock.Unlock() return version, err }