github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/blockchain/checkpoints.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 "fmt" 9 10 "github.com/mit-dci/lit/btcutil" 11 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 12 "github.com/mit-dci/lit/btcutil/database" 13 "github.com/mit-dci/lit/btcutil/txscript" 14 "github.com/mit-dci/lit/coinparam" 15 ) 16 17 // CheckpointConfirmations is the number of blocks before the end of the current 18 // best block chain that a good checkpoint candidate must be. 19 const CheckpointConfirmations = 2016 20 21 // newHashFromStr converts the passed big-endian hex string into a 22 // chainhash.Hash. It only differs from the one available in chainhash in that 23 // it ignores the error since it will only (and must only) be called with 24 // hard-coded, and therefore known good, hashes. 25 func newHashFromStr(hexStr string) *chainhash.Hash { 26 hash, _ := chainhash.NewHashFromStr(hexStr) 27 return hash 28 } 29 30 // DisableCheckpoints provides a mechanism to disable validation against 31 // checkpoints which you DO NOT want to do in production. It is provided only 32 // for debug purposes. 33 // 34 // This function is safe for concurrent access. 35 func (b *BlockChain) DisableCheckpoints(disable bool) { 36 b.chainLock.Lock() 37 b.noCheckpoints = disable 38 b.chainLock.Unlock() 39 } 40 41 // Checkpoints returns a slice of checkpoints (regardless of whether they are 42 // already known). When checkpoints are disabled or there are no checkpoints 43 // for the active network, it will return nil. 44 // 45 // This function is safe for concurrent access. 46 func (b *BlockChain) Checkpoints() []coinparam.Checkpoint { 47 b.chainLock.RLock() 48 defer b.chainLock.RUnlock() 49 50 if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 { 51 return nil 52 } 53 54 return b.chainParams.Checkpoints 55 } 56 57 // latestCheckpoint returns the most recent checkpoint (regardless of whether it 58 // is already known). When checkpoints are disabled or there are no checkpoints 59 // for the active network, it will return nil. 60 // 61 // This function MUST be called with the chain state lock held (for reads). 62 func (b *BlockChain) latestCheckpoint() *coinparam.Checkpoint { 63 if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 { 64 return nil 65 } 66 67 checkpoints := b.chainParams.Checkpoints 68 return &checkpoints[len(checkpoints)-1] 69 } 70 71 // LatestCheckpoint returns the most recent checkpoint (regardless of whether it 72 // is already known). When checkpoints are disabled or there are no checkpoints 73 // for the active network, it will return nil. 74 // 75 // This function is safe for concurrent access. 76 func (b *BlockChain) LatestCheckpoint() *coinparam.Checkpoint { 77 b.chainLock.RLock() 78 checkpoint := b.latestCheckpoint() 79 b.chainLock.RUnlock() 80 return checkpoint 81 } 82 83 // verifyCheckpoint returns whether the passed block height and hash combination 84 // match the hard-coded checkpoint data. It also returns true if there is no 85 // checkpoint data for the passed block height. 86 // 87 // This function MUST be called with the chain lock held (for reads). 88 func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool { 89 if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 { 90 return true 91 } 92 93 // Nothing to check if there is no checkpoint data for the block height. 94 checkpoint, exists := b.checkpointsByHeight[height] 95 if !exists { 96 return true 97 } 98 99 if !checkpoint.Hash.IsEqual(hash) { 100 return false 101 } 102 103 return true 104 } 105 106 // findPreviousCheckpoint finds the most recent checkpoint that is already 107 // available in the downloaded portion of the block chain and returns the 108 // associated block. It returns nil if a checkpoint can't be found (this should 109 // really only happen for blocks before the first checkpoint). 110 // 111 // This function MUST be called with the chain lock held (for reads). 112 func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) { 113 if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 { 114 return nil, nil 115 } 116 117 // No checkpoints. 118 checkpoints := b.chainParams.Checkpoints 119 numCheckpoints := len(checkpoints) 120 if numCheckpoints == 0 { 121 return nil, nil 122 } 123 124 // Perform the initial search to find and cache the latest known 125 // checkpoint if the best chain is not known yet or we haven't already 126 // previously searched. 127 if b.checkpointBlock == nil && b.nextCheckpoint == nil { 128 // Loop backwards through the available checkpoints to find one 129 // that is already available. 130 checkpointIndex := -1 131 err := b.db.View(func(dbTx database.Tx) error { 132 for i := numCheckpoints - 1; i >= 0; i-- { 133 if dbMainChainHasBlock(dbTx, checkpoints[i].Hash) { 134 checkpointIndex = i 135 break 136 } 137 } 138 return nil 139 }) 140 if err != nil { 141 return nil, err 142 } 143 144 // No known latest checkpoint. This will only happen on blocks 145 // before the first known checkpoint. So, set the next expected 146 // checkpoint to the first checkpoint and return the fact there 147 // is no latest known checkpoint block. 148 if checkpointIndex == -1 { 149 b.nextCheckpoint = &checkpoints[0] 150 return nil, nil 151 } 152 153 // Cache the latest known checkpoint block for future lookups. 154 checkpoint := checkpoints[checkpointIndex] 155 err = b.db.View(func(dbTx database.Tx) error { 156 block, err := dbFetchBlockByHash(dbTx, checkpoint.Hash) 157 if err != nil { 158 return err 159 } 160 b.checkpointBlock = block 161 162 // Set the next expected checkpoint block accordingly. 163 b.nextCheckpoint = nil 164 if checkpointIndex < numCheckpoints-1 { 165 b.nextCheckpoint = &checkpoints[checkpointIndex+1] 166 } 167 168 return nil 169 }) 170 if err != nil { 171 return nil, err 172 } 173 174 return b.checkpointBlock, nil 175 } 176 177 // At this point we've already searched for the latest known checkpoint, 178 // so when there is no next checkpoint, the current checkpoint lockin 179 // will always be the latest known checkpoint. 180 if b.nextCheckpoint == nil { 181 return b.checkpointBlock, nil 182 } 183 184 // When there is a next checkpoint and the height of the current best 185 // chain does not exceed it, the current checkpoint lockin is still 186 // the latest known checkpoint. 187 if b.bestNode.height < b.nextCheckpoint.Height { 188 return b.checkpointBlock, nil 189 } 190 191 // We've reached or exceeded the next checkpoint height. Note that 192 // once a checkpoint lockin has been reached, forks are prevented from 193 // any blocks before the checkpoint, so we don't have to worry about the 194 // checkpoint going away out from under us due to a chain reorganize. 195 196 // Cache the latest known checkpoint block for future lookups. Note 197 // that if this lookup fails something is very wrong since the chain 198 // has already passed the checkpoint which was verified as accurate 199 // before inserting it. 200 err := b.db.View(func(tx database.Tx) error { 201 block, err := dbFetchBlockByHash(tx, b.nextCheckpoint.Hash) 202 if err != nil { 203 return err 204 } 205 b.checkpointBlock = block 206 return nil 207 }) 208 if err != nil { 209 return nil, err 210 } 211 212 // Set the next expected checkpoint. 213 checkpointIndex := -1 214 for i := numCheckpoints - 1; i >= 0; i-- { 215 if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) { 216 checkpointIndex = i 217 break 218 } 219 } 220 b.nextCheckpoint = nil 221 if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 { 222 b.nextCheckpoint = &checkpoints[checkpointIndex+1] 223 } 224 225 return b.checkpointBlock, nil 226 } 227 228 // isNonstandardTransaction determines whether a transaction contains any 229 // scripts which are not one of the standard types. 230 func isNonstandardTransaction(tx *btcutil.Tx) bool { 231 // Check all of the output public key scripts for non-standard scripts. 232 for _, txOut := range tx.MsgTx().TxOut { 233 scriptClass := txscript.GetScriptClass(txOut.PkScript) 234 if scriptClass == txscript.NonStandardTy { 235 return true 236 } 237 } 238 return false 239 } 240 241 // IsCheckpointCandidate returns whether or not the passed block is a good 242 // checkpoint candidate. 243 // 244 // The factors used to determine a good checkpoint are: 245 // - The block must be in the main chain 246 // - The block must be at least 'CheckpointConfirmations' blocks prior to the 247 // current end of the main chain 248 // - The timestamps for the blocks before and after the checkpoint must have 249 // timestamps which are also before and after the checkpoint, respectively 250 // (due to the median time allowance this is not always the case) 251 // - The block must not contain any strange transaction such as those with 252 // nonstandard scripts 253 // 254 // The intent is that candidates are reviewed by a developer to make the final 255 // decision and then manually added to the list of checkpoints for a network. 256 // 257 // This function is safe for concurrent access. 258 func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) { 259 b.chainLock.RLock() 260 defer b.chainLock.RUnlock() 261 262 // Checkpoints must be enabled. 263 if b.noCheckpoints { 264 return false, fmt.Errorf("checkpoints are disabled") 265 } 266 267 var isCandidate bool 268 err := b.db.View(func(dbTx database.Tx) error { 269 // A checkpoint must be in the main chain. 270 blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash()) 271 if err != nil { 272 // Only return an error if it's not due to the block not 273 // being in the main chain. 274 if !isNotInMainChainErr(err) { 275 return err 276 } 277 return nil 278 } 279 280 // Ensure the height of the passed block and the entry for the 281 // block in the main chain match. This should always be the 282 // case unless the caller provided an invalid block. 283 if blockHeight != block.Height() { 284 return fmt.Errorf("passed block height of %d does not "+ 285 "match the main chain height of %d", 286 block.Height(), blockHeight) 287 } 288 289 // A checkpoint must be at least CheckpointConfirmations blocks 290 // before the end of the main chain. 291 mainChainHeight := b.bestNode.height 292 if blockHeight > (mainChainHeight - CheckpointConfirmations) { 293 return nil 294 } 295 296 // Get the previous block header. 297 prevHash := &block.MsgBlock().Header.PrevBlock 298 prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash) 299 if err != nil { 300 return err 301 } 302 303 // Get the next block header. 304 nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1) 305 if err != nil { 306 return err 307 } 308 309 // A checkpoint must have timestamps for the block and the 310 // blocks on either side of it in order (due to the median time 311 // allowance this is not always the case). 312 prevTime := prevHeader.Timestamp 313 curTime := block.MsgBlock().Header.Timestamp 314 nextTime := nextHeader.Timestamp 315 if prevTime.After(curTime) || nextTime.Before(curTime) { 316 return nil 317 } 318 319 // A checkpoint must have transactions that only contain 320 // standard scripts. 321 for _, tx := range block.Transactions() { 322 if isNonstandardTransaction(tx) { 323 return nil 324 } 325 } 326 327 // All of the checks passed, so the block is a candidate. 328 isCandidate = true 329 return nil 330 }) 331 return isCandidate, err 332 }