github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/consistency.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 8 "github.com/NebulousLabs/Sia/build" 9 "github.com/NebulousLabs/Sia/crypto" 10 "github.com/NebulousLabs/Sia/encoding" 11 "github.com/NebulousLabs/Sia/types" 12 "github.com/NebulousLabs/fastrand" 13 14 "github.com/coreos/bbolt" 15 ) 16 17 // manageErr handles an error detected by the consistency checks. 18 func manageErr(tx *bolt.Tx, err error) { 19 markInconsistency(tx) 20 if build.DEBUG { 21 panic(err) 22 } else { 23 fmt.Println(err) 24 } 25 } 26 27 // consensusChecksum grabs a checksum of the consensus set by pushing all of 28 // the elements in sorted order into a merkle tree and taking the root. All 29 // consensus sets with the same current block should have identical consensus 30 // checksums. 31 func consensusChecksum(tx *bolt.Tx) crypto.Hash { 32 // Create a checksum tree. 33 tree := crypto.NewTree() 34 35 // For all of the constant buckets, push every key and every value. Buckets 36 // are sorted in byte-order, therefore this operation is deterministic. 37 consensusSetBuckets := []*bolt.Bucket{ 38 tx.Bucket(BlockPath), 39 tx.Bucket(SiacoinOutputs), 40 tx.Bucket(FileContracts), 41 tx.Bucket(SiafundOutputs), 42 tx.Bucket(SiafundPool), 43 } 44 for i := range consensusSetBuckets { 45 err := consensusSetBuckets[i].ForEach(func(k, v []byte) error { 46 tree.Push(k) 47 tree.Push(v) 48 return nil 49 }) 50 if err != nil { 51 manageErr(tx, err) 52 } 53 } 54 55 // Iterate through all the buckets looking for buckets prefixed with 56 // prefixDSCO or prefixFCEX. Buckets are presented in byte-sorted order by 57 // name. 58 err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { 59 // If the bucket is not a delayed siacoin output bucket or a file 60 // contract expiration bucket, skip. 61 if !bytes.HasPrefix(name, prefixDSCO) && !bytes.HasPrefix(name, prefixFCEX) { 62 return nil 63 } 64 65 // The bucket is a prefixed bucket - add all elements to the tree. 66 return b.ForEach(func(k, v []byte) error { 67 tree.Push(k) 68 tree.Push(v) 69 return nil 70 }) 71 }) 72 if err != nil { 73 manageErr(tx, err) 74 } 75 76 return tree.Root() 77 } 78 79 // checkSiacoinCount checks that the number of siacoins countable within the 80 // consensus set equal the expected number of siacoins for the block height. 81 func checkSiacoinCount(tx *bolt.Tx) { 82 // Iterate through all the buckets looking for the delayed siacoin output 83 // buckets, and check that they are for the correct heights. 84 var dscoSiacoins types.Currency 85 err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { 86 // Check if the bucket is a delayed siacoin output bucket. 87 if !bytes.HasPrefix(name, prefixDSCO) { 88 return nil 89 } 90 91 // Sum up the delayed outputs in this bucket. 92 err := b.ForEach(func(_, delayedOutput []byte) error { 93 var sco types.SiacoinOutput 94 err := encoding.Unmarshal(delayedOutput, &sco) 95 if err != nil { 96 manageErr(tx, err) 97 } 98 dscoSiacoins = dscoSiacoins.Add(sco.Value) 99 return nil 100 }) 101 if err != nil { 102 return err 103 } 104 return nil 105 }) 106 if err != nil { 107 manageErr(tx, err) 108 } 109 110 // Add all of the siacoin outputs. 111 var scoSiacoins types.Currency 112 err = tx.Bucket(SiacoinOutputs).ForEach(func(_, scoBytes []byte) error { 113 var sco types.SiacoinOutput 114 err := encoding.Unmarshal(scoBytes, &sco) 115 if err != nil { 116 manageErr(tx, err) 117 } 118 scoSiacoins = scoSiacoins.Add(sco.Value) 119 return nil 120 }) 121 if err != nil { 122 manageErr(tx, err) 123 } 124 125 // Add all of the payouts from file contracts. 126 var fcSiacoins types.Currency 127 err = tx.Bucket(FileContracts).ForEach(func(_, fcBytes []byte) error { 128 var fc types.FileContract 129 err := encoding.Unmarshal(fcBytes, &fc) 130 if err != nil { 131 manageErr(tx, err) 132 } 133 var fcCoins types.Currency 134 for _, output := range fc.ValidProofOutputs { 135 fcCoins = fcCoins.Add(output.Value) 136 } 137 fcSiacoins = fcSiacoins.Add(fcCoins) 138 return nil 139 }) 140 if err != nil { 141 manageErr(tx, err) 142 } 143 144 // Add all of the siafund claims. 145 var claimSiacoins types.Currency 146 err = tx.Bucket(SiafundOutputs).ForEach(func(_, sfoBytes []byte) error { 147 var sfo types.SiafundOutput 148 err := encoding.Unmarshal(sfoBytes, &sfo) 149 if err != nil { 150 manageErr(tx, err) 151 } 152 153 coinsPerFund := getSiafundPool(tx).Sub(sfo.ClaimStart) 154 claimCoins := coinsPerFund.Mul(sfo.Value).Div(types.SiafundCount) 155 claimSiacoins = claimSiacoins.Add(claimCoins) 156 return nil 157 }) 158 if err != nil { 159 manageErr(tx, err) 160 } 161 162 expectedSiacoins := types.CalculateNumSiacoins(blockHeight(tx)) 163 totalSiacoins := dscoSiacoins.Add(scoSiacoins).Add(fcSiacoins).Add(claimSiacoins) 164 if !totalSiacoins.Equals(expectedSiacoins) { 165 diagnostics := fmt.Sprintf("Wrong number of siacoins\nDsco: %v\nSco: %v\nFc: %v\nClaim: %v\n", dscoSiacoins, scoSiacoins, fcSiacoins, claimSiacoins) 166 if totalSiacoins.Cmp(expectedSiacoins) < 0 { 167 diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, expectedSiacoins.Sub(totalSiacoins)) 168 } else { 169 diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, totalSiacoins.Sub(expectedSiacoins)) 170 } 171 manageErr(tx, errors.New(diagnostics)) 172 } 173 } 174 175 // checkSiafundCount checks that the number of siafunds countable within the 176 // consensus set equal the expected number of siafunds for the block height. 177 func checkSiafundCount(tx *bolt.Tx) { 178 var total types.Currency 179 err := tx.Bucket(SiafundOutputs).ForEach(func(_, siafundOutputBytes []byte) error { 180 var sfo types.SiafundOutput 181 err := encoding.Unmarshal(siafundOutputBytes, &sfo) 182 if err != nil { 183 manageErr(tx, err) 184 } 185 total = total.Add(sfo.Value) 186 return nil 187 }) 188 if err != nil { 189 manageErr(tx, err) 190 } 191 if !total.Equals(types.SiafundCount) { 192 manageErr(tx, errors.New("wrong number of siafunds in the consensus set")) 193 } 194 } 195 196 // checkDSCOs scans the sets of delayed siacoin outputs and checks for 197 // consistency. 198 func checkDSCOs(tx *bolt.Tx) { 199 // Create a map to track which delayed siacoin output maps exist, and 200 // another map to track which ids have appeared in the dsco set. 201 dscoTracker := make(map[types.BlockHeight]struct{}) 202 idMap := make(map[types.SiacoinOutputID]struct{}) 203 204 // Iterate through all the buckets looking for the delayed siacoin output 205 // buckets, and check that they are for the correct heights. 206 err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { 207 // If the bucket is not a delayed siacoin output bucket or a file 208 // contract expiration bucket, skip. 209 if !bytes.HasPrefix(name, prefixDSCO) { 210 return nil 211 } 212 213 // Add the bucket to the dscoTracker. 214 var height types.BlockHeight 215 err := encoding.Unmarshal(name[len(prefixDSCO):], &height) 216 if err != nil { 217 manageErr(tx, err) 218 } 219 _, exists := dscoTracker[height] 220 if exists { 221 return errors.New("repeat dsco map") 222 } 223 dscoTracker[height] = struct{}{} 224 225 var total types.Currency 226 err = b.ForEach(func(idBytes, delayedOutput []byte) error { 227 // Check that the output id has not appeared in another dsco. 228 var id types.SiacoinOutputID 229 copy(id[:], idBytes) 230 _, exists := idMap[id] 231 if exists { 232 return errors.New("repeat delayed siacoin output") 233 } 234 idMap[id] = struct{}{} 235 236 // Sum the funds in the bucket. 237 var sco types.SiacoinOutput 238 err := encoding.Unmarshal(delayedOutput, &sco) 239 if err != nil { 240 manageErr(tx, err) 241 } 242 total = total.Add(sco.Value) 243 return nil 244 }) 245 if err != nil { 246 return err 247 } 248 249 // Check that the minimum value has been achieved - the coinbase from 250 // an earlier block is guaranteed to be in the bucket. 251 minimumValue := types.CalculateCoinbase(height - types.MaturityDelay) 252 if total.Cmp(minimumValue) < 0 { 253 return errors.New("total number of coins in the delayed output bucket is incorrect") 254 } 255 return nil 256 }) 257 if err != nil { 258 manageErr(tx, err) 259 } 260 261 // Check that all of the correct heights are represented. 262 currentHeight := blockHeight(tx) 263 expectedBuckets := 0 264 for i := currentHeight + 1; i <= currentHeight+types.MaturityDelay; i++ { 265 if i < types.MaturityDelay { 266 continue 267 } 268 _, exists := dscoTracker[i] 269 if !exists { 270 manageErr(tx, errors.New("missing a dsco bucket")) 271 } 272 expectedBuckets++ 273 } 274 if len(dscoTracker) != expectedBuckets { 275 manageErr(tx, errors.New("too many dsco buckets")) 276 } 277 } 278 279 // checkRevertApply reverts the most recent block, checking to see that the 280 // consensus set hash matches the hash obtained for the previous block. Then it 281 // applies the block again and checks that the consensus set hash matches the 282 // original consensus set hash. 283 func (cs *ConsensusSet) checkRevertApply(tx *bolt.Tx) { 284 current := currentProcessedBlock(tx) 285 // Don't perform the check if this block is the genesis block. 286 if current.Block.ID() == cs.blockRoot.Block.ID() { 287 return 288 } 289 290 parent, err := getBlockMap(tx, current.Block.ParentID) 291 if err != nil { 292 manageErr(tx, err) 293 } 294 if current.Height != parent.Height+1 { 295 manageErr(tx, errors.New("parent structure of a block is incorrect")) 296 } 297 _, _, err = cs.forkBlockchain(tx, parent) 298 if err != nil { 299 manageErr(tx, err) 300 } 301 if consensusChecksum(tx) != parent.ConsensusChecksum { 302 manageErr(tx, errors.New("consensus checksum mismatch after reverting")) 303 } 304 _, _, err = cs.forkBlockchain(tx, current) 305 if err != nil { 306 manageErr(tx, err) 307 } 308 if consensusChecksum(tx) != current.ConsensusChecksum { 309 manageErr(tx, errors.New("consensus checksum mismatch after re-applying")) 310 } 311 } 312 313 // checkConsistency runs a series of checks to make sure that the consensus set 314 // is consistent with some rules that should always be true. 315 func (cs *ConsensusSet) checkConsistency(tx *bolt.Tx) { 316 if cs.checkingConsistency { 317 return 318 } 319 320 cs.checkingConsistency = true 321 checkDSCOs(tx) 322 checkSiacoinCount(tx) 323 checkSiafundCount(tx) 324 if build.DEBUG { 325 cs.checkRevertApply(tx) 326 } 327 cs.checkingConsistency = false 328 } 329 330 // maybeCheckConsistency runs a consistency check with a small probability. 331 // Useful for detecting database corruption in production without needing to go 332 // through the extremely slow process of running a consistency check every 333 // block. 334 func (cs *ConsensusSet) maybeCheckConsistency(tx *bolt.Tx) { 335 if fastrand.Intn(1000) == 0 { 336 cs.checkConsistency(tx) 337 } 338 } 339 340 // TODO: Check that every file contract has an expiration too, and that the 341 // number of file contracts + the number of expirations is equal.