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