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