github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/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 "SiaPrime/build" 14 "SiaPrime/crypto" 15 "SiaPrime/modules" 16 "SiaPrime/types" 17 "gitlab.com/NebulousLabs/fastrand" 18 19 "gitlab.com/NebulousLabs/bolt" 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.mu.Lock() 227 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 228 ht.host.mu.Unlock() 229 validPayouts, missedPayouts := so.payouts() 230 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 231 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 232 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 233 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 234 revisionSet := []types.Transaction{{ 235 FileContractRevisions: []types.FileContractRevision{{ 236 ParentID: so.id(), 237 UnlockConditions: types.UnlockConditions{}, 238 NewRevisionNumber: 1, 239 240 NewFileSize: uint64(len(sectorData)), 241 NewFileMerkleRoot: sectorRoot, 242 NewWindowStart: so.expiration(), 243 NewWindowEnd: so.proofDeadline(), 244 NewValidProofOutputs: validPayouts, 245 NewMissedProofOutputs: missedPayouts, 246 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 247 }}, 248 }} 249 ht.host.managedLockStorageObligation(so.id()) 250 ht.host.mu.Lock() 251 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 252 ht.host.mu.Unlock() 253 if err != nil { 254 t.Fatal(err) 255 } 256 ht.host.managedUnlockStorageObligation(so.id()) 257 // Submit the revision set to the transaction pool. 258 err = ht.tpool.AcceptTransactionSet(revisionSet) 259 if err != nil { 260 t.Fatal(err) 261 } 262 263 // Mine a block to confirm the transactions containing the file contract 264 // and the file contract revision. 265 _, err = ht.miner.AddBlock() 266 if err != nil { 267 t.Fatal(err) 268 } 269 // Load the storage obligation from the database, see if it updated 270 // correctly. 271 err = build.Retry(100, 100*time.Millisecond, func() error { 272 ht.host.mu.Lock() 273 err := ht.host.db.View(func(tx *bolt.Tx) error { 274 so, err = getStorageObligation(tx, so.id()) 275 if err != nil { 276 return err 277 } 278 return nil 279 }) 280 ht.host.mu.Unlock() 281 if err != nil { 282 return err 283 } 284 if !so.OriginConfirmed { 285 return errors.New("origin transaction for storage obligation was not confirmed after a block was mined") 286 } 287 if !so.RevisionConfirmed { 288 return errors.New("revision transaction for storage obligation was not confirmed after a block was mined") 289 } 290 return nil 291 }) 292 if err != nil { 293 t.Fatal(err) 294 } 295 296 // Mine until the host submits a storage proof. 297 ht.host.mu.Lock() 298 bh := ht.host.blockHeight 299 ht.host.mu.Unlock() 300 for i := bh; i <= so.expiration()+resubmissionTimeout; i++ { 301 _, err := ht.miner.AddBlock() 302 if err != nil { 303 t.Fatal(err) 304 } 305 } 306 // Flush the host - flush will block until the host has submitted the 307 // storage proof to the transaction pool. 308 err = ht.host.tg.Flush() 309 if err != nil { 310 t.Fatal(err) 311 } 312 // Mine another block, to get the storage proof from the transaction pool 313 // into the blockchain. 314 _, err = ht.miner.AddBlock() 315 if err != nil { 316 t.Fatal(err) 317 } 318 319 // Grab the storage proof and inspect the contents. 320 err = build.Retry(100, 100*time.Millisecond, func() error { 321 ht.host.mu.Lock() 322 err = ht.host.db.View(func(tx *bolt.Tx) error { 323 so, err = getStorageObligation(tx, so.id()) 324 if err != nil { 325 return err 326 } 327 return nil 328 }) 329 ht.host.mu.Unlock() 330 if err != nil { 331 return err 332 } 333 if !so.OriginConfirmed { 334 return errors.New("origin transaction for storage obligation was not confirmed after a block was mined") 335 } 336 if !so.RevisionConfirmed { 337 return errors.New("revision transaction for storage obligation was not confirmed after a block was mined") 338 } 339 if !so.ProofConfirmed { 340 return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain") 341 } 342 return nil 343 }) 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 // Mine blocks until the storage proof has enough confirmations that the 349 // host will finalize the obligation. 350 for i := 0; i <= int(defaultWindowSize); i++ { 351 _, err := ht.miner.AddBlock() 352 if err != nil { 353 t.Fatal(err) 354 } 355 } 356 ht.host.mu.Lock() 357 err = ht.host.db.View(func(tx *bolt.Tx) error { 358 so, err = getStorageObligation(tx, so.id()) 359 if err != nil { 360 return err 361 } 362 if so.SectorRoots != nil { 363 t.Error("sector roots were not cleared when the host finalized the obligation") 364 } 365 if so.ObligationStatus != obligationSucceeded { 366 t.Error("obligation is not being reported as successful:", so.ObligationStatus) 367 } 368 return nil 369 }) 370 ht.host.mu.Unlock() 371 if err != nil { 372 t.Fatal(err) 373 } 374 ht.host.mu.Lock() 375 storageRevenue := ht.host.financialMetrics.StorageRevenue 376 ht.host.mu.Unlock() 377 if !storageRevenue.Equals(sectorCost) { 378 t.Fatal("the host should be reporting revenue after a successful storage proof") 379 } 380 } 381 382 // TestMultiSectorObligationStack checks that the host correctly manages a 383 // storage obligation with a single sector, the revision is created the same 384 // block as the file contract. 385 // 386 // Unlike the SingleSector test, the multi sector test attempts to spread file 387 // contract revisions over multiple blocks. 388 func TestMultiSectorStorageObligationStack(t *testing.T) { 389 if testing.Short() { 390 t.SkipNow() 391 } 392 t.Parallel() 393 ht, err := newHostTester("TestMultiSectorStorageObligationStack") 394 if err != nil { 395 t.Fatal(err) 396 } 397 defer ht.Close() 398 399 // Start by adding a storage obligation to the host. To emulate conditions 400 // of a renter creating the first contract, the storage obligation has no 401 // data, but does have money. 402 so, err := ht.newTesterStorageObligation() 403 if err != nil { 404 t.Fatal(err) 405 } 406 ht.host.managedLockStorageObligation(so.id()) 407 err = ht.host.managedAddStorageObligation(so) 408 if err != nil { 409 t.Fatal(err) 410 } 411 ht.host.managedUnlockStorageObligation(so.id()) 412 // Storage obligation should not be marked as having the transaction 413 // confirmed on the blockchain. 414 if so.OriginConfirmed { 415 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 416 } 417 // Deviation from SingleSector test - mine a block here to confirm the 418 // storage obligation before a file contract revision is created. 419 _, err = ht.miner.AddBlock() 420 if err != nil { 421 t.Fatal(err) 422 } 423 // Load the storage obligation from the database, see if it updated 424 // correctly. 425 ht.host.mu.Lock() 426 err = ht.host.db.View(func(tx *bolt.Tx) error { 427 so, err = getStorageObligation(tx, so.id()) 428 if err != nil { 429 return err 430 } 431 return nil 432 }) 433 ht.host.mu.Unlock() 434 if err != nil { 435 t.Fatal(err) 436 } 437 if !so.OriginConfirmed { 438 t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined") 439 } 440 441 // Add a file contract revision, moving over a small amount of money to pay 442 // for the file contract. 443 sectorRoot, sectorData := randSector() 444 so.SectorRoots = []crypto.Hash{sectorRoot} 445 sectorCost := types.SiacoinPrecision.Mul64(550) 446 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost) 447 ht.host.mu.Lock() 448 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 449 ht.host.mu.Unlock() 450 validPayouts, missedPayouts := so.payouts() 451 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 452 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 453 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 454 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 455 revisionSet := []types.Transaction{{ 456 FileContractRevisions: []types.FileContractRevision{{ 457 ParentID: so.id(), 458 UnlockConditions: types.UnlockConditions{}, 459 NewRevisionNumber: 1, 460 461 NewFileSize: uint64(len(sectorData)), 462 NewFileMerkleRoot: sectorRoot, 463 NewWindowStart: so.expiration(), 464 NewWindowEnd: so.proofDeadline(), 465 NewValidProofOutputs: validPayouts, 466 NewMissedProofOutputs: missedPayouts, 467 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 468 }}, 469 }} 470 ht.host.managedLockStorageObligation(so.id()) 471 ht.host.mu.Lock() 472 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 473 ht.host.mu.Unlock() 474 if err != nil { 475 t.Fatal(err) 476 } 477 ht.host.managedUnlockStorageObligation(so.id()) 478 // Submit the revision set to the transaction pool. 479 err = ht.tpool.AcceptTransactionSet(revisionSet) 480 if err != nil { 481 t.Fatal(err) 482 } 483 484 // Create a second file contract revision, which is going to be submitted 485 // to the transaction pool after the first revision. Though, in practice 486 // this should never happen, we want to check that the transaction pool is 487 // correctly handling multiple file contract revisions being submitted in 488 // the same block cycle. This test will additionally tell us whether or not 489 // the host can correctly handle building storage proofs for files with 490 // multiple sectors. 491 sectorRoot2, sectorData2 := randSector() 492 so.SectorRoots = []crypto.Hash{sectorRoot, sectorRoot2} 493 sectorCost2 := types.SiacoinPrecision.Mul64(650) 494 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost2) 495 ht.host.mu.Lock() 496 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost2) 497 ht.host.mu.Unlock() 498 validPayouts, missedPayouts = so.payouts() 499 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost2) 500 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost2) 501 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost2) 502 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost2) 503 combinedSectors := append(sectorData, sectorData2...) 504 combinedRoot := crypto.MerkleRoot(combinedSectors) 505 revisionSet2 := []types.Transaction{{ 506 FileContractRevisions: []types.FileContractRevision{{ 507 ParentID: so.id(), 508 UnlockConditions: types.UnlockConditions{}, 509 NewRevisionNumber: 2, 510 511 NewFileSize: uint64(len(sectorData) + len(sectorData2)), 512 NewFileMerkleRoot: combinedRoot, 513 NewWindowStart: so.expiration(), 514 NewWindowEnd: so.proofDeadline(), 515 NewValidProofOutputs: validPayouts, 516 NewMissedProofOutputs: missedPayouts, 517 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 518 }}, 519 }} 520 ht.host.managedLockStorageObligation(so.id()) 521 ht.host.mu.Lock() 522 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot2}, [][]byte{sectorData2}) 523 ht.host.mu.Unlock() 524 if err != nil { 525 t.Fatal(err) 526 } 527 ht.host.managedUnlockStorageObligation(so.id()) 528 // Submit the revision set to the transaction pool. 529 err = ht.tpool.AcceptTransactionSet(revisionSet2) 530 if err != nil { 531 t.Fatal(err) 532 } 533 534 // Mine a block to confirm the transactions containing the file contract 535 // and the file contract revision. 536 _, err = ht.miner.AddBlock() 537 if err != nil { 538 t.Fatal(err) 539 } 540 // Load the storage obligation from the database, see if it updated 541 // correctly. 542 err = build.Retry(100, 100*time.Millisecond, func() error { 543 ht.host.mu.Lock() 544 err := ht.host.db.View(func(tx *bolt.Tx) error { 545 so, err = getStorageObligation(tx, so.id()) 546 if err != nil { 547 return err 548 } 549 return nil 550 }) 551 ht.host.mu.Unlock() 552 if err != nil { 553 return err 554 } 555 if !so.OriginConfirmed { 556 return errors.New("origin transaction for storage obligation was not confirmed after a block was mined") 557 } 558 if !so.RevisionConfirmed { 559 return errors.New("revision transaction for storage obligation was not confirmed after a block was mined") 560 } 561 return nil 562 }) 563 if err != nil { 564 t.Fatal(err) 565 } 566 567 // Mine until the host submits a storage proof. 568 ht.host.mu.Lock() 569 bh := ht.host.blockHeight 570 ht.host.mu.Unlock() 571 for i := bh; i <= so.expiration()+resubmissionTimeout; i++ { 572 _, err := ht.miner.AddBlock() 573 if err != nil { 574 t.Fatal(err) 575 } 576 } 577 // Flush the host - flush will block until the host has submitted the 578 // storage proof to the transaction pool. 579 err = ht.host.tg.Flush() 580 if err != nil { 581 t.Fatal(err) 582 } 583 // Mine another block, to get the storage proof from the transaction pool 584 // into the blockchain. 585 _, err = ht.miner.AddBlock() 586 if err != nil { 587 t.Fatal(err) 588 } 589 590 err = build.Retry(100, 100*time.Millisecond, func() error { 591 ht.host.mu.Lock() 592 err := ht.host.db.View(func(tx *bolt.Tx) error { 593 so, err = getStorageObligation(tx, so.id()) 594 if err != nil { 595 return err 596 } 597 return nil 598 }) 599 ht.host.mu.Unlock() 600 if err != nil { 601 return (err) 602 } 603 if !so.OriginConfirmed { 604 return errors.New("origin transaction for storage obligation was not confirmed after a block was mined") 605 } 606 if !so.RevisionConfirmed { 607 return errors.New("revision transaction for storage obligation was not confirmed after a block was mined") 608 } 609 if !so.ProofConfirmed { 610 return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain") 611 } 612 return nil 613 }) 614 if err != nil { 615 t.Fatal(err) 616 } 617 618 // Mine blocks until the storage proof has enough confirmations that the 619 // host will delete the file entirely. 620 for i := 0; i <= int(defaultWindowSize); i++ { 621 _, err := ht.miner.AddBlock() 622 if err != nil { 623 t.Fatal(err) 624 } 625 } 626 ht.host.mu.Lock() 627 err = ht.host.db.View(func(tx *bolt.Tx) error { 628 so, err = getStorageObligation(tx, so.id()) 629 if err != nil { 630 return err 631 } 632 if so.SectorRoots != nil { 633 t.Error("sector roots were not cleared out when the storage proof was finalized") 634 } 635 if so.ObligationStatus != obligationSucceeded { 636 t.Error("storage obligation was not reported as a success") 637 } 638 return nil 639 }) 640 ht.host.mu.Unlock() 641 if err != nil { 642 t.Fatal(err) 643 } 644 if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost.Add(sectorCost2)) { 645 t.Fatal("the host should be reporting revenue after a successful storage proof") 646 } 647 } 648 649 // TestAutoRevisionSubmission checks that the host correctly submits a file 650 // contract revision to the consensus set. 651 func TestAutoRevisionSubmission(t *testing.T) { 652 if testing.Short() || !build.VLONG { 653 t.SkipNow() 654 } 655 t.Parallel() 656 ht, err := newHostTester("TestAutoRevisionSubmission") 657 if err != nil { 658 t.Fatal(err) 659 } 660 defer ht.Close() 661 662 // Start by adding a storage obligation to the host. To emulate conditions 663 // of a renter creating the first contract, the storage obligation has no 664 // data, but does have money. 665 so, err := ht.newTesterStorageObligation() 666 if err != nil { 667 t.Fatal(err) 668 } 669 ht.host.managedLockStorageObligation(so.id()) 670 err = ht.host.managedAddStorageObligation(so) 671 if err != nil { 672 t.Fatal(err) 673 } 674 ht.host.managedUnlockStorageObligation(so.id()) 675 // Storage obligation should not be marked as having the transaction 676 // confirmed on the blockchain. 677 if so.OriginConfirmed { 678 t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way") 679 } 680 681 // Add a file contract revision, moving over a small amount of money to pay 682 // for the file contract. 683 sectorRoot, sectorData := randSector() 684 so.SectorRoots = []crypto.Hash{sectorRoot} 685 sectorCost := types.SiacoinPrecision.Mul64(550) 686 so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost) 687 ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost) 688 validPayouts, missedPayouts := so.payouts() 689 validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost) 690 validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost) 691 missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost) 692 missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost) 693 revisionSet := []types.Transaction{{ 694 FileContractRevisions: []types.FileContractRevision{{ 695 ParentID: so.id(), 696 UnlockConditions: types.UnlockConditions{}, 697 NewRevisionNumber: 1, 698 699 NewFileSize: uint64(len(sectorData)), 700 NewFileMerkleRoot: sectorRoot, 701 NewWindowStart: so.expiration(), 702 NewWindowEnd: so.proofDeadline(), 703 NewValidProofOutputs: validPayouts, 704 NewMissedProofOutputs: missedPayouts, 705 NewUnlockHash: types.UnlockConditions{}.UnlockHash(), 706 }}, 707 }} 708 so.RevisionTransactionSet = revisionSet 709 ht.host.managedLockStorageObligation(so.id()) 710 err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData}) 711 if err != nil { 712 t.Fatal(err) 713 } 714 ht.host.managedUnlockStorageObligation(so.id()) 715 err = ht.host.tg.Flush() 716 if err != nil { 717 t.Fatal(err) 718 } 719 // Unlike the other tests, this test does not submit the file contract 720 // revision to the transaction pool for the host, the host is expected to 721 // do it automatically. 722 723 // Mine until the host submits a storage proof. 724 for i := types.BlockHeight(0); i <= revisionSubmissionBuffer+2+resubmissionTimeout; i++ { 725 _, err := ht.miner.AddBlock() 726 if err != nil { 727 t.Fatal(err) 728 } 729 err = ht.host.tg.Flush() 730 if err != nil { 731 t.Fatal(err) 732 } 733 } 734 // Flush the host - flush will block until the host has submitted the 735 // storage proof to the transaction pool. 736 err = ht.host.tg.Flush() 737 if err != nil { 738 t.Fatal(err) 739 } 740 // Mine another block, to get the storage proof from the transaction pool 741 // into the blockchain. 742 _, err = ht.miner.AddBlock() 743 if err != nil { 744 t.Fatal(err) 745 } 746 err = ht.host.tg.Flush() 747 if err != nil { 748 t.Fatal(err) 749 } 750 751 err = build.Retry(50, 250*time.Millisecond, func() error { 752 err = ht.host.db.View(func(tx *bolt.Tx) error { 753 so, err = getStorageObligation(tx, so.id()) 754 if err != nil { 755 return err 756 } 757 return nil 758 }) 759 if err != nil { 760 return (err) 761 } 762 if !so.OriginConfirmed { 763 return errors.New("origin transaction for storage obligation was not confirmed after blocks were mined") 764 } 765 if !so.RevisionConfirmed { 766 return errors.New("revision transaction for storage obligation was not confirmed after blocks were mined") 767 } 768 if !so.ProofConfirmed { 769 return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain") 770 } 771 return nil 772 }) 773 if err != nil { 774 t.Fatal(err) 775 } 776 777 // Mine blocks until the storage proof has enough confirmations that the 778 // host will delete the file entirely. 779 for i := 0; i <= int(defaultWindowSize); i++ { 780 _, err := ht.miner.AddBlock() 781 if err != nil { 782 t.Fatal(err) 783 } 784 err = ht.host.tg.Flush() 785 if err != nil { 786 t.Fatal(err) 787 } 788 } 789 err = ht.host.db.View(func(tx *bolt.Tx) error { 790 so, err = getStorageObligation(tx, so.id()) 791 if err != nil { 792 return err 793 } 794 if so.SectorRoots != nil { 795 t.Error("sector roots were not cleared out when the storage proof was finalized") 796 } 797 if so.ObligationStatus != obligationSucceeded { 798 t.Error("storage obligation was not reported as a success") 799 } 800 return nil 801 }) 802 if err != nil { 803 t.Fatal(err) 804 } 805 if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost) { 806 t.Fatal("the host should be reporting revenue after a successful storage proof") 807 } 808 }