github.com/nebulouslabs/sia@v1.3.7/modules/host/storageobligations_smoke_test.go (about) 1 package host 2 3 // storageobligations_smoke_test.go performs smoke testing on the the storage 4 // obligation management. This includes adding valid storage obligations, and 5 // waiting until they expire, to see if the failure modes are all handled 6 // correctly. 7 8 import ( 9 "errors" 10 "testing" 11 "time" 12 13 "github.com/NebulousLabs/Sia/build" 14 "github.com/NebulousLabs/Sia/crypto" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/types" 17 "github.com/NebulousLabs/fastrand" 18 19 "github.com/coreos/bbolt" 20 ) 21 22 // randSector creates a random sector, returning the sector along with the 23 // Merkle root of the sector. 24 func randSector() (crypto.Hash, []byte) { 25 sectorData := fastrand.Bytes(int(modules.SectorSize)) 26 sectorRoot := crypto.MerkleRoot(sectorData) 27 return sectorRoot, sectorData 28 } 29 30 // newTesterStorageObligation uses the wallet to create and fund a file 31 // contract that will form the foundation of a storage obligation. 32 func (ht *hostTester) newTesterStorageObligation() (storageObligation, error) { 33 // Create the file contract that will be used in the obligation. 34 builder, err := ht.wallet.StartTransaction() 35 if err != nil { 36 return storageObligation{}, err 37 } 38 // Fund the file contract with a payout. The payout needs to be big enough 39 // that the expected revenue is larger than the fee that the host may end 40 // up paying. 41 payout := types.SiacoinPrecision.Mul64(1e3) 42 err = builder.FundSiacoins(payout) 43 if err != nil { 44 return storageObligation{}, err 45 } 46 // Add the file contract that consumes the funds. 47 _ = builder.AddFileContract(types.FileContract{ 48 // Because this file contract needs to be able to accept file contract 49 // revisions, the expiration is put more than 50 // 'revisionSubmissionBuffer' blocks into the future. 51 WindowStart: ht.host.blockHeight + revisionSubmissionBuffer + 2, 52 WindowEnd: ht.host.blockHeight + revisionSubmissionBuffer + defaultWindowSize + 2, 53 54 Payout: payout, 55 ValidProofOutputs: []types.SiacoinOutput{ 56 { 57 Value: types.PostTax(ht.host.blockHeight, payout), 58 }, 59 { 60 Value: types.ZeroCurrency, 61 }, 62 }, 63 MissedProofOutputs: []types.SiacoinOutput{ 64 { 65 Value: types.PostTax(ht.host.blockHeight, payout), 66 }, 67 { 68 Value: types.ZeroCurrency, 69 }, 70 }, 71 UnlockHash: (types.UnlockConditions{}).UnlockHash(), 72 RevisionNumber: 0, 73 }) 74 // Sign the transaction. 75 tSet, err := builder.Sign(true) 76 if err != nil { 77 return storageObligation{}, err 78 } 79 80 // Assemble and return the storage obligation. 81 so := storageObligation{ 82 OriginTransactionSet: tSet, 83 84 // TODO: There are no tracking values, because no fees were added. 85 } 86 return so, nil 87 } 88 89 // TestBlankStorageObligation checks that the host correctly manages a blank 90 // storage obligation. 91 func TestBlankStorageObligation(t *testing.T) { 92 if testing.Short() { 93 t.SkipNow() 94 } 95 t.Parallel() 96 ht, err := newHostTester("TestBlankStorageObligation") 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer ht.Close() 101 102 // The number of contracts reported by the host should be zero. 103 fm := ht.host.FinancialMetrics() 104 if fm.ContractCount != 0 { 105 t.Error("host does not start with 0 contracts:", fm.ContractCount) 106 } 107 108 // Start by adding a storage obligation to the host. To emulate conditions 109 // of a renter creating the first contract, the storage obligation has no 110 // data, but does have money. 111 so, err := ht.newTesterStorageObligation() 112 if err != nil { 113 t.Fatal(err) 114 } 115 ht.host.managedLockStorageObligation(so.id()) 116 err = ht.host.managedAddStorageObligation(so) 117 if err != nil { 118 t.Fatal(err) 119 } 120 ht.host.managedUnlockStorageObligation(so.id()) 121 // Storage obligation should not be marked as having the transaction 122 // confirmed on the blockchain. 123 if so.OriginConfirmed { 124 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 125 } 126 fm = ht.host.FinancialMetrics() 127 if fm.ContractCount != 1 { 128 t.Error("host should have 1 contract:", fm.ContractCount) 129 } 130 131 // Mine a block to confirm the transaction containing the storage 132 // obligation. 133 _, err = ht.miner.AddBlock() 134 if err != nil { 135 t.Fatal(err) 136 } 137 err = ht.host.tg.Flush() 138 if err != nil { 139 t.Fatal(err) 140 } 141 // Load the storage obligation from the database, see if it updated 142 // correctly. 143 err = ht.host.db.View(func(tx *bolt.Tx) error { 144 so, err = getStorageObligation(tx, so.id()) 145 if err != nil { 146 return err 147 } 148 return nil 149 }) 150 if err != nil { 151 t.Fatal(err) 152 } 153 if !so.OriginConfirmed { 154 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 155 } 156 157 // Mine until the host would be submitting a storage proof. Check that the 158 // host has cleared out the storage proof - the consensus code makes it 159 // impossible to submit a storage proof for an empty file contract, so the 160 // host should fail and give up by deleting the storage obligation. 161 for i := types.BlockHeight(0); i <= revisionSubmissionBuffer*2+1; i++ { 162 _, err := ht.miner.AddBlock() 163 if err != nil { 164 t.Fatal(err) 165 } 166 err = ht.host.tg.Flush() 167 if err != nil { 168 t.Fatal(err) 169 } 170 } 171 err = ht.host.db.View(func(tx *bolt.Tx) error { 172 so, err = getStorageObligation(tx, so.id()) 173 if err != nil { 174 return err 175 } 176 return nil 177 }) 178 if err != nil { 179 t.Fatal(err) 180 } 181 fm = ht.host.FinancialMetrics() 182 if fm.ContractCount != 0 { 183 t.Error("host should have 0 contracts, the contracts were all completed:", fm.ContractCount) 184 } 185 } 186 187 // TestSingleSectorObligationStack checks that the host correctly manages a 188 // storage obligation with a single sector, the revision is created the same 189 // block as the file contract. 190 func TestSingleSectorStorageObligationStack(t *testing.T) { 191 if testing.Short() { 192 t.SkipNow() 193 } 194 t.Parallel() 195 ht, err := newHostTester("TestSingleSectorStorageObligationStack") 196 if err != nil { 197 t.Fatal(err) 198 } 199 defer ht.Close() 200 201 // Start by adding a storage obligation to the host. To emulate conditions 202 // of a renter creating the first contract, the storage obligation has no 203 // data, but does have money. 204 so, err := ht.newTesterStorageObligation() 205 if err != nil { 206 t.Fatal(err) 207 } 208 ht.host.managedLockStorageObligation(so.id()) 209 err = ht.host.managedAddStorageObligation(so) 210 if err != nil { 211 t.Fatal(err) 212 } 213 ht.host.managedUnlockStorageObligation(so.id()) 214 // Storage obligation should not be marked as having the transaction 215 // confirmed on the blockchain. 216 if so.OriginConfirmed { 217 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 218 } 219 220 // Add a file contract revision, moving over a small amount of money to pay 221 // for the file contract. 222 sectorRoot, sectorData := randSector() 223 so.SectorRoots = []crypto.Hash{sectorRoot} 224 sectorCost := types.SiacoinPrecision.Mul64(550) 225 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost) 226 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 227 validPayouts, missedPayouts := so.payouts() 228 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 229 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 230 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 231 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 232 revisionSet := []types.Transaction{{ 233 FileContractRevisions: []types.FileContractRevision{{ 234 ParentID: so.id(), 235 UnlockConditions: types.UnlockConditions{}, 236 NewRevisionNumber: 1, 237 238 NewFileSize: uint64(len(sectorData)), 239 NewFileMerkleRoot: sectorRoot, 240 NewWindowStart: so.expiration(), 241 NewWindowEnd: so.proofDeadline(), 242 NewValidProofOutputs: validPayouts, 243 NewMissedProofOutputs: missedPayouts, 244 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 245 }}, 246 }} 247 ht.host.managedLockStorageObligation(so.id()) 248 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 249 if err != nil { 250 t.Fatal(err) 251 } 252 ht.host.managedUnlockStorageObligation(so.id()) 253 // Submit the revision set to the transaction pool. 254 err = ht.tpool.AcceptTransactionSet(revisionSet) 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 // Mine a block to confirm the transactions containing the file contract 260 // and the file contract revision. 261 _, err = ht.miner.AddBlock() 262 if err != nil { 263 t.Fatal(err) 264 } 265 // Load the storage obligation from the database, see if it updated 266 // correctly. 267 err = ht.host.db.View(func(tx *bolt.Tx) error { 268 so, err = getStorageObligation(tx, so.id()) 269 if err != nil { 270 return err 271 } 272 return nil 273 }) 274 if err != nil { 275 t.Fatal(err) 276 } 277 if !so.OriginConfirmed { 278 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 279 } 280 if !so.RevisionConfirmed { 281 t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined") 282 } 283 284 // Mine until the host submits a storage proof. 285 for i := ht.host.blockHeight; i <= so.expiration()+resubmissionTimeout; i++ { 286 _, err := ht.miner.AddBlock() 287 if err != nil { 288 t.Fatal(err) 289 } 290 } 291 // Flush the host - flush will block until the host has submitted the 292 // storage proof to the transaction pool. 293 err = ht.host.tg.Flush() 294 if err != nil { 295 t.Fatal(err) 296 } 297 // Mine another block, to get the storage proof from the transaction pool 298 // into the blockchain. 299 _, err = ht.miner.AddBlock() 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 // Grab the storage proof and inspect the contents. 305 err = ht.host.db.View(func(tx *bolt.Tx) error { 306 so, err = getStorageObligation(tx, so.id()) 307 if err != nil { 308 return err 309 } 310 return nil 311 }) 312 if err != nil { 313 t.Fatal(err) 314 } 315 if !so.OriginConfirmed { 316 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 317 } 318 if !so.RevisionConfirmed { 319 t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined") 320 } 321 if !so.ProofConfirmed { 322 t.Fatal("storage obligation is not saying that the storage proof was confirmed on the blockchain") 323 } 324 325 // Mine blocks until the storage proof has enough confirmations that the 326 // host will finalize the obligation. 327 for i := 0; i <= int(defaultWindowSize); i++ { 328 _, err := ht.miner.AddBlock() 329 if err != nil { 330 t.Fatal(err) 331 } 332 } 333 err = ht.host.db.View(func(tx *bolt.Tx) error { 334 so, err = getStorageObligation(tx, so.id()) 335 if err != nil { 336 return err 337 } 338 if so.SectorRoots != nil { 339 t.Error("sector roots were not cleared when the host finalized the obligation") 340 } 341 if so.ObligationStatus != obligationSucceeded { 342 t.Error("obligation is not being reported as successful:", so.ObligationStatus) 343 } 344 return nil 345 }) 346 if err != nil { 347 t.Fatal(err) 348 } 349 if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost) { 350 t.Fatal("the host should be reporting revenue after a successful storage proof") 351 } 352 } 353 354 // TestMultiSectorObligationStack checks that the host correctly manages a 355 // storage obligation with a single sector, the revision is created the same 356 // block as the file contract. 357 // 358 // Unlike the SingleSector test, the multi sector test attempts to spread file 359 // contract revisions over multiple blocks. 360 func TestMultiSectorStorageObligationStack(t *testing.T) { 361 if testing.Short() { 362 t.SkipNow() 363 } 364 t.Parallel() 365 ht, err := newHostTester("TestMultiSectorStorageObligationStack") 366 if err != nil { 367 t.Fatal(err) 368 } 369 defer ht.Close() 370 371 // Start by adding a storage obligation to the host. To emulate conditions 372 // of a renter creating the first contract, the storage obligation has no 373 // data, but does have money. 374 so, err := ht.newTesterStorageObligation() 375 if err != nil { 376 t.Fatal(err) 377 } 378 ht.host.managedLockStorageObligation(so.id()) 379 err = ht.host.managedAddStorageObligation(so) 380 if err != nil { 381 t.Fatal(err) 382 } 383 ht.host.managedUnlockStorageObligation(so.id()) 384 // Storage obligation should not be marked as having the transaction 385 // confirmed on the blockchain. 386 if so.OriginConfirmed { 387 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 388 } 389 // Deviation from SingleSector test - mine a block here to confirm the 390 // storage obligation before a file contract revision is created. 391 _, err = ht.miner.AddBlock() 392 if err != nil { 393 t.Fatal(err) 394 } 395 // Load the storage obligation from the database, see if it updated 396 // correctly. 397 err = ht.host.db.View(func(tx *bolt.Tx) error { 398 so, err = getStorageObligation(tx, so.id()) 399 if err != nil { 400 return err 401 } 402 return nil 403 }) 404 if err != nil { 405 t.Fatal(err) 406 } 407 if !so.OriginConfirmed { 408 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 409 } 410 411 // Add a file contract revision, moving over a small amount of money to pay 412 // for the file contract. 413 sectorRoot, sectorData := randSector() 414 so.SectorRoots = []crypto.Hash{sectorRoot} 415 sectorCost := types.SiacoinPrecision.Mul64(550) 416 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost) 417 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 418 validPayouts, missedPayouts := so.payouts() 419 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 420 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 421 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 422 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 423 revisionSet := []types.Transaction{{ 424 FileContractRevisions: []types.FileContractRevision{{ 425 ParentID: so.id(), 426 UnlockConditions: types.UnlockConditions{}, 427 NewRevisionNumber: 1, 428 429 NewFileSize: uint64(len(sectorData)), 430 NewFileMerkleRoot: sectorRoot, 431 NewWindowStart: so.expiration(), 432 NewWindowEnd: so.proofDeadline(), 433 NewValidProofOutputs: validPayouts, 434 NewMissedProofOutputs: missedPayouts, 435 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 436 }}, 437 }} 438 ht.host.managedLockStorageObligation(so.id()) 439 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 440 if err != nil { 441 t.Fatal(err) 442 } 443 ht.host.managedUnlockStorageObligation(so.id()) 444 // Submit the revision set to the transaction pool. 445 err = ht.tpool.AcceptTransactionSet(revisionSet) 446 if err != nil { 447 t.Fatal(err) 448 } 449 450 // Create a second file contract revision, which is going to be submitted 451 // to the transaction pool after the first revision. Though, in practice 452 // this should never happen, we want to check that the transaction pool is 453 // correctly handling multiple file contract revisions being submitted in 454 // the same block cycle. This test will additionally tell us whether or not 455 // the host can correctly handle building storage proofs for files with 456 // multiple sectors. 457 sectorRoot2, sectorData2 := randSector() 458 so.SectorRoots = []crypto.Hash{sectorRoot, sectorRoot2} 459 sectorCost2 := types.SiacoinPrecision.Mul64(650) 460 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost2) 461 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost2) 462 validPayouts, missedPayouts = so.payouts() 463 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost2) 464 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost2) 465 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost2) 466 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost2) 467 combinedSectors := append(sectorData, sectorData2...) 468 combinedRoot := crypto.MerkleRoot(combinedSectors) 469 revisionSet2 := []types.Transaction{{ 470 FileContractRevisions: []types.FileContractRevision{{ 471 ParentID: so.id(), 472 UnlockConditions: types.UnlockConditions{}, 473 NewRevisionNumber: 2, 474 475 NewFileSize: uint64(len(sectorData) + len(sectorData2)), 476 NewFileMerkleRoot: combinedRoot, 477 NewWindowStart: so.expiration(), 478 NewWindowEnd: so.proofDeadline(), 479 NewValidProofOutputs: validPayouts, 480 NewMissedProofOutputs: missedPayouts, 481 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 482 }}, 483 }} 484 ht.host.managedLockStorageObligation(so.id()) 485 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot2}, [][]byte{sectorData2}) 486 if err != nil { 487 t.Fatal(err) 488 } 489 ht.host.managedUnlockStorageObligation(so.id()) 490 // Submit the revision set to the transaction pool. 491 err = ht.tpool.AcceptTransactionSet(revisionSet2) 492 if err != nil { 493 t.Fatal(err) 494 } 495 496 // Mine a block to confirm the transactions containing the file contract 497 // and the file contract revision. 498 _, err = ht.miner.AddBlock() 499 if err != nil { 500 t.Fatal(err) 501 } 502 // Load the storage obligation from the database, see if it updated 503 // correctly. 504 err = ht.host.db.View(func(tx *bolt.Tx) error { 505 so, err = getStorageObligation(tx, so.id()) 506 if err != nil { 507 return err 508 } 509 return nil 510 }) 511 if err != nil { 512 t.Fatal(err) 513 } 514 if !so.OriginConfirmed { 515 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 516 } 517 if !so.RevisionConfirmed { 518 t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined") 519 } 520 521 // Mine until the host submits a storage proof. 522 for i := ht.host.blockHeight; i <= so.expiration()+resubmissionTimeout; i++ { 523 _, err := ht.miner.AddBlock() 524 if err != nil { 525 t.Fatal(err) 526 } 527 } 528 // Flush the host - flush will block until the host has submitted the 529 // storage proof to the transaction pool. 530 err = ht.host.tg.Flush() 531 if err != nil { 532 t.Fatal(err) 533 } 534 // Mine another block, to get the storage proof from the transaction pool 535 // into the blockchain. 536 _, err = ht.miner.AddBlock() 537 if err != nil { 538 t.Fatal(err) 539 } 540 541 err = ht.host.db.View(func(tx *bolt.Tx) error { 542 so, err = getStorageObligation(tx, so.id()) 543 if err != nil { 544 return err 545 } 546 return nil 547 }) 548 if err != nil { 549 t.Fatal(err) 550 } 551 if !so.OriginConfirmed { 552 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 553 } 554 if !so.RevisionConfirmed { 555 t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined") 556 } 557 if !so.ProofConfirmed { 558 t.Fatal("storage obligation is not saying that the storage proof was confirmed on the blockchain") 559 } 560 561 // Mine blocks until the storage proof has enough confirmations that the 562 // host will delete the file entirely. 563 for i := 0; i <= int(defaultWindowSize); i++ { 564 _, err := ht.miner.AddBlock() 565 if err != nil { 566 t.Fatal(err) 567 } 568 } 569 err = ht.host.db.View(func(tx *bolt.Tx) error { 570 so, err = getStorageObligation(tx, so.id()) 571 if err != nil { 572 return err 573 } 574 if so.SectorRoots != nil { 575 t.Error("sector roots were not cleared out when the storage proof was finalized") 576 } 577 if so.ObligationStatus != obligationSucceeded { 578 t.Error("storage obligation was not reported as a success") 579 } 580 return nil 581 }) 582 if err != nil { 583 t.Fatal(err) 584 } 585 if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost.Add(sectorCost2)) { 586 t.Fatal("the host should be reporting revenue after a successful storage proof") 587 } 588 } 589 590 // TestAutoRevisionSubmission checks that the host correctly submits a file 591 // contract revision to the consensus set. 592 func TestAutoRevisionSubmission(t *testing.T) { 593 if testing.Short() || !build.VLONG { 594 t.SkipNow() 595 } 596 t.Parallel() 597 ht, err := newHostTester("TestAutoRevisionSubmission") 598 if err != nil { 599 t.Fatal(err) 600 } 601 defer ht.Close() 602 603 // Start by adding a storage obligation to the host. To emulate conditions 604 // of a renter creating the first contract, the storage obligation has no 605 // data, but does have money. 606 so, err := ht.newTesterStorageObligation() 607 if err != nil { 608 t.Fatal(err) 609 } 610 ht.host.managedLockStorageObligation(so.id()) 611 err = ht.host.managedAddStorageObligation(so) 612 if err != nil { 613 t.Fatal(err) 614 } 615 ht.host.managedUnlockStorageObligation(so.id()) 616 // Storage obligation should not be marked as having the transaction 617 // confirmed on the blockchain. 618 if so.OriginConfirmed { 619 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 620 } 621 622 // Add a file contract revision, moving over a small amount of money to pay 623 // for the file contract. 624 sectorRoot, sectorData := randSector() 625 so.SectorRoots = []crypto.Hash{sectorRoot} 626 sectorCost := types.SiacoinPrecision.Mul64(550) 627 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost) 628 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 629 validPayouts, missedPayouts := so.payouts() 630 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 631 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 632 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 633 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 634 revisionSet := []types.Transaction{{ 635 FileContractRevisions: []types.FileContractRevision{{ 636 ParentID: so.id(), 637 UnlockConditions: types.UnlockConditions{}, 638 NewRevisionNumber: 1, 639 640 NewFileSize: uint64(len(sectorData)), 641 NewFileMerkleRoot: sectorRoot, 642 NewWindowStart: so.expiration(), 643 NewWindowEnd: so.proofDeadline(), 644 NewValidProofOutputs: validPayouts, 645 NewMissedProofOutputs: missedPayouts, 646 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 647 }}, 648 }} 649 so.RevisionTransactionSet = revisionSet 650 ht.host.managedLockStorageObligation(so.id()) 651 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 652 if err != nil { 653 t.Fatal(err) 654 } 655 ht.host.managedUnlockStorageObligation(so.id()) 656 err = ht.host.tg.Flush() 657 if err != nil { 658 t.Fatal(err) 659 } 660 // Unlike the other tests, this test does not submit the file contract 661 // revision to the transaction pool for the host, the host is expected to 662 // do it automatically. 663 664 // Mine until the host submits a storage proof. 665 for i := types.BlockHeight(0); i <= revisionSubmissionBuffer+2+resubmissionTimeout; i++ { 666 _, err := ht.miner.AddBlock() 667 if err != nil { 668 t.Fatal(err) 669 } 670 err = ht.host.tg.Flush() 671 if err != nil { 672 t.Fatal(err) 673 } 674 } 675 // Flush the host - flush will block until the host has submitted the 676 // storage proof to the transaction pool. 677 err = ht.host.tg.Flush() 678 if err != nil { 679 t.Fatal(err) 680 } 681 // Mine another block, to get the storage proof from the transaction pool 682 // into the blockchain. 683 _, err = ht.miner.AddBlock() 684 if err != nil { 685 t.Fatal(err) 686 } 687 err = ht.host.tg.Flush() 688 if err != nil { 689 t.Fatal(err) 690 } 691 692 err = build.Retry(50, 250*time.Millisecond, func() error { 693 err = ht.host.db.View(func(tx *bolt.Tx) error { 694 so, err = getStorageObligation(tx, so.id()) 695 if err != nil { 696 return err 697 } 698 return nil 699 }) 700 if err != nil { 701 return (err) 702 } 703 if !so.OriginConfirmed { 704 return errors.New("origin transaction for storage obligation was not confirmed after blocks were mined") 705 } 706 if !so.RevisionConfirmed { 707 return errors.New("revision transaction for storage obligation was not confirmed after blocks were mined") 708 } 709 if !so.ProofConfirmed { 710 return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain") 711 } 712 return nil 713 }) 714 if err != nil { 715 t.Fatal(err) 716 } 717 718 // Mine blocks until the storage proof has enough confirmations that the 719 // host will delete the file entirely. 720 for i := 0; i <= int(defaultWindowSize); i++ { 721 _, err := ht.miner.AddBlock() 722 if err != nil { 723 t.Fatal(err) 724 } 725 err = ht.host.tg.Flush() 726 if err != nil { 727 t.Fatal(err) 728 } 729 } 730 err = ht.host.db.View(func(tx *bolt.Tx) error { 731 so, err = getStorageObligation(tx, so.id()) 732 if err != nil { 733 return err 734 } 735 if so.SectorRoots != nil { 736 t.Error("sector roots were not cleared out when the storage proof was finalized") 737 } 738 if so.ObligationStatus != obligationSucceeded { 739 t.Error("storage obligation was not reported as a success") 740 } 741 return nil 742 }) 743 if err != nil { 744 t.Fatal(err) 745 } 746 if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost) { 747 t.Fatal("the host should be reporting revenue after a successful storage proof") 748 } 749 }