github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/blockchain/blocklocator.go (about) 1 // Copyright (c) 2013-2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 9 "github.com/mit-dci/lit/btcutil/database" 10 "github.com/mit-dci/lit/wire" 11 ) 12 13 // BlockLocator is used to help locate a specific block. The algorithm for 14 // building the block locator is to add the hashes in reverse order until 15 // the genesis block is reached. In order to keep the list of locator hashes 16 // to a reasonable number of entries, first the most recent previous 10 block 17 // hashes are added, then the step is doubled each loop iteration to 18 // exponentially decrease the number of hashes as a function of the distance 19 // from the block being located. 20 // 21 // For example, assume you have a block chain with a side chain as depicted 22 // below: 23 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 24 // \-> 16a -> 17a 25 // 26 // The block locator for block 17a would be the hashes of blocks: 27 // [17a 16a 15 14 13 12 11 10 9 8 6 2 genesis] 28 type BlockLocator []*chainhash.Hash 29 30 // blockLocatorFromHash returns a block locator for the passed block hash. 31 // See BlockLocator for details on the algotirhm used to create a block locator. 32 // 33 // In addition to the general algorithm referenced above, there are a couple of 34 // special cases which are handled: 35 // 36 // - If the genesis hash is passed, there are no previous hashes to add and 37 // therefore the block locator will only consist of the genesis hash 38 // - If the passed hash is not currently known, the block locator will only 39 // consist of the passed hash 40 // 41 // This function MUST be called with the chain state lock held (for reads). 42 func (b *BlockChain) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { 43 // The locator contains the requested hash at the very least. 44 locator := make(BlockLocator, 0, wire.MaxBlockLocatorsPerMsg) 45 locator = append(locator, hash) 46 47 // Nothing more to do if a locator for the genesis hash was requested. 48 if hash.IsEqual(b.chainParams.GenesisHash) { 49 return locator 50 } 51 52 // Attempt to find the height of the block that corresponds to the 53 // passed hash, and if it's on a side chain, also find the height at 54 // which it forks from the main chain. 55 blockHeight := int32(-1) 56 forkHeight := int32(-1) 57 node, exists := b.index[*hash] 58 if !exists { 59 // Try to look up the height for passed block hash. Assume an 60 // error means it doesn't exist and just return the locator for 61 // the block itself. 62 var height int32 63 err := b.db.View(func(dbTx database.Tx) error { 64 var err error 65 height, err = dbFetchHeightByHash(dbTx, hash) 66 return err 67 }) 68 if err != nil { 69 return locator 70 } 71 72 blockHeight = height 73 } else { 74 blockHeight = node.height 75 76 // Find the height at which this node forks from the main chain 77 // if the node is on a side chain. 78 if !node.inMainChain { 79 for n := node; n.parent != nil; n = n.parent { 80 if n.inMainChain { 81 forkHeight = n.height 82 break 83 } 84 } 85 } 86 } 87 88 // Generate the block locators according to the algorithm described in 89 // in the BlockLocator comment and make sure to leave room for the final 90 // genesis hash. 91 // 92 // The error is intentionally ignored here since the only way the code 93 // could fail is if there is something wrong with the database which 94 // will be caught in short order anyways and it's also safe to ignore 95 // block locators. 96 _ = b.db.View(func(dbTx database.Tx) error { 97 iterNode := node 98 increment := int32(1) 99 for len(locator) < wire.MaxBlockLocatorsPerMsg-1 { 100 // Once there are 10 locators, exponentially increase 101 // the distance between each block locator. 102 if len(locator) > 10 { 103 increment *= 2 104 } 105 blockHeight -= increment 106 if blockHeight < 1 { 107 break 108 } 109 110 // As long as this is still on the side chain, walk 111 // backwards along the side chain nodes to each block 112 // height. 113 if forkHeight != -1 && blockHeight > forkHeight { 114 // Intentionally use parent field instead of the 115 // getPrevNodeFromNode function since we don't 116 // want to dynamically load nodes when building 117 // block locators. Side chain blocks should 118 // always be in memory already, and if they 119 // aren't for some reason it's ok to skip them. 120 for iterNode != nil && blockHeight > iterNode.height { 121 iterNode = iterNode.parent 122 } 123 if iterNode != nil && iterNode.height == blockHeight { 124 locator = append(locator, iterNode.hash) 125 } 126 continue 127 } 128 129 // The desired block height is in the main chain, so 130 // look it up from the main chain database. 131 h, err := dbFetchHashByHeight(dbTx, blockHeight) 132 if err != nil { 133 // This shouldn't happen and it's ok to ignore 134 // block locators, so just continue to the next 135 // one. 136 continue 137 } 138 locator = append(locator, h) 139 } 140 141 return nil 142 }) 143 144 // Append the appropriate genesis block. 145 locator = append(locator, b.chainParams.GenesisHash) 146 return locator 147 } 148 149 // BlockLocatorFromHash returns a block locator for the passed block hash. 150 // See BlockLocator for details on the algorithm used to create a block locator. 151 // 152 // In addition to the general algorithm referenced above, there are a couple of 153 // special cases which are handled: 154 // 155 // - If the genesis hash is passed, there are no previous hashes to add and 156 // therefore the block locator will only consist of the genesis hash 157 // - If the passed hash is not currently known, the block locator will only 158 // consist of the passed hash 159 // 160 // This function is safe for concurrent access. 161 func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { 162 b.chainLock.RLock() 163 locator := b.blockLocatorFromHash(hash) 164 b.chainLock.RUnlock() 165 return locator 166 } 167 168 // LatestBlockLocator returns a block locator for the latest known tip of the 169 // main (best) chain. 170 // 171 // This function is safe for concurrent access. 172 func (b *BlockChain) LatestBlockLocator() (BlockLocator, error) { 173 b.chainLock.RLock() 174 locator := b.blockLocatorFromHash(b.bestNode.hash) 175 b.chainLock.RUnlock() 176 return locator, nil 177 }