// 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" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" ) // 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 10 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 6 2 genesis] type BlockLocator []*chainhash.Hash // 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 only // consist of the passed hash // // This function MUST be called with the block index lock held (for reads). func (bi *blockIndex) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { // The locator contains the requested hash at the very least. locator := make(BlockLocator, 0, wire.MaxBlockLocatorsPerMsg) locator = append(locator, hash) // Nothing more to do if a locator for the genesis hash was requested. if hash.IsEqual(bi.chainParams.GenesisHash) { return locator } // Attempt to find the height of the block that corresponds to the // passed hash, and if it's on a side chain, also find the height at // which it forks from the main chain. blockHeight := int32(-1) forkHeight := int32(-1) node, exists := bi.index[*hash] if !exists { // Try to look up the height for passed block hash. Assume an // error means it doesn't exist and just return the locator for // the block itself. var height int32 err := bi.db.View(func(dbTx database.Tx) error { var err error height, err = dbFetchHeightByHash(dbTx, hash) return err }) if err != nil { return locator } blockHeight = height } else { blockHeight = node.height // Find the height at which this node forks from the main chain // if the node is on a side chain. if !node.inMainChain { for n := node; n.parent != nil; n = n.parent { if n.inMainChain { forkHeight = n.height break } } } } // Generate the block locators according to the algorithm described in // in the BlockLocator comment and make sure to leave room for the final // genesis hash. // // The error is intentionally ignored here since the only way the code // could fail is if there is something wrong with the database which // will be caught in short order anyways and it's also safe to ignore // block locators. _ = bi.db.View(func(dbTx database.Tx) error { iterNode := node increment := int32(1) for len(locator) < wire.MaxBlockLocatorsPerMsg-1 { // Once there are 10 locators, exponentially increase // the distance between each block locator. if len(locator) > 10 { increment *= 2 } blockHeight -= increment if blockHeight < 1 { break } // As long as this is still on the side chain, walk // backwards along the side chain nodes to each block // height. if forkHeight != -1 && blockHeight > forkHeight { for iterNode != nil && blockHeight > iterNode.height { iterNode = iterNode.parent } if iterNode != nil && iterNode.height == blockHeight { locator = append(locator, &iterNode.hash) } continue } // The desired block height is in the main chain, so // look it up from the main chain database. h, err := dbFetchHashByHeight(dbTx, blockHeight) if err != nil { // This shouldn't happen and it's ok to ignore // block locators, so just continue to the next // one. log.Warnf("Lookup of known valid height failed %v", blockHeight) continue } locator = append(locator, h) } return nil }) // Append the appropriate genesis block. locator = append(locator, bi.chainParams.GenesisHash) 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 only // consist of the passed hash // // This function is safe for concurrent access. func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { b.chainLock.RLock() b.index.RLock() locator := b.index.blockLocatorFromHash(hash) 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 := b.index.blockLocatorFromHash(&b.bestNode.hash) b.index.RUnlock() b.chainLock.RUnlock() return locator, nil }