gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/storageobligations.go (about) 1 package host 2 3 // storageobligations.go is responsible for managing the storage obligations 4 // within the host - making sure that any file contracts, transaction 5 // dependencies, file contract revisions, and storage proofs are making it into 6 // the blockchain in a reasonable time. 7 // 8 // NOTE: Currently, the code partially supports changing the storage proof 9 // window in file contract revisions, however the action item code will not 10 // handle it correctly. Until the action item code is improved (to also handle 11 // byzantine situations where the renter submits prior revisions), the host 12 // should not support changing the storage proof window, especially to further 13 // in the future. 14 15 // TODO: Need to queue the action item for checking on the submission status of 16 // the file contract revision. Also need to make sure that multiple actions are 17 // being taken if needed. 18 19 // TODO: Make sure that the origin tranasction set is not submitted to the 20 // transaction pool before addSO is called - if it is, there will be a 21 // duplicate transaction error, and then the storage obligation will return an 22 // error, which is bad. Well, or perhas we just need to have better logic 23 // handling. 24 25 // TODO: Need to make sure that 'revision confirmed' is actually looking only 26 // at the most recent revision (I think it is...) 27 28 // TODO: Make sure that not too many action items are being created. 29 30 // TODO: The ProofConstructed field of storageObligation 31 // is not set or used. 32 33 import ( 34 "encoding/binary" 35 "encoding/json" 36 "errors" 37 "strconv" 38 39 bolt "github.com/coreos/bbolt" 40 "gitlab.com/SiaPrime/SiaPrime/build" 41 "gitlab.com/SiaPrime/SiaPrime/crypto" 42 "gitlab.com/SiaPrime/SiaPrime/encoding" 43 "gitlab.com/SiaPrime/SiaPrime/modules" 44 "gitlab.com/SiaPrime/SiaPrime/modules/wallet" 45 "gitlab.com/SiaPrime/SiaPrime/types" 46 ) 47 48 const ( 49 obligationUnresolved storageObligationStatus = iota // Indicatees that an unitialized value was used. 50 obligationRejected // Indicates that the obligation never got started, no revenue gained or lost. 51 obligationSucceeded // Indicates that the obligation was completed, revenues were gained. 52 obligationFailed // Indicates that the obligation failed, revenues and collateral were lost. 53 ) 54 55 var ( 56 // errDuplicateStorageObligation is returned when the storage obligation 57 // database already has a storage obligation with the provided file 58 // contract. This error should only happen in the event of a developer 59 // mistake. 60 errDuplicateStorageObligation = errors.New("storage obligation has a file contract which conflicts with an existing storage obligation") 61 62 // errInsaneFileContractOutputCounts is returned when a file contract has 63 // the wrong number of outputs for either the valid or missed payouts. 64 errInsaneFileContractOutputCounts = errors.New("file contract has incorrect number of outputs for the valid or missed payouts") 65 66 // errInsaneFileContractRevisionOutputCounts is returned when a file 67 // contract has the wrong number of outputs for either the valid or missed 68 // payouts. 69 errInsaneFileContractRevisionOutputCounts = errors.New("file contract revision has incorrect number of outputs for the valid or missed payouts") 70 71 // errInsaneOriginSetFileContract is returned is the final transaction of 72 // the origin transaction set of a storage obligation does not have a file 73 // contract in the final transaction - there should be a file contract 74 // associated with every storage obligation. 75 errInsaneOriginSetFileContract = errors.New("origin transaction set of storage obligation should have one file contract in the final transaction") 76 77 // errInsaneOriginSetSize is returned if the origin transaction set of a 78 // storage obligation is empty - there should be a file contract associated 79 // with every storage obligation. 80 errInsaneOriginSetSize = errors.New("origin transaction set of storage obligation is size zero") 81 82 // errInsaneRevisionSetRevisionCount is returned if the final transaction 83 // in the revision transaction set of a storage obligation has more or less 84 // than one file contract revision. 85 errInsaneRevisionSetRevisionCount = errors.New("revision transaction set of storage obligation should have one file contract revision in the final transaction") 86 87 // errInsaneStorageObligationRevision is returned if there is an attempted 88 // storage obligation revision which does not have sensical inputs. 89 errInsaneStorageObligationRevision = errors.New("revision to storage obligation does not make sense") 90 91 // errInsaneStorageObligationRevisionData is returned if there is an 92 // attempted storage obligation revision which does not have sensical 93 // inputs. 94 errInsaneStorageObligationRevisionData = errors.New("revision to storage obligation has insane data") 95 96 // errNoBuffer is returned if there is an attempted storage obligation that 97 // needs to have the storage proof submitted in less than 98 // revisionSubmissionBuffer blocks. 99 errNoBuffer = errors.New("file contract rejected because storage proof window is too close") 100 101 // errNoStorageObligation is returned if the requested storage obligation 102 // is not found in the database. 103 errNoStorageObligation = errors.New("storage obligation not found in database") 104 105 // errObligationUnlocked is returned when a storage obligation is being 106 // removed from lock, but is already unlocked. 107 errObligationUnlocked = errors.New("storage obligation is unlocked, and should not be getting unlocked") 108 ) 109 110 type storageObligationStatus uint64 111 112 // storageObligation contains all of the metadata related to a file contract 113 // and the storage contained by the file contract. 114 type storageObligation struct { 115 // Storage obligations are broken up into ordered atomic sectors that are 116 // exactly 4MiB each. By saving the roots of each sector, storage proofs 117 // and modifications to the data can be made inexpensively by making use of 118 // the merkletree.CachedTree. Sectors can be appended, modified, or deleted 119 // and the host can recompute the Merkle root of the whole file without 120 // much computational or I/O expense. 121 SectorRoots []crypto.Hash 122 123 // Variables about the file contract that enforces the storage obligation. 124 // The origin an revision transaction are stored as a set, where the set 125 // contains potentially unconfirmed transactions. 126 ContractCost types.Currency 127 LockedCollateral types.Currency 128 PotentialDownloadRevenue types.Currency 129 PotentialStorageRevenue types.Currency 130 PotentialUploadRevenue types.Currency 131 RiskedCollateral types.Currency 132 TransactionFeesAdded types.Currency 133 134 // The negotiation height specifies the block height at which the file 135 // contract was negotiated. If the origin transaction set is not accepted 136 // onto the blockchain quickly enough, the contract is pruned from the 137 // host. The origin and revision transaction set contain the contracts + 138 // revisions as well as all parent transactions. The parents are necessary 139 // because after a restart the transaction pool may be emptied out. 140 NegotiationHeight types.BlockHeight 141 OriginTransactionSet []types.Transaction 142 RevisionTransactionSet []types.Transaction 143 144 // Variables indicating whether the critical transactions in a storage 145 // obligation have been confirmed on the blockchain. 146 ObligationStatus storageObligationStatus 147 OriginConfirmed bool 148 ProofConfirmed bool 149 ProofConstructed bool 150 RevisionConfirmed bool 151 RevisionConstructed bool 152 } 153 154 func (i storageObligationStatus) String() string { 155 if i == 0 { 156 return "obligationUnresolved" 157 } 158 if i == 1 { 159 return "obligationRejected" 160 } 161 if i == 2 { 162 return "obligationSucceeded" 163 } 164 if i == 3 { 165 return "obligationFailed" 166 } 167 return "storageObligationStatus(" + strconv.FormatInt(int64(i), 10) + ")" 168 } 169 170 // getStorageObligation fetches a storage obligation from the database tx. 171 func getStorageObligation(tx *bolt.Tx, soid types.FileContractID) (so storageObligation, err error) { 172 soBytes := tx.Bucket(bucketStorageObligations).Get(soid[:]) 173 if soBytes == nil { 174 return storageObligation{}, errNoStorageObligation 175 } 176 err = json.Unmarshal(soBytes, &so) 177 if err != nil { 178 return storageObligation{}, err 179 } 180 return so, nil 181 } 182 183 // putStorageObligation places a storage obligation into the database, 184 // overwriting the existing storage obligation if there is one. 185 func putStorageObligation(tx *bolt.Tx, so storageObligation) error { 186 soBytes, err := json.Marshal(so) 187 if err != nil { 188 return err 189 } 190 soid := so.id() 191 return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes) 192 } 193 194 // expiration returns the height at which the storage obligation expires. 195 func (so storageObligation) expiration() types.BlockHeight { 196 if len(so.RevisionTransactionSet) > 0 { 197 return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowStart 198 } 199 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowStart 200 } 201 202 // fileSize returns the size of the data protected by the obligation. 203 func (so storageObligation) fileSize() uint64 { 204 if len(so.RevisionTransactionSet) > 0 { 205 return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileSize 206 } 207 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileSize 208 } 209 210 // id returns the id of the storage obligation, which is defined by the file 211 // contract id of the file contract that governs the storage contract. 212 func (so storageObligation) id() types.FileContractID { 213 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContractID(0) 214 } 215 216 // isSane checks that required assumptions about the storage obligation are 217 // correct. 218 func (so storageObligation) isSane() error { 219 // There should be an origin transaction set. 220 if len(so.OriginTransactionSet) == 0 { 221 build.Critical("origin transaction set is empty") 222 return errInsaneOriginSetSize 223 } 224 225 // The final transaction of the origin transaction set should have one file 226 // contract. 227 final := len(so.OriginTransactionSet) - 1 228 fcCount := len(so.OriginTransactionSet[final].FileContracts) 229 if fcCount != 1 { 230 build.Critical("wrong number of file contracts associated with storage obligation:", fcCount) 231 return errInsaneOriginSetFileContract 232 } 233 234 // The file contract in the final transaction of the origin transaction set 235 // should have two valid proof outputs and two missed proof outputs. 236 lenVPOs := len(so.OriginTransactionSet[final].FileContracts[0].ValidProofOutputs) 237 lenMPOs := len(so.OriginTransactionSet[final].FileContracts[0].MissedProofOutputs) 238 if lenVPOs != 2 || lenMPOs != 2 { 239 build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs) 240 return errInsaneFileContractOutputCounts 241 } 242 243 // If there is a revision transaction set, there should be one file 244 // contract revision in the final transaction. 245 if len(so.RevisionTransactionSet) > 0 { 246 final = len(so.OriginTransactionSet) - 1 247 fcrCount := len(so.OriginTransactionSet[final].FileContractRevisions) 248 if fcrCount != 1 { 249 build.Critical("wrong number of file contract revisions in final transaction of revision transaction set:", fcrCount) 250 return errInsaneRevisionSetRevisionCount 251 } 252 253 // The file contract revision in the final transaction of the revision 254 // transaction set should have two valid proof outputs and two missed 255 // proof outputs. 256 lenVPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewValidProofOutputs) 257 lenMPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewMissedProofOutputs) 258 if lenVPOs != 2 || lenMPOs != 2 { 259 build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs) 260 return errInsaneFileContractRevisionOutputCounts 261 } 262 } 263 return nil 264 } 265 266 // merkleRoot returns the file merkle root of a storage obligation. 267 func (so storageObligation) merkleRoot() crypto.Hash { 268 if len(so.RevisionTransactionSet) > 0 { 269 return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileMerkleRoot 270 } 271 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileMerkleRoot 272 } 273 274 // payouts returns the set of valid payouts and missed payouts that represent 275 // the latest revision for the storage obligation. 276 func (so storageObligation) payouts() (valid []types.SiacoinOutput, missed []types.SiacoinOutput) { 277 valid = make([]types.SiacoinOutput, 2) 278 missed = make([]types.SiacoinOutput, 2) 279 if len(so.RevisionTransactionSet) > 0 { 280 copy(valid, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewValidProofOutputs) 281 copy(missed, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewMissedProofOutputs) 282 return 283 } 284 copy(valid, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].ValidProofOutputs) 285 copy(missed, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].MissedProofOutputs) 286 return 287 } 288 289 // proofDeadline returns the height by which the storage proof must be 290 // submitted. 291 func (so storageObligation) proofDeadline() types.BlockHeight { 292 if len(so.RevisionTransactionSet) > 0 { 293 return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowEnd 294 } 295 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowEnd 296 } 297 298 // transactionID returns the ID of the transaction containing the file 299 // contract. 300 func (so storageObligation) transactionID() types.TransactionID { 301 return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].ID() 302 } 303 304 // value returns the value of fulfilling the storage obligation to the host. 305 func (so storageObligation) value() types.Currency { 306 return so.ContractCost.Add(so.PotentialDownloadRevenue).Add(so.PotentialStorageRevenue).Add(so.PotentialUploadRevenue).Add(so.RiskedCollateral) 307 } 308 309 // deleteStorageObligations deletes obligations from the database. 310 // It is assumed the deleted obligations don't belong in the database in the first place, 311 // so no financial metrics are updated. 312 func (h *Host) deleteStorageObligations(soids []types.FileContractID) error { 313 h.mu.RLock() 314 defer h.mu.RUnlock() 315 err := h.db.Update(func(tx *bolt.Tx) error { 316 // Delete obligations. 317 b := tx.Bucket(bucketStorageObligations) 318 for _, soid := range soids { 319 err := b.Delete([]byte(soid[:])) 320 if err != nil { 321 return build.ExtendErr("unable to delete transaction id:", err) 322 } 323 } 324 return nil 325 }) 326 if err != nil { 327 h.log.Println(build.ExtendErr("database failed to delete storage obligations:", err)) 328 return err 329 } 330 return nil 331 } 332 333 // queueActionItem adds an action item to the host at the input height so that 334 // the host knows to perform maintenance on the associated storage obligation 335 // when that height is reached. 336 func (h *Host) queueActionItem(height types.BlockHeight, id types.FileContractID) error { 337 // Sanity check - action item should be at a higher height than the current 338 // block height. 339 if height <= h.blockHeight { 340 h.log.Println("action item queued improperly") 341 } 342 return h.db.Update(func(tx *bolt.Tx) error { 343 // Translate the height into a byte slice. 344 heightBytes := make([]byte, 8) 345 binary.BigEndian.PutUint64(heightBytes, uint64(height)) 346 347 // Get the list of action items already at this height and extend it. 348 bai := tx.Bucket(bucketActionItems) 349 existingItems := bai.Get(heightBytes) 350 var extendedItems = make([]byte, len(existingItems), len(existingItems)+len(id[:])) 351 copy(extendedItems, existingItems) 352 extendedItems = append(extendedItems, id[:]...) 353 return bai.Put(heightBytes, extendedItems) 354 }) 355 } 356 357 // managedAddStorageObligation adds a storage obligation to the host. Because 358 // this operation can return errors, the transactions should not be submitted to 359 // the blockchain until after this function has indicated success. All of the 360 // sectors that are present in the storage obligation should already be on disk, 361 // which means that addStorageObligation should be exclusively called when 362 // creating a new, empty file contract or when renewing an existing file 363 // contract. 364 func (h *Host) managedAddStorageObligation(so storageObligation) error { 365 soid := so.id() 366 367 err := func() error { 368 h.mu.Lock() 369 defer h.mu.Unlock() 370 371 // Sanity check - obligation should be under lock while being added. 372 _, exists := h.lockedStorageObligations[soid] 373 if !exists { 374 h.log.Critical("addStorageObligation called with an obligation that is not locked") 375 } 376 // Sanity check - There needs to be enough time left on the file contract 377 // for the host to safely submit the file contract revision. 378 if h.blockHeight+revisionSubmissionBuffer >= so.expiration() { 379 h.log.Critical("submission window was not verified before trying to submit a storage obligation") 380 return errNoBuffer 381 } 382 // Sanity check - the resubmission timeout needs to be smaller than storage 383 // proof window. 384 if so.expiration()+resubmissionTimeout >= so.proofDeadline() { 385 h.log.Critical("host is misconfigured - the storage proof window needs to be long enough to resubmit if needed") 386 return errors.New("fill me in") 387 } 388 389 // Add the storage obligation information to the database. 390 err := h.db.Update(func(tx *bolt.Tx) error { 391 // Sanity check - a storage obligation using the same file contract id 392 // should not already exist. This situation can happen if the 393 // transaction pool ejects a file contract and then a new one is 394 // created. Though the file contract will have the same terms, some 395 // other conditions might cause problems. The check for duplicate file 396 // contract ids should happen during the negotiation phase, and not 397 // during the 'addStorageObligation' phase. 398 bso := tx.Bucket(bucketStorageObligations) 399 400 // If the storage obligation already has sectors, it means that the 401 // file contract is being renewed, and that the sector should be 402 // re-added with a new expiration height. If there is an error at any 403 // point, all of the sectors should be removed. 404 renewal := len(so.SectorRoots) > 0 405 if renewal { 406 err := h.AddSectorBatch(so.SectorRoots) 407 if err != nil { 408 return err 409 } 410 } 411 412 // Add the storage obligation to the database. 413 soBytes, err := json.Marshal(so) 414 if err == nil { 415 err = bso.Put(soid[:], soBytes) 416 } 417 if renewal && err != nil { 418 _ = h.RemoveSectorBatch(so.SectorRoots) 419 } 420 return err 421 }) 422 return err 423 }() 424 if err != nil { 425 return err 426 } 427 428 // Check that the transaction is fully valid and submit it to the 429 // transaction pool. 430 err = h.tpool.AcceptTransactionSet(so.OriginTransactionSet) 431 if err != nil { 432 h.log.Println("Failed to add storage obligation, transaction set was not accepted:", err) 433 return err 434 } 435 436 // Queue the action items. 437 h.mu.Lock() 438 defer h.mu.Unlock() 439 440 // Update the host financial metrics with regards to this storage 441 // obligation. 442 h.financialMetrics.ContractCount++ 443 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Add(so.ContractCost) 444 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Add(so.LockedCollateral) 445 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Add(so.PotentialStorageRevenue) 446 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue) 447 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue) 448 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Add(so.RiskedCollateral) 449 h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Add(so.TransactionFeesAdded) 450 451 // The file contract was already submitted to the blockchain, need to check 452 // after the resubmission timeout that it was submitted successfully. 453 err1 := h.queueActionItem(h.blockHeight+resubmissionTimeout, soid) 454 err2 := h.queueActionItem(h.blockHeight+resubmissionTimeout*2, soid) // Paranoia 455 // Queue an action item to submit the file contract revision - if there is 456 // never a file contract revision, the handling of this action item will be 457 // a no-op. 458 err3 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer, soid) 459 err4 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer+resubmissionTimeout, soid) // Paranoia 460 // The storage proof should be submitted 461 err5 := h.queueActionItem(so.expiration()+resubmissionTimeout, soid) 462 err6 := h.queueActionItem(so.expiration()+resubmissionTimeout*2, soid) // Paranoia 463 err = composeErrors(err1, err2, err3, err4, err5, err6) 464 if err != nil { 465 h.log.Println("Error with transaction set, redacting obligation, id", so.id()) 466 return composeErrors(err, h.removeStorageObligation(so, obligationRejected)) 467 } 468 return nil 469 } 470 471 // modifyStorageObligation will take an updated storage obligation along with a 472 // list of sector changes and update the database to account for all of it. The 473 // sector modifications are only used to update the sector database, they will 474 // not be used to modify the storage obligation (most importantly, this means 475 // that sectorRoots needs to be updated by the calling function). Virtual 476 // sectors will be removed the number of times that they are listed, to remove 477 // multiple instances of the same virtual sector, the virtural sector will need 478 // to appear in 'sectorsRemoved' multiple times. Same with 'sectorsGained'. 479 func (h *Host) modifyStorageObligation(so storageObligation, sectorsRemoved []crypto.Hash, sectorsGained []crypto.Hash, gainedSectorData [][]byte) error { 480 // Sanity check - obligation should be under lock while being modified. 481 soid := so.id() 482 _, exists := h.lockedStorageObligations[soid] 483 if !exists { 484 h.log.Critical("modifyStorageObligation called with an obligation that is not locked") 485 } 486 // Sanity check - there needs to be enough time to submit the file contract 487 // revision to the blockchain. 488 if so.expiration()-revisionSubmissionBuffer <= h.blockHeight { 489 return errNoBuffer 490 } 491 // Sanity check - sectorsGained and gainedSectorData need to have the same length. 492 if len(sectorsGained) != len(gainedSectorData) { 493 h.log.Critical("modifying a revision with garbage sector data", len(sectorsGained), len(gainedSectorData)) 494 return errInsaneStorageObligationRevision 495 } 496 // Sanity check - all of the sector data should be modules.SectorSize 497 for _, data := range gainedSectorData { 498 if uint64(len(data)) != modules.SectorSize { 499 h.log.Critical("modifying a revision with garbase sector sizes", len(data)) 500 return errInsaneStorageObligationRevision 501 } 502 } 503 504 // Note, for safe error handling, the operation order should be: add 505 // sectors, update database, remove sectors. If the adding or update fails, 506 // the added sectors should be removed and the storage obligation shoud be 507 // considered invalid. If the removing fails, this is okay, it's ignored 508 // and left to consistency checks and user actions to fix (will reduce host 509 // capacity, but will not inhibit the host's ability to submit storage 510 // proofs) 511 var i int 512 var err error 513 for i = range sectorsGained { 514 err = h.AddSector(sectorsGained[i], gainedSectorData[i]) 515 if err != nil { 516 break 517 } 518 } 519 if err != nil { 520 // Because there was an error, all of the sectors that got added need 521 // to be reverted. 522 for j := 0; j < i; j++ { 523 // Error is not checked because there's nothing useful that can be 524 // done about an error. 525 _ = h.RemoveSector(sectorsGained[j]) 526 } 527 return err 528 } 529 // Update the database to contain the new storage obligation. 530 var oldSO storageObligation 531 err = h.db.Update(func(tx *bolt.Tx) error { 532 // Get the old storage obligation as a reference to know how to upate 533 // the host financial stats. 534 oldSO, err = getStorageObligation(tx, soid) 535 if err != nil { 536 return err 537 } 538 539 // Store the new storage obligation to replace the old one. 540 return putStorageObligation(tx, so) 541 }) 542 if err != nil { 543 // Because there was an error, all of the sectors that got added need 544 // to be reverted. 545 for i := range sectorsGained { 546 // Error is not checked because there's nothing useful that can be 547 // done about an error. 548 _ = h.RemoveSector(sectorsGained[i]) 549 } 550 return err 551 } 552 // Call removeSector for all of the sectors that have been removed. 553 for k := range sectorsRemoved { 554 // Error is not checkeed because there's nothing useful that can be 555 // done about an error. Failing to remove a sector is not a terrible 556 // place to be, especially if the host can run consistency checks. 557 _ = h.RemoveSector(sectorsRemoved[k]) 558 } 559 560 // Update the financial information for the storage obligation - apply the 561 // new values. 562 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Add(so.ContractCost) 563 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Add(so.LockedCollateral) 564 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Add(so.PotentialStorageRevenue) 565 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue) 566 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue) 567 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Add(so.RiskedCollateral) 568 h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Add(so.TransactionFeesAdded) 569 570 // Update the financial information for the storage obligation - remove the 571 // old values. 572 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(oldSO.ContractCost) 573 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(oldSO.LockedCollateral) 574 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(oldSO.PotentialStorageRevenue) 575 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(oldSO.PotentialDownloadRevenue) 576 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(oldSO.PotentialUploadRevenue) 577 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(oldSO.RiskedCollateral) 578 h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(oldSO.TransactionFeesAdded) 579 return nil 580 } 581 582 // PruneStaleStorageObligations will delete storage obligations from the host 583 // that, for whatever reason, did not make it on the block chain. 584 // As these stale storage obligations have an impact on the host financial metrics, 585 // this method updates the host financial metrics to show the correct values. 586 func (h *Host) PruneStaleStorageObligations() error { 587 // Filter the stale obligations from the set of all obligations. 588 sos := h.StorageObligations() 589 var stale []types.FileContractID 590 for _, so := range sos { 591 conf, err := h.tpool.TransactionConfirmed(so.TransactionID) 592 if err != nil { 593 return build.ExtendErr("unable to get transaction ID:", err) 594 } 595 // An obligation is considered stale if it has not been confirmed 596 // within RespendTimeout blocks after negotiation. 597 if (h.blockHeight > so.NegotiationHeight+wallet.RespendTimeout) && !conf { 598 stale = append(stale, so.ObligationId) 599 } 600 } 601 // Delete stale obligations from the database. 602 err := h.deleteStorageObligations(stale) 603 if err != nil { 604 return build.ExtendErr("unable to delete stale storage ids:", err) 605 } 606 // Update the financial metrics of the host. 607 err = h.resetFinancialMetrics() 608 if err != nil { 609 h.log.Println(build.ExtendErr("unable to reset host financial metrics:", err)) 610 return err 611 } 612 return nil 613 } 614 615 // removeStorageObligation will remove a storage obligation from the host, 616 // either due to failure or success. 617 func (h *Host) removeStorageObligation(so storageObligation, sos storageObligationStatus) error { 618 // Error is not checked, we want to call remove on every sector even if 619 // there are problems - disk health information will be updated. 620 _ = h.RemoveSectorBatch(so.SectorRoots) 621 622 // Update the host revenue metrics based on the status of the obligation. 623 if sos == obligationUnresolved { 624 h.log.Critical("storage obligation 'unresolved' during call to removeStorageObligation, id", so.id()) 625 } 626 if sos == obligationRejected { 627 if h.financialMetrics.TransactionFeeExpenses.Cmp(so.TransactionFeesAdded) >= 0 { 628 h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(so.TransactionFeesAdded) 629 630 // Remove the obligation statistics as potential risk and income. 631 h.log.Printf("Rejecting storage obligation expiring at block %v, current height is %v. Potential revenue is %v.\n", so.expiration(), h.blockHeight, h.financialMetrics.PotentialContractCompensation.Add(h.financialMetrics.PotentialStorageRevenue).Add(h.financialMetrics.PotentialDownloadBandwidthRevenue).Add(h.financialMetrics.PotentialUploadBandwidthRevenue)) 632 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost) 633 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral) 634 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue) 635 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue) 636 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue) 637 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral) 638 } 639 } 640 if sos == obligationSucceeded { 641 // Empty obligations don't submit a storage proof. The revenue for an empty 642 // storage obligation should equal the contract cost of the obligation 643 revenue := so.ContractCost.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue) 644 if len(so.SectorRoots) == 0 { 645 h.log.Printf("No need to submit a storage proof for empty contract. Revenue is %v.\n", revenue) 646 } else { 647 h.log.Printf("Successfully submitted a storage proof. Revenue is %v.\n", revenue) 648 } 649 650 // Remove the obligation statistics as potential risk and income. 651 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost) 652 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral) 653 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue) 654 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue) 655 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue) 656 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral) 657 658 // Add the obligation statistics as actual income. 659 h.financialMetrics.ContractCompensation = h.financialMetrics.ContractCompensation.Add(so.ContractCost) 660 h.financialMetrics.StorageRevenue = h.financialMetrics.StorageRevenue.Add(so.PotentialStorageRevenue) 661 h.financialMetrics.DownloadBandwidthRevenue = h.financialMetrics.DownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue) 662 h.financialMetrics.UploadBandwidthRevenue = h.financialMetrics.UploadBandwidthRevenue.Add(so.PotentialUploadRevenue) 663 } 664 if sos == obligationFailed { 665 // Remove the obligation statistics as potential risk and income. 666 h.log.Printf("Missed storage proof. Revenue would have been %v.\n", so.ContractCost.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue)) 667 h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost) 668 h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral) 669 h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue) 670 h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue) 671 h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue) 672 h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral) 673 674 // Add the obligation statistics as loss. 675 h.financialMetrics.LostStorageCollateral = h.financialMetrics.LostStorageCollateral.Add(so.RiskedCollateral) 676 h.financialMetrics.LostRevenue = h.financialMetrics.LostRevenue.Add(so.ContractCost).Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue) 677 } 678 679 // Update the storage obligation to be finalized but still in-database. The 680 // obligation status is updated so that the user can see how the obligation 681 // ended up, and the sector roots are removed because they are large 682 // objects with little purpose once storage proofs are no longer needed. 683 h.financialMetrics.ContractCount-- 684 so.ObligationStatus = sos 685 so.SectorRoots = nil 686 return h.db.Update(func(tx *bolt.Tx) error { 687 return putStorageObligation(tx, so) 688 }) 689 } 690 691 func (h *Host) resetFinancialMetrics() error { 692 h.mu.RLock() 693 defer h.mu.RUnlock() 694 // Initialize new values for the host financial metrics. 695 fm := modules.HostFinancialMetrics{} 696 err := h.db.View(func(tx *bolt.Tx) error { 697 c := tx.Bucket(bucketStorageObligations).Cursor() 698 for k, v := c.First(); k != nil; k, v = c.Next() { 699 var so storageObligation 700 if err := json.Unmarshal(v, &so); err != nil { 701 return build.ExtendErr("unable to unmarshal storage obligation:", err) 702 } 703 // Transaction fees are always added. 704 fm.TransactionFeeExpenses = fm.TransactionFeeExpenses.Add(so.TransactionFeesAdded) 705 // Update the other financial values based on the obligation status. 706 if so.ObligationStatus == obligationUnresolved { 707 fm.ContractCount++ 708 fm.PotentialContractCompensation = fm.PotentialContractCompensation.Add(so.ContractCost) 709 fm.LockedStorageCollateral = fm.LockedStorageCollateral.Add(so.LockedCollateral) 710 fm.PotentialStorageRevenue = fm.PotentialStorageRevenue.Add(so.PotentialStorageRevenue) 711 fm.RiskedStorageCollateral = fm.RiskedStorageCollateral.Add(so.RiskedCollateral) 712 fm.PotentialDownloadBandwidthRevenue = fm.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue) 713 fm.PotentialUploadBandwidthRevenue = fm.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue) 714 } 715 if so.ObligationStatus == obligationSucceeded { 716 fm.ContractCompensation = fm.ContractCompensation.Add(so.ContractCost) 717 fm.StorageRevenue = fm.StorageRevenue.Add(so.PotentialStorageRevenue) 718 fm.DownloadBandwidthRevenue = fm.DownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue) 719 fm.UploadBandwidthRevenue = fm.UploadBandwidthRevenue.Add(so.PotentialUploadRevenue) 720 } 721 if so.ObligationStatus == obligationFailed { 722 // If there was no risked collateral for the failed obligation, we don't 723 // update anything since no revenues were lost. Only the contract compensation 724 // and transaction fees are added. 725 fm.ContractCompensation = fm.ContractCompensation.Add(so.ContractCost) 726 if !so.RiskedCollateral.IsZero() { 727 // Storage obligation failed with risked collateral. 728 fm.LostRevenue = fm.LostRevenue.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue) 729 fm.LostStorageCollateral = fm.LostStorageCollateral.Add(so.RiskedCollateral) 730 } 731 } 732 733 } 734 return nil 735 }) 736 if err != nil { 737 h.log.Println(build.ExtendErr("unable to reset host financial metrics:", err)) 738 return err 739 } 740 h.financialMetrics = fm 741 return nil 742 } 743 744 // threadedHandleActionItem will look at a storage obligation and determine 745 // which action is necessary for the storage obligation to succeed. 746 func (h *Host) threadedHandleActionItem(soid types.FileContractID) { 747 err := h.tg.Add() 748 if err != nil { 749 return 750 } 751 defer h.tg.Done() 752 753 // Lock the storage obligation in question. 754 h.managedLockStorageObligation(soid) 755 defer func() { 756 h.managedUnlockStorageObligation(soid) 757 }() 758 759 // Fetch the storage obligation associated with the storage obligation id. 760 var so storageObligation 761 h.mu.RLock() 762 blockHeight := h.blockHeight 763 err = h.db.View(func(tx *bolt.Tx) error { 764 so, err = getStorageObligation(tx, soid) 765 return err 766 }) 767 h.mu.RUnlock() 768 if err != nil { 769 h.log.Println("Could not get storage obligation:", err) 770 return 771 } 772 773 // Check whether the storage obligation has already been completed. 774 if so.ObligationStatus != obligationUnresolved { 775 // Storage obligation has already been completed, skip action item. 776 return 777 } 778 779 // Check whether the file contract has been seen. If not, resubmit and 780 // queue another action item. Check for death. (signature should have a 781 // kill height) 782 if !so.OriginConfirmed { 783 // Submit the transaction set again, try to get the transaction 784 // confirmed. 785 err := h.tpool.AcceptTransactionSet(so.OriginTransactionSet) 786 if err != nil { 787 h.log.Debugln("Could not get origin transaction set accepted", err) 788 789 // Check if the transaction is invalid with the current consensus set. 790 // If so, the transaction is highly unlikely to ever be confirmed, and 791 // the storage obligation should be removed. This check should come 792 // after logging the errror so that the function can quit. 793 // 794 // TODO: If the host or tpool is behind consensus, might be difficult 795 // to have certainty about the issue. If some but not all of the 796 // parents are confirmed, might be some difficulty. 797 _, t := err.(modules.ConsensusConflict) 798 if t { 799 h.log.Println("Consensus conflict on the origin transaction set, id", so.id()) 800 h.mu.Lock() 801 err = h.removeStorageObligation(so, obligationRejected) 802 h.mu.Unlock() 803 if err != nil { 804 h.log.Println("Error removing storage obligation:", err) 805 } 806 return 807 } 808 } 809 810 // Queue another action item to check the status of the transaction. 811 h.mu.Lock() 812 err = h.queueActionItem(h.blockHeight+resubmissionTimeout, so.id()) 813 h.mu.Unlock() 814 if err != nil { 815 h.log.Println("Error queuing action item:", err) 816 } 817 } 818 819 // Check if the file contract revision is ready for submission. Check for death. 820 if !so.RevisionConfirmed && len(so.RevisionTransactionSet) > 0 && blockHeight >= so.expiration()-revisionSubmissionBuffer { 821 // Sanity check - there should be a file contract revision. 822 rtsLen := len(so.RevisionTransactionSet) 823 if rtsLen < 1 || len(so.RevisionTransactionSet[rtsLen-1].FileContractRevisions) != 1 { 824 h.log.Critical("transaction revision marked as unconfirmed, yet there is no transaction revision") 825 return 826 } 827 828 // Check if the revision has failed to submit correctly. 829 if blockHeight > so.expiration() { 830 // TODO: Check this error. 831 // 832 // TODO: this is not quite right, because a previous revision may 833 // be confirmed, and the origin transaction may be confirmed, which 834 // would confuse the revenue stuff a bit. Might happen frequently 835 // due to the dynamic fee pool. 836 h.log.Println("Full time has elapsed, but the revision transaction could not be submitted to consensus, id", so.id()) 837 h.mu.Lock() 838 h.removeStorageObligation(so, obligationRejected) 839 h.mu.Unlock() 840 return 841 } 842 843 // Queue another action item to check the status of the transaction. 844 h.mu.Lock() 845 err := h.queueActionItem(blockHeight+resubmissionTimeout, so.id()) 846 h.mu.Unlock() 847 if err != nil { 848 h.log.Println("Error queuing action item:", err) 849 } 850 851 // Add a miner fee to the transaction and submit it to the blockchain. 852 revisionTxnIndex := len(so.RevisionTransactionSet) - 1 853 revisionParents := so.RevisionTransactionSet[:revisionTxnIndex] 854 revisionTxn := so.RevisionTransactionSet[revisionTxnIndex] 855 builder, err := h.wallet.RegisterTransaction(revisionTxn, revisionParents) 856 if err != nil { 857 h.log.Println("Error registering transaction:", err) 858 return 859 } 860 _, feeRecommendation := h.tpool.FeeEstimation() 861 if so.value().Div64(2).Cmp(feeRecommendation) < 0 { 862 // There's no sense submitting the revision if the fee is more than 863 // half of the anticipated revenue - fee market went up 864 // unexpectedly, and the money that the renter paid to cover the 865 // fees is no longer enough. 866 builder.Drop() 867 return 868 } 869 txnSize := uint64(len(encoding.MarshalAll(so.RevisionTransactionSet)) + 300) 870 requiredFee := feeRecommendation.Mul64(txnSize) 871 err = builder.FundSiacoins(requiredFee) 872 if err != nil { 873 h.log.Println("Error funding transaction fees", err) 874 builder.Drop() 875 } 876 builder.AddMinerFee(requiredFee) 877 if err != nil { 878 h.log.Println("Error adding miner fees", err) 879 builder.Drop() 880 } 881 feeAddedRevisionTransactionSet, err := builder.Sign(true) 882 if err != nil { 883 h.log.Println("Error signing transaction", err) 884 builder.Drop() 885 } 886 err = h.tpool.AcceptTransactionSet(feeAddedRevisionTransactionSet) 887 if err != nil { 888 h.log.Println("Error submitting transaction to transaction pool", err) 889 builder.Drop() 890 } 891 so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee) 892 // return 893 } 894 895 // Check whether a storage proof is ready to be provided, and whether it 896 // has been accepted. Check for death. 897 if !so.ProofConfirmed && blockHeight >= so.expiration()+resubmissionTimeout { 898 h.log.Debugln("Host is attempting a storage proof for", so.id()) 899 900 // If the obligation has no sector roots, we can remove the obligation and not 901 // submit a storage proof. The host payout for a failed empty contract 902 // includes the contract cost and locked collateral. 903 if len(so.SectorRoots) == 0 { 904 h.log.Debugln("storage proof not submitted for empty contract, id", so.id()) 905 h.mu.Lock() 906 err := h.removeStorageObligation(so, obligationSucceeded) 907 h.mu.Unlock() 908 if err != nil { 909 h.log.Println("Error removing storage obligation:", err) 910 } 911 return 912 } 913 // If the window has closed, the host has failed and the obligation can 914 // be removed. 915 if so.proofDeadline() < blockHeight { 916 h.log.Debugln("storage proof not confirmed by deadline, id", so.id()) 917 h.mu.Lock() 918 err := h.removeStorageObligation(so, obligationFailed) 919 h.mu.Unlock() 920 if err != nil { 921 h.log.Println("Error removing storage obligation:", err) 922 } 923 return 924 } 925 // Get the index of the segment, and the index of the sector containing 926 // the segment. 927 segmentIndex, err := h.cs.StorageProofSegment(so.id()) 928 if err != nil { 929 h.log.Debugln("Host got an error when fetching a storage proof segment:", err) 930 return 931 } 932 sectorIndex := segmentIndex / (modules.SectorSize / crypto.SegmentSize) 933 // Pull the corresponding sector into memory. 934 sectorRoot := so.SectorRoots[sectorIndex] 935 sectorBytes, err := h.ReadSector(sectorRoot) 936 if err != nil { 937 h.log.Debugln(err) 938 return 939 } 940 941 // Build the storage proof for just the sector. 942 sectorSegment := segmentIndex % (modules.SectorSize / crypto.SegmentSize) 943 base, cachedHashSet := crypto.MerkleProof(sectorBytes, sectorSegment) 944 945 // Using the sector, build a cached root. 946 log2SectorSize := uint64(0) 947 for 1<<log2SectorSize < (modules.SectorSize / crypto.SegmentSize) { 948 log2SectorSize++ 949 } 950 ct := crypto.NewCachedTree(log2SectorSize) 951 ct.SetIndex(segmentIndex) 952 for _, root := range so.SectorRoots { 953 ct.Push(root) 954 } 955 hashSet := ct.Prove(base, cachedHashSet) 956 sp := types.StorageProof{ 957 ParentID: so.id(), 958 HashSet: hashSet, 959 } 960 copy(sp.Segment[:], base) 961 962 // Create and build the transaction with the storage proof. 963 builder, err := h.wallet.StartTransaction() 964 if err != nil { 965 h.log.Println("Failed to start transaction:", err) 966 return 967 } 968 _, feeRecommendation := h.tpool.FeeEstimation() 969 if so.value().Cmp(feeRecommendation) < 0 { 970 // There's no sense submitting the storage proof if the fee is more 971 // than the anticipated revenue. 972 h.log.Debugln("Host not submitting storage proof due to a value that does not sufficiently exceed the fee cost") 973 builder.Drop() 974 return 975 } 976 txnSize := uint64(len(encoding.Marshal(sp)) + 300) 977 requiredFee := feeRecommendation.Mul64(txnSize) 978 err = builder.FundSiacoins(requiredFee) 979 if err != nil { 980 h.log.Println("Host error when funding a storage proof transaction fee:", err) 981 builder.Drop() 982 return 983 } 984 builder.AddMinerFee(requiredFee) 985 builder.AddStorageProof(sp) 986 storageProofSet, err := builder.Sign(true) 987 if err != nil { 988 h.log.Println("Host error when signing the storage proof transaction:", err) 989 builder.Drop() 990 return 991 } 992 err = h.tpool.AcceptTransactionSet(storageProofSet) 993 if err != nil { 994 h.log.Println("Host unable to submit storage proof transaction to transaction pool:", err) 995 builder.Drop() 996 return 997 } 998 so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee) 999 1000 // Queue another action item to check whether the storage proof 1001 // got confirmed. 1002 h.mu.Lock() 1003 err = h.queueActionItem(so.proofDeadline(), so.id()) 1004 h.mu.Unlock() 1005 if err != nil { 1006 h.log.Println("Error queuing action item:", err) 1007 } 1008 } 1009 1010 // Save the storage obligation to account for any fee changes. 1011 err = h.db.Update(func(tx *bolt.Tx) error { 1012 soBytes, err := json.Marshal(so) 1013 if err != nil { 1014 return err 1015 } 1016 return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes) 1017 }) 1018 if err != nil { 1019 h.log.Println("Error updating the storage obligations", err) 1020 } 1021 1022 // Check if all items have succeeded with the required confirmations. Report 1023 // success, delete the obligation. 1024 if so.ProofConfirmed && blockHeight >= so.proofDeadline() { 1025 h.log.Println("file contract complete, id", so.id()) 1026 h.mu.Lock() 1027 h.removeStorageObligation(so, obligationSucceeded) 1028 h.mu.Unlock() 1029 } 1030 } 1031 1032 // StorageObligations fetches the set of storage obligations in the host and 1033 // returns metadata on them. 1034 func (h *Host) StorageObligations() (sos []modules.StorageObligation) { 1035 h.mu.RLock() 1036 defer h.mu.RUnlock() 1037 1038 err := h.db.View(func(tx *bolt.Tx) error { 1039 b := tx.Bucket(bucketStorageObligations) 1040 err := b.ForEach(func(idBytes, soBytes []byte) error { 1041 var so storageObligation 1042 err := json.Unmarshal(soBytes, &so) 1043 if err != nil { 1044 return build.ExtendErr("unable to unmarshal storage obligation:", err) 1045 } 1046 mso := modules.StorageObligation{ 1047 ContractCost: so.ContractCost, 1048 DataSize: so.fileSize(), 1049 LockedCollateral: so.LockedCollateral, 1050 ObligationId: so.id(), 1051 PotentialDownloadRevenue: so.PotentialDownloadRevenue, 1052 PotentialStorageRevenue: so.PotentialStorageRevenue, 1053 PotentialUploadRevenue: so.PotentialUploadRevenue, 1054 RiskedCollateral: so.RiskedCollateral, 1055 SectorRootsCount: uint64(len(so.SectorRoots)), 1056 TransactionFeesAdded: so.TransactionFeesAdded, 1057 TransactionID: so.transactionID(), 1058 1059 ExpirationHeight: so.expiration(), 1060 NegotiationHeight: so.NegotiationHeight, 1061 ProofDeadLine: so.proofDeadline(), 1062 1063 ObligationStatus: so.ObligationStatus.String(), 1064 OriginConfirmed: so.OriginConfirmed, 1065 ProofConfirmed: so.ProofConfirmed, 1066 ProofConstructed: so.ProofConstructed, 1067 RevisionConfirmed: so.RevisionConfirmed, 1068 RevisionConstructed: so.RevisionConstructed, 1069 } 1070 sos = append(sos, mso) 1071 return nil 1072 }) 1073 if err != nil { 1074 return build.ExtendErr("ForEach failed to get next storage obligation:", err) 1075 } 1076 return nil 1077 }) 1078 if err != nil { 1079 h.log.Println(build.ExtendErr("database failed to provide storage obligations:", err)) 1080 } 1081 1082 return sos 1083 }