github.com/NebulousLabs/Sia@v1.3.7/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 "github.com/NebulousLabs/Sia/build" 11 "github.com/NebulousLabs/Sia/encoding" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/types" 14 15 "github.com/coreos/bbolt" 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 // Set the siafund pool to 0. 115 setSiafundPool(tx, types.NewCurrency64(0)) 116 117 // Update the siafund output diffs map for the genesis block on disk. This 118 // needs to happen between the database being opened/initilized and the 119 // consensus set hash being calculated 120 for _, sfod := range cs.blockRoot.SiafundOutputDiffs { 121 commitSiafundOutputDiff(tx, sfod, modules.DiffApply) 122 } 123 124 // Add the miner payout from the genesis block to the delayed siacoin 125 // outputs - unspendable, as the unlock hash is blank. 126 createDSCOBucket(tx, types.MaturityDelay) 127 addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{ 128 Value: types.CalculateCoinbase(0), 129 UnlockHash: types.UnlockHash{}, 130 }) 131 132 // Add the genesis block to the block structures - checksum must be taken 133 // after pushing the genesis block into the path. 134 pushPath(tx, cs.blockRoot.Block.ID()) 135 if build.DEBUG { 136 cs.blockRoot.ConsensusChecksum = consensusChecksum(tx) 137 } 138 addBlockMap(tx, &cs.blockRoot) 139 return nil 140 } 141 142 // blockHeight returns the height of the blockchain. 143 func blockHeight(tx *bolt.Tx) types.BlockHeight { 144 var height types.BlockHeight 145 bh := tx.Bucket(BlockHeight) 146 err := encoding.Unmarshal(bh.Get(BlockHeight), &height) 147 if build.DEBUG && err != nil { 148 panic(err) 149 } 150 return height 151 } 152 153 // currentBlockID returns the id of the most recent block in the consensus set. 154 func currentBlockID(tx *bolt.Tx) types.BlockID { 155 id, err := getPath(tx, blockHeight(tx)) 156 if build.DEBUG && err != nil { 157 panic(err) 158 } 159 return id 160 } 161 162 // dbCurrentBlockID is a convenience function allowing currentBlockID to be 163 // called without a bolt.Tx. 164 func (cs *ConsensusSet) dbCurrentBlockID() (id types.BlockID) { 165 dbErr := cs.db.View(func(tx *bolt.Tx) error { 166 id = currentBlockID(tx) 167 return nil 168 }) 169 if dbErr != nil { 170 panic(dbErr) 171 } 172 return id 173 } 174 175 // currentProcessedBlock returns the most recent block in the consensus set. 176 func currentProcessedBlock(tx *bolt.Tx) *processedBlock { 177 pb, err := getBlockMap(tx, currentBlockID(tx)) 178 if build.DEBUG && err != nil { 179 panic(err) 180 } 181 return pb 182 } 183 184 // getBlockMap returns a processed block with the input id. 185 func getBlockMap(tx *bolt.Tx, id types.BlockID) (*processedBlock, error) { 186 // Look up the encoded block. 187 pbBytes := tx.Bucket(BlockMap).Get(id[:]) 188 if pbBytes == nil { 189 return nil, errNilItem 190 } 191 192 // Decode the block - should never fail. 193 var pb processedBlock 194 err := encoding.Unmarshal(pbBytes, &pb) 195 if build.DEBUG && err != nil { 196 panic(err) 197 } 198 return &pb, nil 199 } 200 201 // addBlockMap adds a processed block to the block map. 202 func addBlockMap(tx *bolt.Tx, pb *processedBlock) { 203 id := pb.Block.ID() 204 err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb)) 205 if build.DEBUG && err != nil { 206 panic(err) 207 } 208 } 209 210 // getPath returns the block id at 'height' in the block path. 211 func getPath(tx *bolt.Tx, height types.BlockHeight) (id types.BlockID, err error) { 212 idBytes := tx.Bucket(BlockPath).Get(encoding.Marshal(height)) 213 if idBytes == nil { 214 return types.BlockID{}, errNilItem 215 } 216 217 err = encoding.Unmarshal(idBytes, &id) 218 if build.DEBUG && err != nil { 219 panic(err) 220 } 221 return id, nil 222 } 223 224 // pushPath adds a block to the BlockPath at current height + 1. 225 func pushPath(tx *bolt.Tx, bid types.BlockID) { 226 // Fetch and update the block height. 227 bh := tx.Bucket(BlockHeight) 228 heightBytes := bh.Get(BlockHeight) 229 var oldHeight types.BlockHeight 230 err := encoding.Unmarshal(heightBytes, &oldHeight) 231 if build.DEBUG && err != nil { 232 panic(err) 233 } 234 newHeightBytes := encoding.Marshal(oldHeight + 1) 235 err = bh.Put(BlockHeight, newHeightBytes) 236 if build.DEBUG && err != nil { 237 panic(err) 238 } 239 240 // Add the block to the block path. 241 bp := tx.Bucket(BlockPath) 242 err = bp.Put(newHeightBytes, bid[:]) 243 if build.DEBUG && err != nil { 244 panic(err) 245 } 246 } 247 248 // popPath removes a block from the "end" of the chain, i.e. the block 249 // with the largest height. 250 func popPath(tx *bolt.Tx) { 251 // Fetch and update the block height. 252 bh := tx.Bucket(BlockHeight) 253 oldHeightBytes := bh.Get(BlockHeight) 254 var oldHeight types.BlockHeight 255 err := encoding.Unmarshal(oldHeightBytes, &oldHeight) 256 if build.DEBUG && err != nil { 257 panic(err) 258 } 259 newHeightBytes := encoding.Marshal(oldHeight - 1) 260 err = bh.Put(BlockHeight, newHeightBytes) 261 if build.DEBUG && err != nil { 262 panic(err) 263 } 264 265 // Remove the block from the path - make sure to remove the block at 266 // oldHeight. 267 bp := tx.Bucket(BlockPath) 268 err = bp.Delete(oldHeightBytes) 269 if build.DEBUG && err != nil { 270 panic(err) 271 } 272 } 273 274 // isSiacoinOutput returns true if there is a siacoin output of that id in the 275 // database. 276 func isSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) bool { 277 bucket := tx.Bucket(SiacoinOutputs) 278 sco := bucket.Get(id[:]) 279 return sco != nil 280 } 281 282 // getSiacoinOutput fetches a siacoin output from the database. An error is 283 // returned if the siacoin output does not exist. 284 func getSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) (types.SiacoinOutput, error) { 285 scoBytes := tx.Bucket(SiacoinOutputs).Get(id[:]) 286 if scoBytes == nil { 287 return types.SiacoinOutput{}, errNilItem 288 } 289 var sco types.SiacoinOutput 290 err := encoding.Unmarshal(scoBytes, &sco) 291 if err != nil { 292 return types.SiacoinOutput{}, err 293 } 294 return sco, nil 295 } 296 297 // addSiacoinOutput adds a siacoin output to the database. An error is returned 298 // if the siacoin output is already in the database. 299 func addSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, sco types.SiacoinOutput) { 300 // While this is not supposed to be allowed, there's a bug in the consensus 301 // code which means that earlier versions have accetped 0-value outputs 302 // onto the blockchain. A hardfork to remove 0-value outputs will fix this, 303 // and that hardfork is planned, but not yet. 304 /* 305 if build.DEBUG && sco.Value.IsZero() { 306 panic("discovered a zero value siacoin output") 307 } 308 */ 309 siacoinOutputs := tx.Bucket(SiacoinOutputs) 310 // Sanity check - should not be adding an item that exists. 311 if build.DEBUG && siacoinOutputs.Get(id[:]) != nil { 312 panic("repeat siacoin output") 313 } 314 err := siacoinOutputs.Put(id[:], encoding.Marshal(sco)) 315 if build.DEBUG && err != nil { 316 panic(err) 317 } 318 } 319 320 // removeSiacoinOutput removes a siacoin output from the database. An error is 321 // returned if the siacoin output is not in the database prior to removal. 322 func removeSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) { 323 scoBucket := tx.Bucket(SiacoinOutputs) 324 // Sanity check - should not be removing an item that is not in the db. 325 if build.DEBUG && scoBucket.Get(id[:]) == nil { 326 panic("nil siacoin output") 327 } 328 err := scoBucket.Delete(id[:]) 329 if build.DEBUG && err != nil { 330 panic(err) 331 } 332 } 333 334 // getFileContract fetches a file contract from the database, returning an 335 // error if it is not there. 336 func getFileContract(tx *bolt.Tx, id types.FileContractID) (fc types.FileContract, err error) { 337 fcBytes := tx.Bucket(FileContracts).Get(id[:]) 338 if fcBytes == nil { 339 return types.FileContract{}, errNilItem 340 } 341 err = encoding.Unmarshal(fcBytes, &fc) 342 if err != nil { 343 return types.FileContract{}, err 344 } 345 return fc, nil 346 } 347 348 // addFileContract adds a file contract to the database. An error is returned 349 // if the file contract is already in the database. 350 func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) { 351 // Add the file contract to the database. 352 fcBucket := tx.Bucket(FileContracts) 353 // Sanity check - should not be adding a zero-payout file contract. 354 if build.DEBUG && fc.Payout.IsZero() { 355 panic("adding zero-payout file contract") 356 } 357 // Sanity check - should not be adding a file contract already in the db. 358 if build.DEBUG && fcBucket.Get(id[:]) != nil { 359 panic("repeat file contract") 360 } 361 err := fcBucket.Put(id[:], encoding.Marshal(fc)) 362 if build.DEBUG && err != nil { 363 panic(err) 364 } 365 366 // Add an entry for when the file contract expires. 367 expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...) 368 expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID) 369 if build.DEBUG && err != nil { 370 panic(err) 371 } 372 err = expirationBucket.Put(id[:], []byte{}) 373 if build.DEBUG && err != nil { 374 panic(err) 375 } 376 } 377 378 // removeFileContract removes a file contract from the database. 379 func removeFileContract(tx *bolt.Tx, id types.FileContractID) { 380 // Delete the file contract entry. 381 fcBucket := tx.Bucket(FileContracts) 382 fcBytes := fcBucket.Get(id[:]) 383 // Sanity check - should not be removing a file contract not in the db. 384 if build.DEBUG && fcBytes == nil { 385 panic("nil file contract") 386 } 387 err := fcBucket.Delete(id[:]) 388 if build.DEBUG && err != nil { 389 panic(err) 390 } 391 392 // Delete the entry for the file contract's expiration. The portion of 393 // 'fcBytes' used to determine the expiration bucket id is the 394 // byte-representation of the file contract window end, which always 395 // appears at bytes 48-56. 396 expirationBucketID := append(prefixFCEX, fcBytes[48:56]...) 397 expirationBucket := tx.Bucket(expirationBucketID) 398 expirationBytes := expirationBucket.Get(id[:]) 399 if expirationBytes == nil { 400 panic(errNilItem) 401 } 402 err = expirationBucket.Delete(id[:]) 403 if build.DEBUG && err != nil { 404 panic(err) 405 } 406 } 407 408 // The address of the devs. 409 var devAddr = types.UnlockHash{243, 113, 199, 11, 206, 158, 184, 410 151, 156, 213, 9, 159, 89, 158, 196, 228, 252, 177, 78, 10, 411 252, 243, 31, 151, 145, 224, 62, 100, 150, 164, 192, 179} 412 413 // getSiafundOutput fetches a siafund output from the database. An error is 414 // returned if the siafund output does not exist. 415 func getSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (types.SiafundOutput, error) { 416 sfoBytes := tx.Bucket(SiafundOutputs).Get(id[:]) 417 if sfoBytes == nil { 418 return types.SiafundOutput{}, errNilItem 419 } 420 var sfo types.SiafundOutput 421 err := encoding.Unmarshal(sfoBytes, &sfo) 422 if err != nil { 423 return types.SiafundOutput{}, err 424 } 425 gsa := types.GenesisSiafundAllocation 426 if sfo.UnlockHash == gsa[len(gsa)-1].UnlockHash && blockHeight(tx) > 10e3 { 427 sfo.UnlockHash = devAddr 428 } 429 return sfo, nil 430 } 431 432 // addSiafundOutput adds a siafund output to the database. An error is returned 433 // if the siafund output is already in the database. 434 func addSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, sfo types.SiafundOutput) { 435 siafundOutputs := tx.Bucket(SiafundOutputs) 436 // Sanity check - should not be adding a siafund output with a value of 437 // zero. 438 if build.DEBUG && sfo.Value.IsZero() { 439 panic("zero value siafund being added") 440 } 441 // Sanity check - should not be adding an item already in the db. 442 if build.DEBUG && siafundOutputs.Get(id[:]) != nil { 443 panic("repeat siafund output") 444 } 445 err := siafundOutputs.Put(id[:], encoding.Marshal(sfo)) 446 if build.DEBUG && err != nil { 447 panic(err) 448 } 449 } 450 451 // removeSiafundOutput removes a siafund output from the database. An error is 452 // returned if the siafund output is not in the database prior to removal. 453 func removeSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) { 454 sfoBucket := tx.Bucket(SiafundOutputs) 455 if build.DEBUG && sfoBucket.Get(id[:]) == nil { 456 panic("nil siafund output") 457 } 458 err := sfoBucket.Delete(id[:]) 459 if build.DEBUG && err != nil { 460 panic(err) 461 } 462 } 463 464 // getSiafundPool returns the current value of the siafund pool. No error is 465 // returned as the siafund pool should always be available. 466 func getSiafundPool(tx *bolt.Tx) (pool types.Currency) { 467 bucket := tx.Bucket(SiafundPool) 468 poolBytes := bucket.Get(SiafundPool) 469 // An error should only be returned if the object stored in the siafund 470 // pool bucket is either unavailable or otherwise malformed. As this is a 471 // developer error, a panic is appropriate. 472 err := encoding.Unmarshal(poolBytes, &pool) 473 if build.DEBUG && err != nil { 474 panic(err) 475 } 476 return pool 477 } 478 479 // setSiafundPool updates the saved siafund pool on disk 480 func setSiafundPool(tx *bolt.Tx, c types.Currency) { 481 err := tx.Bucket(SiafundPool).Put(SiafundPool, encoding.Marshal(c)) 482 if build.DEBUG && err != nil { 483 panic(err) 484 } 485 } 486 487 // addDSCO adds a delayed siacoin output to the consnesus set. 488 func addDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID, sco types.SiacoinOutput) { 489 // Sanity check - dsco should never have a value of zero. 490 // An error in the consensus code means sometimes there are 0-value dscos 491 // in the blockchain. A hardfork will fix this. 492 /* 493 if build.DEBUG && sco.Value.IsZero() { 494 panic("zero-value dsco being added") 495 } 496 */ 497 // Sanity check - output should not already be in the full set of outputs. 498 if build.DEBUG && tx.Bucket(SiacoinOutputs).Get(id[:]) != nil { 499 panic("dsco already in output set") 500 } 501 dscoBucketID := append(prefixDSCO, encoding.EncUint64(uint64(bh))...) 502 dscoBucket := tx.Bucket(dscoBucketID) 503 // Sanity check - should not be adding an item already in the db. 504 if build.DEBUG && dscoBucket.Get(id[:]) != nil { 505 panic(errRepeatInsert) 506 } 507 err := dscoBucket.Put(id[:], encoding.Marshal(sco)) 508 if build.DEBUG && err != nil { 509 panic(err) 510 } 511 } 512 513 // removeDSCO removes a delayed siacoin output from the consensus set. 514 func removeDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID) { 515 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 516 // Sanity check - should not remove an item not in the db. 517 dscoBucket := tx.Bucket(bucketID) 518 if build.DEBUG && dscoBucket.Get(id[:]) == nil { 519 panic("nil dsco") 520 } 521 err := dscoBucket.Delete(id[:]) 522 if build.DEBUG && err != nil { 523 panic(err) 524 } 525 } 526 527 // createDSCOBucket creates a bucket for the delayed siacoin outputs at the 528 // input height. 529 func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { 530 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 531 _, err := tx.CreateBucket(bucketID) 532 if build.DEBUG && err != nil { 533 panic(err) 534 } 535 } 536 537 // deleteDSCOBucket deletes the bucket that held a set of delayed siacoin 538 // outputs. 539 func deleteDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { 540 // Delete the bucket. 541 bucketID := append(prefixDSCO, encoding.Marshal(bh)...) 542 bucket := tx.Bucket(bucketID) 543 if build.DEBUG && bucket == nil { 544 panic(errNilBucket) 545 } 546 547 // TODO: Check that the bucket is empty. Using Stats() does not work at the 548 // moment, as there is an error in the boltdb code. 549 550 err := tx.DeleteBucket(bucketID) 551 if build.DEBUG && err != nil { 552 panic(err) 553 } 554 }