diff --git a/blockchain/blocklocator.go b/blockchain/blocklocator.go index bad87cced7..f58ae9e7fd 100644 --- a/blockchain/blocklocator.go +++ b/blockchain/blocklocator.go @@ -7,17 +7,34 @@ package blockchain import ( "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/database" - "github.com/decred/dcrd/wire" ) +// 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 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. +// 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: @@ -25,121 +42,59 @@ import ( // \-> 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] +// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis] type BlockLocator []*chainhash.Hash -// blockLocatorFromHash returns a block locator for the passed block hash. -// See BlockLocator for details on the algotirhm used to create a block locator. +// blockLocator returns a block locator for the passed block node. // -// 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 +// 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 (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 +func blockLocator(node *blockNode) BlockLocator { + if node == nil { + return nil } - // 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 := int64(-1) - forkHeight := int64(-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 int64 - 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 + // 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 { - 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 - } - } - } + // 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) - // 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 := int64(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 - } + step := int64(1) + for node != nil { + locator = append(locator, &node.hash) - // 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 - } + // Nothing more to add once the genesis block has been added. + if node.height == 0 { + break + } - // 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) + // Calculate height of previous node to include ensuring the + // final node is the genesis block. + height := node.height - step + if height < 0 { + height = 0 } - return nil - }) + // 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 + } + } - // Append the appropriate genesis block. - locator = append(locator, bi.chainParams.GenesisHash) return locator } @@ -151,14 +106,18 @@ func (bi *blockIndex) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { // // - 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 +// - 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() - locator := b.index.blockLocatorFromHash(hash) + node, exists := b.index.index[*hash] + if !exists { + node = b.bestNode + } + locator := blockLocator(node) b.index.RUnlock() b.chainLock.RUnlock() return locator @@ -171,7 +130,7 @@ func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) { b.chainLock.RLock() b.index.RLock() - locator := b.index.blockLocatorFromHash(&b.bestNode.hash) + locator := blockLocator(b.bestNode) b.index.RUnlock() b.chainLock.RUnlock() return locator, nil