github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/consensus/consensusdb.go (about) 1 package consensus 2 3 // consensusdb.go contains all of the functions related to performing consensus 4 // related actions on the database, including initializing the consensus 5 // portions of the database. Many errors cause panics instead of being handled 6 // gracefully, but only when the debug flag is set. The errors are silently 7 // ignored otherwise, which is suboptimal. 8 9 import ( 10 "SiaPrime/build" 11 "SiaPrime/encoding" 12 "SiaPrime/modules" 13 "SiaPrime/types" 14 15 "gitlab.com/NebulousLabs/bolt" 16 ) 17 18 var ( 19 prefixDSCO = []byte("dsco_") 20 prefixFCEX = []byte("fcex_") 21 ) 22 23 var ( 24 // BlockHeight is a bucket that stores the current block height. 25 // 26 // Generally we would just look at BlockPath.Stats(), but there is an error 27 // in boltdb that prevents the bucket stats from updating until a tx is 28 // committed. Wasn't a problem until we started doing the entire block as 29 // one tx. 30 // 31 // DEPRECATED - block.Stats() should be sufficient to determine the block 32 // height, but currently stats are only computed after committing a 33 // transaction, therefore cannot be assumed reliable. 34 BlockHeight = []byte("BlockHeight") 35 36 // BlockMap is a database bucket containing all of the processed blocks, 37 // keyed by their id. This includes blocks that are not currently in the 38 // consensus set, and blocks that may not have been fully validated yet. 39 BlockMap = []byte("BlockMap") 40 41 // BlockPath is a database bucket containing a mapping from the height of a 42 // block to the id of the block at that height. BlockPath only includes 43 // blocks in the current path. 44 BlockPath = []byte("BlockPath") 45 46 // BucketOak is the database bucket that contains all of the fields related 47 // to the oak difficulty adjustment algorithm. The cumulative difficulty and 48 // time values are stored for each block id, and then the key "OakInit" 49 // contains the value "true" if the oak fields have been properly 50 // initialized. 51 BucketOak = []byte("Oak") 52 53 // Consistency is a database bucket with a flag indicating whether 54 // inconsistencies within the database have been detected. 55 Consistency = []byte("Consistency") 56 57 // FileContracts is a database bucket that contains all of the open file 58 // contracts. 59 FileContracts = []byte("FileContracts") 60 61 // SiacoinOutputs is a database bucket that contains all of the unspent 62 // siacoin outputs. 63 SiacoinOutputs = []byte("SiacoinOutputs") 64 65 // SiafundOutputs is a database bucket that contains all of the unspent 66 // siafund outputs. 67 SiafundOutputs = []byte("SiafundOutputs") 68 69 // SiafundPool is a database bucket storing the current value of the 70 // siafund pool. 71 SiafundPool = []byte("SiafundPool") 72 ) 73 74 var ( 75 // FieldOakInit is a field in BucketOak that gets set to "true" after the 76 // oak initialiation process has completed. 77 FieldOakInit = []byte("OakInit") 78 ) 79 80 var ( 81 // ValueOakInit is the value that the oak init field is set to if the oak 82 // difficulty adjustment fields have been correctly intialized. 83 ValueOakInit = []byte("true") 84 ) 85 86 // createConsensusObjects initialzes the consensus portions of the database. 87 func (cs *ConsensusSet) createConsensusDB(tx *bolt.Tx) error { 88 // Enumerate and create the database buckets. 89 buckets := [][]byte{ 90 BlockHeight, 91 BlockMap, 92 BlockPath, 93 Consistency, 94 SiacoinOutputs, 95 FileContracts, 96 SiafundOutputs, 97 SiafundPool, 98 } 99 for _, bucket := range buckets { 100 _, err := tx.CreateBucket(bucket) 101 if err != nil { 102 return err 103 } 104 } 105 106 // Set the block height to -1, so the genesis block is at height 0. 107 blockHeight := tx.Bucket(BlockHeight) 108 underflow := types.BlockHeight(0) 109 err := blockHeight.Put(BlockHeight, encoding.Marshal(underflow-1)) 110 if err != nil { 111 return err 112 } 113 114 // Update the siacoin output diffs map for the genesis block on disk. This 115 // needs to happen between the database being opened/initilized and the 116 // consensus set hash being calculated 117 for _, scod := range cs.blockRoot.SiacoinOutputDiffs { 118 commitSiacoinOutputDiff(tx, scod, modules.DiffApply) 119 } 120 121 // Set the siafund pool to 0. 122 setSiafundPool(tx, types.NewCurrency64(0)) 123 124 // Update the siafund output diffs map for the genesis block on disk. This 125 // needs to happen between the database being opened/initilized and the 126 // consensus set hash being calculated 127 for _, sfod := range cs.blockRoot.SiafundOutputDiffs { 128 commitSiafundOutputDiff(tx, sfod, modules.DiffApply) 129 } 130 131 // Add the miner payout from the genesis block to the delayed siacoin 132 // outputs - unspendable, as the unlock hash is blank. 133 createDSCOBucket(tx, types.MaturityDelay) 134 addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{ 135 Value: types.CalculateCoinbase(0), 136 UnlockHash: types.UnlockHash{}, 137 }) 138 139 // Add the genesis block to the block structures - checksum must be taken 140 // after pushing the genesis block into the path. 141 pushPath(tx, cs.blockRoot.Block.ID()) 142 if build.DEBUG { 143 cs.blockRoot.ConsensusChecksum = consensusChecksum(tx) 144 } 145 addBlockMap(tx, &cs.blockRoot) 146 return nil 147 } 148 149 // blockHeight returns the height of the blockchain. 150 func blockHeight(tx *bolt.Tx) types.BlockHeight { 151 var height types.BlockHeight 152 bh := tx.Bucket(BlockHeight) 153 err := encoding.Unmarshal(bh.Get(BlockHeight), &height) 154 if build.DEBUG && err != nil { 155 panic(err) 156 } 157 return height 158 } 159 160 // currentBlockID returns the id of the most recent block in the consensus set. 161 func currentBlockID(tx *bolt.Tx) types.BlockID { 162 id, err := getPath(tx, blockHeight(tx)) 163 if build.DEBUG && err != nil { 164 panic(err) 165 } 166 return id 167 } 168 169 // dbCurrentBlockID is a convenience function allowing currentBlockID to be 170 // called without a bolt.Tx. 171 func (cs *ConsensusSet) dbCurrentBlockID() (id types.BlockID) { 172 dbErr := cs.db.View(func(tx *bolt.Tx) error { 173 id = currentBlockID(tx) 174 return nil 175 }) 176 if dbErr != nil { 177 panic(dbErr) 178 } 179 return id 180 } 181 182 // currentProcessedBlock returns the most recent block in the consensus set. 183 func currentProcessedBlock(tx *bolt.Tx) *processedBlock { 184 pb, err := getBlockMap(tx, currentBlockID(tx)) 185 if build.DEBUG && err != nil { 186 panic(err) 187 } 188 return pb 189 } 190 191 // getBlockMap returns a processed block with the input id. 192 func getBlockMap(tx *bolt.Tx, id types.BlockID) (*processedBlock, error) { 193 // Look up the encoded block. 194 pbBytes := tx.Bucket(BlockMap).Get(id[:]) 195 if pbBytes == nil { 196 return nil, errNilItem 197 } 198 199 // Decode the block - should never fail. 200 var pb processedBlock 201 err := encoding.Unmarshal(pbBytes, &pb) 202 if build.DEBUG && err != nil { 203 panic(err) 204 } 205 return &pb, nil 206 } 207 208 // addBlockMap adds a processed block to the block map. 209 func addBlockMap(tx *bolt.Tx, pb *processedBlock) { 210 id := pb.Block.ID() 211 err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb)) 212 if build.DEBUG && err != nil { 213 panic(err) 214 } 215 } 216 217 // getPath returns the block id at 'height' in the block path. 218 func getPath(tx *bolt.Tx, height types.BlockHeight) (id types.BlockID, err error) { 219 idBytes := tx.Bucket(BlockPath).Get(encoding.Marshal(height)) 220 if idBytes == nil { 221 return types.BlockID{}, errNilItem 222 } 223 224 err = encoding.Unmarshal(idBytes, &id) 225 if build.DEBUG && err != nil { 226 panic(err) 227 } 228 return id, nil 229 } 230 231 // pushPath adds a block to the BlockPath at current height + 1. 232 func pushPath(tx *bolt.Tx, bid types.BlockID) { 233 // Fetch and update the block height. 234 bh := tx.Bucket(BlockHeight) 235 heightBytes := bh.Get(BlockHeight) 236 var oldHeight types.BlockHeight 237 err := encoding.Unmarshal(heightBytes, &oldHeight) 238 if build.DEBUG && err != nil { 239 panic(err) 240 } 241 newHeightBytes := encoding.Marshal(oldHeight + 1) 242 err = bh.Put(BlockHeight, newHeightBytes) 243 if build.DEBUG && err != nil { 244 panic(err) 245 } 246 247 // Add the block to the block path. 248 bp := tx.Bucket(BlockPath) 249 err = bp.Put(newHeightBytes, bid[:]) 250 if build.DEBUG && err != nil { 251 panic(err) 252 } 253 } 254 255 // popPath removes a block from the "end" of the chain, i.e. the block 256 // with the largest height. 257 func popPath(tx *bolt.Tx) { 258 // Fetch and update the block height. 259 bh := tx.Bucket(BlockHeight) 260 oldHeightBytes := bh.Get(BlockHeight) 261 var oldHeight types.BlockHeight 262 err := encoding.Unmarshal(oldHeightBytes, &oldHeight) 263 if build.DEBUG && err != nil { 264 panic(err) 265 } 266 newHeightBytes := encoding.Marshal(oldHeight - 1) 267 err = bh.Put(BlockHeight, newHeightBytes) 268 if build.DEBUG && err != nil { 269 panic(err) 270 } 271 272 // Remove the block from the path - make sure to remove the block at 273 // oldHeight. 274 bp := tx.Bucket(BlockPath) 275 err = bp.Delete(oldHeightBytes) 276 if build.DEBUG && err != nil { 277 panic(err) 278 } 279 } 280 281 // isSiacoinOutput returns true if there is a siacoin output of that id in the 282 // database. 283 func isSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) bool { 284 bucket := tx.Bucket(SiacoinOutputs) 285 sco := bucket.Get(id[:]) 286 return sco != nil 287 } 288 289 // getSiacoinOutput fetches a siacoin output from the database. An error is 290 // returned if the siacoin output does not exist. 291 func getSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) (types.SiacoinOutput, error) { 292 scoBytes := tx.Bucket(SiacoinOutputs).Get(id[:]) 293 if scoBytes == nil { 294 return types.SiacoinOutput{}, errNilItem 295 } 296 var sco types.SiacoinOutput 297 err := encoding.Unmarshal(scoBytes, &sco) 298 if err != nil { 299 return types.SiacoinOutput{}, err 300 } 301 return sco, nil 302 } 303 304 // addSiacoinOutput adds a siacoin output to the database. An error is returned 305 // if the siacoin output is already in the database. 306 func addSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, sco types.SiacoinOutput) { 307 // While this is not supposed to be allowed, there's a bug in the consensus 308 // code which means that earlier versions have accetped 0-value outputs 309 // onto the blockchain. A hardfork to remove 0-value outputs will fix this, 310 // and that hardfork is planned, but not yet. 311 /* 312 if build.DEBUG && sco.Value.IsZero() { 313 panic("discovered a zero value siacoin output") 314 } 315 */ 316 siacoinOutputs := tx.Bucket(SiacoinOutputs) 317 // Sanity check - should not be adding an item that exists. 318 if build.DEBUG && siacoinOutputs.Get(id[:]) != nil { 319 panic("repeat siacoin output") 320 } 321 err := siacoinOutputs.Put(id[:], encoding.Marshal(sco)) 322 if build.DEBUG && err != nil { 323 panic(err) 324 } 325 } 326 327 // removeSiacoinOutput removes a siacoin output from the database. An error is 328 // returned if the siacoin output is not in the database prior to removal. 329 func removeSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) { 330 scoBucket := tx.Bucket(SiacoinOutputs) 331 // Sanity check - should not be removing an item that is not in the db. 332 if build.DEBUG && scoBucket.Get(id[:]) == nil { 333 panic("nil siacoin output") 334 } 335 err := scoBucket.Delete(id[:]) 336 if build.DEBUG && err != nil { 337 panic(err) 338 } 339 } 340 341 // getFileContract fetches a file contract from the database, returning an 342 // error if it is not there. 343 func getFileContract(tx *bolt.Tx, id types.FileContractID) (fc types.FileContract, err error) { 344 fcBytes := tx.Bucket(FileContracts).Get(id[:]) 345 if fcBytes == nil { 346 return types.FileContract{}, errNilItem 347 } 348 err = encoding.Unmarshal(fcBytes, &fc) 349 if err != nil { 350 return types.FileContract{}, err 351 } 352 return fc, nil 353 } 354 355 // addFileContract adds a file contract to the database. An error is returned 356 // if the file contract is already in the database. 357 func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) { 358 // Add the file contract to the database. 359 fcBucket := tx.Bucket(FileContracts) 360 // Sanity check - should not be adding a zero-payout file contract. 361 if build.DEBUG && fc.Payout.IsZero() { 362 panic("adding zero-payout file contract") 363 } 364 // Sanity check - should not be adding a file contract already in the db. 365 if build.DEBUG && fcBucket.Get(id[:]) != nil { 366 panic("repeat file contract") 367 } 368 err := fcBucket.Put(id[:], encoding.Marshal(fc)) 369 if build.DEBUG && err != nil { 370 panic(err) 371 } 372 373 // Add an entry for when the file contract expires. 374 expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...) 375 expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID) 376 if build.DEBUG && err != nil { 377 panic(err) 378 } 379 err = expirationBucket.Put(id[:], []byte{}) 380 if build.DEBUG && err != nil { 381 panic(err) 382 } 383 } 384 385 // removeFileContract removes a file contract from the database. 386 func removeFileContract(tx *bolt.Tx, id types.FileContractID) { 387 // Delete the file contract entry. 388 fcBucket := tx.Bucket(FileContracts) 389 fcBytes := fcBucket.Get(id[:]) 390 // Sanity check - should not be removing a file contract not in the db. 391 if build.DEBUG && fcBytes == nil { 392 panic("nil file contract") 393 } 394 err := fcBucket.Delete(id[:]) 395 if build.DEBUG && err != nil { 396 panic(err) 397 } 398 399 // Delete the entry for the file contract's expiration. The portion of 400 // 'fcBytes' used to determine the expiration bucket id is the 401 // byte-representation of the file contract window end, which always 402 // appears at bytes 48-56. 403 expirationBucketID := append(prefixFCEX, fcBytes[48:56]...) 404 expirationBucket := tx.Bucket(expirationBucketID) 405 expirationBytes := expirationBucket.Get(id[:]) 406 if expirationBytes == nil { 407 panic(errNilItem) 408 } 409 err = expirationBucket.Delete(id[:]) 410 if build.DEBUG && err != nil { 411 panic(err) 412 } 413 } 414 415 // The address of the devs. 416 var devAddr = types.UnlockHash{243, 113, 199, 11, 206, 158, 184, 417 151, 156, 213, 9, 159, 89, 158, 196, 228, 252, 177, 78, 10, 418 252, 243, 31, 151, 145, 224, 62, 100, 150, 164, 192, 179} 419 420 // getSiafundOutput fetches a siafund output from the database. An error is 421 // returned if the siafund output does not exist. 422 func getSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (types.SiafundOutput, error) { 423 sfoBytes := tx.Bucket(SiafundOutputs).Get(id[:]) 424 if sfoBytes == nil { 425 return types.SiafundOutput{}, errNilItem 426 } 427 var sfo types.SiafundOutput 428 err := encoding.Unmarshal(sfoBytes, &sfo) 429 if err != nil { 430 return types.SiafundOutput{}, err 431 } 432 gsa := types.GenesisSiafundAllocation 433 if sfo.UnlockHash == gsa[len(gsa)-1].UnlockHash && blockHeight(tx) > 10e3 { 434 sfo.UnlockHash = devAddr 435 } 436 return sfo, nil 437 } 438 439 // addSiafundOutput adds a siafund output to the database. An error is returned 440 // if the siafund output is already in the database. 441 func addSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, sfo types.SiafundOutput) { 442 siafundOutputs := tx.Bucket(SiafundOutputs) 443 // Sanity check - should not be adding a siafund output with a value of 444 // zero. 445 if build.DEBUG && sfo.Value.IsZero() { 446 panic("zero value siafund being added") 447 } 448 // Sanity check - should not be adding an item already in the db. 449 if build.DEBUG && siafundOutputs.Get(id[:]) != nil { 450 panic("repeat siafund output") 451 } 452 err := siafundOutputs.Put(id[:], encoding.Marshal(sfo)) 453 if build.DEBUG && err != nil { 454 panic(err) 455 } 456 } 457 458 // removeSiafundOutput removes a siafund output from the database. An error is 459 // returned if the siafund output is not in the database prior to removal. 460 func removeSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) { 461 sfoBucket := tx.Bucket(SiafundOutputs) 462 if build.DEBUG && sfoBucket.Get(id[:]) == nil { 463 panic("nil siafund output") 464 } 465 err := sfoBucket.Delete(id[:]) 466 if build.DEBUG && err != nil { 467 panic(err) 468 } 469 } 470 471 // getSiafundPool returns the current value of the siafund pool. No error is 472 // returned as the siafund pool should always be available. 473 func getSiafundPool(tx *bolt.Tx) (pool types.Currency) { 474 bucket := tx.Bucket(SiafundPool) 475 poolBytes := bucket.Get(SiafundPool) 476 // An error should only be returned if the object stored in the siafund 477 // pool bucket is either unavailable or otherwise malformed. As this is a 478 // developer error, a panic is appropriate. 479 err := encoding.Unmarshal(poolBytes, &pool) 480 if build.DEBUG && err != nil { 481 panic(err) 482 } 483 return pool 484 } 485 486 // setSiafundPool updates the saved siafund pool on disk 487 func setSiafundPool(tx *bolt.Tx, c types.Currency) { 488 err := tx.Bucket(SiafundPool).Put(SiafundPool, encoding.Marshal(c)) 489 if build.DEBUG && err != nil { 490 panic(err) 491 } 492 } 493 494 // addDSCO adds a delayed siacoin output to the consnesus set. 495 func addDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID, sco types.SiacoinOutput) { 496 // Sanity check - dsco should never have a value of zero. 497 // An error in the consensus code means sometimes there are 0-value dscos 498 // in the blockchain. A hardfork will fix this. 499 /* 500 if build.DEBUG && sco.Value.IsZero() { 501 panic("zero-value dsco being added") 502 } 503 */ 504 // Sanity check - output should not already be in the full set of outputs. 505 if build.DEBUG && tx.Bucket(SiacoinOutputs).Get(id[:]) != nil { 506 panic("dsco already in output set") 507 } 508 dscoBucketID := append(prefixDSCO, encoding.EncUint64(uint64(bh))...) 509 dscoBucket := tx.Bucket(dscoBucketID) 510 // Sanity check - should not be adding an item already in the db. 511 if build.DEBUG && dscoBucket.Get(id[:]) != nil { 512 panic(errRepeatInsert) 513 } 514 err := dscoBucket.Put(id[:], encoding.Marshal(sco)) 515 if build.DEBUG && err != nil { 516 panic(err) 517 } 518 } 519 520 // removeDSCO removes a delayed siacoin output from the consensus set. 521 func removeDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID) { 522 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 523 // Sanity check - should not remove an item not in the db. 524 dscoBucket := tx.Bucket(bucketID) 525 if build.DEBUG && dscoBucket.Get(id[:]) == nil { 526 panic("nil dsco") 527 } 528 err := dscoBucket.Delete(id[:]) 529 if build.DEBUG && err != nil { 530 panic(err) 531 } 532 } 533 534 // createDSCOBucket creates a bucket for the delayed siacoin outputs at the 535 // input height. 536 func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { 537 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 538 _, err := tx.CreateBucket(bucketID) 539 if build.DEBUG && err != nil { 540 panic(err) 541 } 542 } 543 544 // deleteDSCOBucket deletes the bucket that held a set of delayed siacoin 545 // outputs. 546 func deleteDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { 547 // Delete the bucket. 548 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 549 bucket := tx.Bucket(bucketID) 550 if build.DEBUG && bucket == nil { 551 panic(errNilBucket) 552 } 553 554 // TODO: Check that the bucket is empty. Using Stats() does not work at the 555 // moment, as there is an error in the boltdb code. 556 557 err := tx.DeleteBucket(bucketID) 558 if build.DEBUG && err != nil { 559 panic(err) 560 } 561 }