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