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