github.com/btcsuite/btcd@v0.24.0/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 "time" 10 11 "github.com/btcsuite/btcd/btcutil" 12 "github.com/btcsuite/btcd/chaincfg" 13 "github.com/btcsuite/btcd/chaincfg/chainhash" 14 "github.com/btcsuite/btcd/txscript" 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 // Checkpoints returns a slice of checkpoints (regardless of whether they are 31 // already known). When there are no checkpoints for the chain, it will return 32 // nil. 33 // 34 // This function is safe for concurrent access. 35 func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint { 36 return b.checkpoints 37 } 38 39 // HasCheckpoints returns whether this BlockChain has checkpoints defined. 40 // 41 // This function is safe for concurrent access. 42 func (b *BlockChain) HasCheckpoints() bool { 43 return len(b.checkpoints) > 0 44 } 45 46 // LatestCheckpoint returns the most recent checkpoint (regardless of whether it 47 // is already known). When there are no defined checkpoints for the active chain 48 // instance, it will return nil. 49 // 50 // This function is safe for concurrent access. 51 func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint { 52 if !b.HasCheckpoints() { 53 return nil 54 } 55 return &b.checkpoints[len(b.checkpoints)-1] 56 } 57 58 // verifyCheckpoint returns whether the passed block height and hash combination 59 // match the checkpoint data. It also returns true if there is no checkpoint 60 // data for the passed block height. 61 func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool { 62 if !b.HasCheckpoints() { 63 return true 64 } 65 66 // Nothing to check if there is no checkpoint data for the block height. 67 checkpoint, exists := b.checkpointsByHeight[height] 68 if !exists { 69 return true 70 } 71 72 if !checkpoint.Hash.IsEqual(hash) { 73 return false 74 } 75 76 log.Infof("Verified checkpoint at height %d/block %s", checkpoint.Height, 77 checkpoint.Hash) 78 return true 79 } 80 81 // findPreviousCheckpoint finds the most recent checkpoint that is already 82 // available in the downloaded portion of the block chain and returns the 83 // associated block node. It returns nil if a checkpoint can't be found (this 84 // should really only happen for blocks before the first checkpoint). 85 // 86 // This function MUST be called with the chain lock held (for reads). 87 func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) { 88 if !b.HasCheckpoints() { 89 return nil, nil 90 } 91 92 // Perform the initial search to find and cache the latest known 93 // checkpoint if the best chain is not known yet or we haven't already 94 // previously searched. 95 checkpoints := b.checkpoints 96 numCheckpoints := len(checkpoints) 97 if b.checkpointNode == nil && b.nextCheckpoint == nil { 98 // Loop backwards through the available checkpoints to find one 99 // that is already available. 100 for i := numCheckpoints - 1; i >= 0; i-- { 101 node := b.index.LookupNode(checkpoints[i].Hash) 102 if node == nil || !b.bestChain.Contains(node) { 103 continue 104 } 105 106 // Checkpoint found. Cache it for future lookups and 107 // set the next expected checkpoint accordingly. 108 b.checkpointNode = node 109 if i < numCheckpoints-1 { 110 b.nextCheckpoint = &checkpoints[i+1] 111 } 112 return b.checkpointNode, nil 113 } 114 115 // No known latest checkpoint. This will only happen on blocks 116 // before the first known checkpoint. So, set the next expected 117 // checkpoint to the first checkpoint and return the fact there 118 // is no latest known checkpoint block. 119 b.nextCheckpoint = &checkpoints[0] 120 return nil, nil 121 } 122 123 // At this point we've already searched for the latest known checkpoint, 124 // so when there is no next checkpoint, the current checkpoint lockin 125 // will always be the latest known checkpoint. 126 if b.nextCheckpoint == nil { 127 return b.checkpointNode, nil 128 } 129 130 // When there is a next checkpoint and the height of the current best 131 // chain does not exceed it, the current checkpoint lockin is still 132 // the latest known checkpoint. 133 if b.bestChain.Tip().height < b.nextCheckpoint.Height { 134 return b.checkpointNode, nil 135 } 136 137 // We've reached or exceeded the next checkpoint height. Note that 138 // once a checkpoint lockin has been reached, forks are prevented from 139 // any blocks before the checkpoint, so we don't have to worry about the 140 // checkpoint going away out from under us due to a chain reorganize. 141 142 // Cache the latest known checkpoint for future lookups. Note that if 143 // this lookup fails something is very wrong since the chain has already 144 // passed the checkpoint which was verified as accurate before inserting 145 // it. 146 checkpointNode := b.index.LookupNode(b.nextCheckpoint.Hash) 147 if checkpointNode == nil { 148 return nil, AssertError(fmt.Sprintf("findPreviousCheckpoint "+ 149 "failed lookup of known good block node %s", 150 b.nextCheckpoint.Hash)) 151 } 152 b.checkpointNode = checkpointNode 153 154 // Set the next expected checkpoint. 155 checkpointIndex := -1 156 for i := numCheckpoints - 1; i >= 0; i-- { 157 if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) { 158 checkpointIndex = i 159 break 160 } 161 } 162 b.nextCheckpoint = nil 163 if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 { 164 b.nextCheckpoint = &checkpoints[checkpointIndex+1] 165 } 166 167 return b.checkpointNode, nil 168 } 169 170 // isNonstandardTransaction determines whether a transaction contains any 171 // scripts which are not one of the standard types. 172 func isNonstandardTransaction(tx *btcutil.Tx) bool { 173 // Check all of the output public key scripts for non-standard scripts. 174 for _, txOut := range tx.MsgTx().TxOut { 175 scriptClass := txscript.GetScriptClass(txOut.PkScript) 176 if scriptClass == txscript.NonStandardTy { 177 return true 178 } 179 } 180 return false 181 } 182 183 // IsCheckpointCandidate returns whether or not the passed block is a good 184 // checkpoint candidate. 185 // 186 // The factors used to determine a good checkpoint are: 187 // - The block must be in the main chain 188 // - The block must be at least 'CheckpointConfirmations' blocks prior to the 189 // current end of the main chain 190 // - The timestamps for the blocks before and after the checkpoint must have 191 // timestamps which are also before and after the checkpoint, respectively 192 // (due to the median time allowance this is not always the case) 193 // - The block must not contain any strange transaction such as those with 194 // nonstandard scripts 195 // 196 // The intent is that candidates are reviewed by a developer to make the final 197 // decision and then manually added to the list of checkpoints for a network. 198 // 199 // This function is safe for concurrent access. 200 func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) { 201 b.chainLock.RLock() 202 defer b.chainLock.RUnlock() 203 204 // A checkpoint must be in the main chain. 205 node := b.index.LookupNode(block.Hash()) 206 if node == nil || !b.bestChain.Contains(node) { 207 return false, nil 208 } 209 210 // Ensure the height of the passed block and the entry for the block in 211 // the main chain match. This should always be the case unless the 212 // caller provided an invalid block. 213 if node.height != block.Height() { 214 return false, fmt.Errorf("passed block height of %d does not "+ 215 "match the main chain height of %d", block.Height(), 216 node.height) 217 } 218 219 // A checkpoint must be at least CheckpointConfirmations blocks 220 // before the end of the main chain. 221 mainChainHeight := b.bestChain.Tip().height 222 if node.height > (mainChainHeight - CheckpointConfirmations) { 223 return false, nil 224 } 225 226 // A checkpoint must be have at least one block after it. 227 // 228 // This should always succeed since the check above already made sure it 229 // is CheckpointConfirmations back, but be safe in case the constant 230 // changes. 231 nextNode := b.bestChain.Next(node) 232 if nextNode == nil { 233 return false, nil 234 } 235 236 // A checkpoint must be have at least one block before it. 237 if node.parent == nil { 238 return false, nil 239 } 240 241 // A checkpoint must have timestamps for the block and the blocks on 242 // either side of it in order (due to the median time allowance this is 243 // not always the case). 244 prevTime := time.Unix(node.parent.timestamp, 0) 245 curTime := block.MsgBlock().Header.Timestamp 246 nextTime := time.Unix(nextNode.timestamp, 0) 247 if prevTime.After(curTime) || nextTime.Before(curTime) { 248 return false, nil 249 } 250 251 // A checkpoint must have transactions that only contain standard 252 // scripts. 253 for _, tx := range block.Transactions() { 254 if isNonstandardTransaction(tx) { 255 return false, nil 256 } 257 } 258 259 // All of the checks passed, so the block is a candidate. 260 return true, nil 261 }