gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/host_integration_test.go (about) 1 package contractor 2 3 import ( 4 "bytes" 5 "fmt" 6 "net" 7 "os" 8 "path/filepath" 9 "testing" 10 "time" 11 12 "gitlab.com/NebulousLabs/errors" 13 "gitlab.com/NebulousLabs/fastrand" 14 "gitlab.com/NebulousLabs/ratelimit" 15 "gitlab.com/NebulousLabs/siamux" 16 17 "gitlab.com/NebulousLabs/encoding" 18 "gitlab.com/SkynetLabs/skyd/build" 19 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 20 "gitlab.com/SkynetLabs/skyd/skymodules" 21 "gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb" 22 "go.sia.tech/siad/crypto" 23 "go.sia.tech/siad/modules" 24 "go.sia.tech/siad/modules/consensus" 25 "go.sia.tech/siad/modules/gateway" 26 "go.sia.tech/siad/modules/host" 27 "go.sia.tech/siad/modules/miner" 28 "go.sia.tech/siad/modules/transactionpool" 29 modWallet "go.sia.tech/siad/modules/wallet" 30 "go.sia.tech/siad/types" 31 ) 32 33 // newTestingWallet is a helper function that creates a ready-to-use wallet 34 // and mines some coins into it. 35 func newTestingWallet(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Wallet, closeFn, error) { 36 w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 37 if err != nil { 38 return nil, nil, err 39 } 40 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 41 encrypted, err := w.Encrypted() 42 if err != nil { 43 return nil, nil, err 44 } 45 if !encrypted { 46 _, err = w.Encrypt(key) 47 if err != nil { 48 return nil, nil, err 49 } 50 } 51 err = w.Unlock(key) 52 if err != nil { 53 return nil, nil, err 54 } 55 // give it some money 56 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 57 if err != nil { 58 return nil, nil, err 59 } 60 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 61 _, err := m.AddBlock() 62 if err != nil { 63 return nil, nil, err 64 } 65 } 66 67 cf := func() error { 68 return errors.Compose(m.Close(), w.Close()) 69 } 70 return w, cf, nil 71 } 72 73 // newCustomTestingHost is a helper function that creates a ready-to-use host. 74 func newCustomTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool, mux *siamux.SiaMux, deps modules.Dependencies) (modules.Host, closeFn, error) { 75 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 76 if err != nil { 77 return nil, nil, err 78 } 79 w, walletCF, err := newTestingWallet(testdir, cs, tp) 80 if err != nil { 81 return nil, nil, err 82 } 83 h, err := host.NewCustomHost(deps, cs, g, tp, w, mux, "localhost:0", filepath.Join(testdir, modules.HostDir)) 84 if err != nil { 85 return nil, nil, err 86 } 87 88 // configure host to accept contracts 89 settings := h.InternalSettings() 90 settings.AcceptingContracts = true 91 err = h.SetInternalSettings(settings) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 // add storage to host 97 storageFolder := filepath.Join(testdir, "storage") 98 err = os.MkdirAll(storageFolder, 0700) 99 if err != nil { 100 return nil, nil, err 101 } 102 err = h.AddStorageFolder(storageFolder, modules.SectorSize*64) 103 if err != nil { 104 return nil, nil, err 105 } 106 107 cf := func() error { 108 return errors.Compose(h.Close(), walletCF(), g.Close()) 109 } 110 return h, cf, nil 111 } 112 113 // newTestingHost is a helper function that creates a ready-to-use host. 114 func newTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool, mux *siamux.SiaMux) (modules.Host, closeFn, error) { 115 return newCustomTestingHost(testdir, cs, tp, mux, modules.ProdDependencies) 116 } 117 118 // newTestingContractor is a helper function that creates a ready-to-use 119 // contractor. 120 func newTestingContractor(testdir string, g modules.Gateway, cs modules.ConsensusSet, tp modules.TransactionPool, rl *ratelimit.RateLimit, deps modules.Dependencies) (*Contractor, closeFn, error) { 121 w, walletCF, err := newTestingWallet(testdir, cs, tp) 122 if err != nil { 123 return nil, nil, err 124 } 125 siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir) 126 mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0") 127 if err != nil { 128 return nil, nil, err 129 } 130 hdb, errChan := hostdb.New(g, cs, tp, mux, filepath.Join(testdir, "hostdb")) 131 if err := <-errChan; err != nil { 132 return nil, nil, err 133 } 134 contractor, errChan := newWithDeps(cs, w, tp, hdb, rl, filepath.Join(testdir, "contractor"), deps) 135 err = <-errChan 136 if err != nil { 137 return nil, nil, err 138 } 139 cf := func() error { 140 return errors.Compose(contractor.Close(), hdb.Close(), mux.Close(), walletCF()) 141 } 142 return contractor, cf, <-errChan 143 } 144 145 // newTestingTrio creates a Host, Contractor, and TestMiner that can be 146 // used for testing host/renter interactions. 147 func newTestingTrio(name string) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) { 148 return newTestingTrioWithContractorDeps(name, modules.ProdDependencies) 149 } 150 151 // newTestingTrioWithContractorDeps creates a Host, Contractor, and TestMiner 152 // that can be used for testing host/renter interactions. 153 func newTestingTrioWithContractorDeps(name string, deps modules.Dependencies) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) { 154 testdir := build.TempDir("contractor", name) 155 156 // create mux 157 siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir) 158 mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0") 159 if err != nil { 160 return nil, nil, nil, nil, err 161 } 162 163 return newCustomTestingTrio(name, mux, modules.ProdDependencies, deps) 164 } 165 166 // newCustomTestingTrio creates a Host, Contractor, and TestMiner that can be 167 // used for testing host/renter interactions. It allows to pass a custom siamux. 168 func newCustomTestingTrio(name string, mux *siamux.SiaMux, hdeps, cdeps modules.Dependencies) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) { 169 testdir := build.TempDir("contractor", name) 170 171 // create miner 172 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 173 if err != nil { 174 return nil, nil, nil, nil, err 175 } 176 cs, errChan := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 177 if err := <-errChan; err != nil { 178 return nil, nil, nil, nil, err 179 } 180 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 181 if err != nil { 182 return nil, nil, nil, nil, err 183 } 184 w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 185 if err != nil { 186 return nil, nil, nil, nil, err 187 } 188 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 189 encrypted, err := w.Encrypted() 190 if err != nil { 191 return nil, nil, nil, nil, err 192 } 193 if !encrypted { 194 _, err = w.Encrypt(key) 195 if err != nil { 196 return nil, nil, nil, nil, err 197 } 198 } 199 err = w.Unlock(key) 200 if err != nil { 201 return nil, nil, nil, nil, err 202 } 203 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 204 if err != nil { 205 return nil, nil, nil, nil, err 206 } 207 208 // create host and contractor, using same consensus set and gateway 209 h, hostCF, err := newCustomTestingHost(filepath.Join(testdir, "Host"), cs, tp, mux, hdeps) 210 if err != nil { 211 return nil, nil, nil, nil, build.ExtendErr("error creating testing host", err) 212 } 213 c, contractorCF, err := newTestingContractor(filepath.Join(testdir, "Contractor"), g, cs, tp, ratelimit.NewRateLimit(0, 0, 0), cdeps) 214 if err != nil { 215 return nil, nil, nil, nil, err 216 } 217 218 // announce the host 219 err = h.Announce() 220 if err != nil { 221 return nil, nil, nil, nil, build.ExtendErr("error announcing host", err) 222 } 223 224 // mine a block, processing the announcement 225 _, err = m.AddBlock() 226 if err != nil { 227 return nil, nil, nil, nil, err 228 } 229 230 // wait for hostdb to scan host 231 err = build.Retry(100, 100*time.Millisecond, func() error { 232 activeHosts, err := c.staticHDB.ActiveHosts() 233 if err != nil { 234 return err 235 } 236 if len(activeHosts) == 0 { 237 return errors.New("no active hosts") 238 } 239 complete, scanCheckErr := c.staticHDB.InitialScanComplete() 240 if scanCheckErr != nil { 241 return scanCheckErr 242 } 243 if !complete { 244 return errors.New("initial scan not complete") 245 } 246 return nil 247 }) 248 if err != nil { 249 return nil, nil, nil, nil, err 250 } 251 252 cf := func() error { 253 return errors.Compose(mux.Close(), m.Close(), contractorCF(), hostCF(), w.Close(), tp.Close(), cs.Close(), g.Close()) 254 } 255 return h, c, m, cf, nil 256 } 257 258 // TestIntegrationFormContract tests that the contractor can form contracts 259 // with the host module. 260 func TestIntegrationFormContract(t *testing.T) { 261 if testing.Short() { 262 t.SkipNow() 263 } 264 t.Parallel() 265 h, c, _, cf, err := newTestingTrio(t.Name()) 266 if err != nil { 267 t.Fatal(err) 268 } 269 defer tryClose(cf, t) 270 271 // acquire the contract maintenance lock for the duration of the test. This 272 // prevents theadedContractMaintenance from running. 273 c.maintenanceLock.Lock() 274 defer c.maintenanceLock.Unlock() 275 276 // get the host's entry from the db 277 hostEntry, ok, err := c.staticHDB.Host(h.PublicKey()) 278 if err != nil { 279 t.Fatal(err) 280 } 281 if !ok { 282 t.Fatal("no entry for host in db") 283 } 284 285 // set an allowance but don't use SetAllowance to avoid automatic contract 286 // formation. 287 c.mu.Lock() 288 c.allowance = skymodules.DefaultAllowance 289 a := c.allowance 290 c.mu.Unlock() 291 292 // form a contract with the host 293 _, _, err = c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 294 if err != nil { 295 t.Fatal(err) 296 } 297 } 298 299 // TestFormContractSmallAllowance tests to make sure that a contract doesn't 300 // form when there are insufficient funds in the allowance 301 func TestFormContractSmallAllowance(t *testing.T) { 302 if testing.Short() { 303 t.SkipNow() 304 } 305 t.Parallel() 306 h, c, _, cf, err := newTestingTrio(t.Name()) 307 if err != nil { 308 t.Fatal(err) 309 } 310 defer tryClose(cf, t) 311 312 // get the host's entry from the db 313 hostEntry, ok, err := c.staticHDB.Host(h.PublicKey()) 314 if err != nil { 315 t.Fatal(err) 316 } 317 if !ok { 318 t.Fatal("no entry for host in db") 319 } 320 321 // set an allowance but don't use SetAllowance to avoid automatic contract 322 // formation. Setting funds to 1SC to mimic bug report found in production. 323 // Using production number of hosts as well 324 c.mu.Lock() 325 c.allowance = skymodules.DefaultAllowance 326 c.allowance.Funds = types.SiacoinPrecision.Mul64(1) 327 c.allowance.Hosts = uint64(50) 328 a := c.allowance 329 initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3) 330 c.mu.Unlock() 331 332 // try to form a contract with the host 333 _, _, err = c.managedNewContract(hostEntry, a, initialContractFunds, c.blockHeight+100) 334 if err == nil { 335 t.Fatal("Expected underflow error for insufficient funds") 336 } 337 } 338 339 // TestIntegrationReviseContract tests that the contractor can revise a 340 // contract previously formed with a host. 341 func TestIntegrationReviseContract(t *testing.T) { 342 if testing.Short() { 343 t.SkipNow() 344 } 345 t.Parallel() 346 // create testing trio 347 h, c, _, cf, err := newTestingTrio(t.Name()) 348 if err != nil { 349 t.Fatal(err) 350 } 351 defer tryClose(cf, t) 352 353 // acquire the contract maintenance lock for the duration of the test. This 354 // prevents theadedContractMaintenance from running. 355 c.maintenanceLock.Lock() 356 defer c.maintenanceLock.Unlock() 357 358 // get the host's entry from the db 359 hostEntry, ok, err := c.staticHDB.Host(h.PublicKey()) 360 if err != nil { 361 t.Fatal(err) 362 } 363 if !ok { 364 t.Fatal("no entry for host in db") 365 } 366 367 // set an allowance but don't use SetAllowance to avoid automatic contract 368 // formation. 369 c.mu.Lock() 370 c.allowance = skymodules.DefaultAllowance 371 a := c.allowance 372 c.mu.Unlock() 373 374 // form a contract with the host 375 _, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 376 if err != nil { 377 t.Fatal(err) 378 } 379 380 // revise the contract 381 editor, err := c.Editor(contract.HostPublicKey, nil) 382 if err != nil { 383 t.Fatal(err) 384 } 385 data := fastrand.Bytes(int(modules.SectorSize)) 386 _, err = editor.Upload(data) 387 if err != nil { 388 t.Fatal(err) 389 } 390 err = editor.Close() 391 if err != nil { 392 t.Fatal(err) 393 } 394 } 395 396 // TestIntegrationUploadDownload tests that the contractor can upload data to 397 // a host and download it intact. 398 func TestIntegrationUploadDownload(t *testing.T) { 399 if testing.Short() { 400 t.SkipNow() 401 } 402 t.Parallel() 403 // create testing trio 404 h, c, _, cf, err := newTestingTrio(t.Name()) 405 if err != nil { 406 t.Fatal(err) 407 } 408 defer tryClose(cf, t) 409 410 // get the host's entry from the db 411 hostEntry, ok, err := c.staticHDB.Host(h.PublicKey()) 412 if err != nil { 413 t.Fatal(err) 414 } 415 if !ok { 416 t.Fatal("no entry for host in db") 417 } 418 419 // set an allowance but don't use SetAllowance to avoid automatic contract 420 // formation. 421 c.mu.Lock() 422 c.allowance = skymodules.DefaultAllowance 423 a := c.allowance 424 c.mu.Unlock() 425 426 // form a contract with the host 427 _, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 428 if err != nil { 429 t.Fatal(err) 430 } 431 432 // revise the contract 433 editor, err := c.Editor(contract.HostPublicKey, nil) 434 if err != nil { 435 t.Fatal(err) 436 } 437 data := fastrand.Bytes(int(modules.SectorSize)) 438 root, err := editor.Upload(data) 439 if err != nil { 440 t.Fatal(err) 441 } 442 err = editor.Close() 443 if err != nil { 444 t.Fatal(err) 445 } 446 447 // download the data 448 downloader, err := c.Downloader(contract.HostPublicKey, nil) 449 if err != nil { 450 t.Fatal(err) 451 } 452 retrieved, err := downloader.Download(root, 0, uint32(modules.SectorSize)) 453 if err != nil { 454 t.Fatal(err) 455 } 456 if !bytes.Equal(data, retrieved) { 457 t.Fatal("downloaded data does not match original") 458 } 459 err = downloader.Close() 460 if err != nil { 461 t.Fatal(err) 462 } 463 } 464 465 // TestIntegrationRenew tests that the contractor can renew a previously- 466 // formed file contract. 467 func TestIntegrationRenew(t *testing.T) { 468 if testing.Short() { 469 t.SkipNow() 470 } 471 t.Parallel() 472 // create testing trio 473 _, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{}) 474 if err != nil { 475 t.Fatal(err) 476 } 477 defer tryClose(cf, t) 478 479 // set an allowance and wait for a contract to be formed. 480 a := skymodules.DefaultAllowance 481 a.Hosts = 1 482 if err := c.SetAllowance(a); err != nil { 483 t.Fatal(err) 484 } 485 numRetries := 0 486 err = build.Retry(100, 100*time.Millisecond, func() error { 487 if numRetries%10 == 0 { 488 if _, err := m.AddBlock(); err != nil { 489 return err 490 } 491 } 492 numRetries++ 493 // Check for number of contracts and number of pubKeys as there is a 494 // slight delay between the contract being added to the contract set and 495 // the pubkey being added to the contractor map 496 c.mu.Lock() 497 numPubKeys := len(c.pubKeysToContractID) 498 c.mu.Unlock() 499 numContracts := len(c.Contracts()) 500 if numContracts != 1 { 501 return fmt.Errorf("Expected 1 contracts, found %v", numContracts) 502 } 503 if numPubKeys != 1 { 504 return fmt.Errorf("Expected 1 pubkey, found %v", numPubKeys) 505 } 506 return nil 507 }) 508 if err != nil { 509 t.Fatal(err) 510 } 511 // get the contract 512 contract := c.Contracts()[0] 513 514 // revise the contract 515 editor, err := c.Editor(contract.HostPublicKey, nil) 516 if err != nil { 517 t.Fatal(err) 518 } 519 data := fastrand.Bytes(int(modules.SectorSize)) 520 // insert the sector 521 root, err := editor.Upload(data) 522 if err != nil { 523 t.Fatal(err) 524 } 525 err = editor.Close() 526 if err != nil { 527 t.Fatal(err) 528 } 529 530 // Grab the host settings. 531 hostSettings := editor.HostSettings() 532 533 // renew the contract 534 err = c.managedAcquireAndUpdateContractUtility(contract.ID, skymodules.ContractUtility{GoodForRenew: true}, false) 535 if err != nil { 536 t.Fatal(err) 537 } 538 contract, err = c.managedRenew(contract.ID, a, contract.HostPublicKey, types.SiacoinPrecision.Mul64(50), c.blockHeight+200, hostSettings) 539 if err != nil { 540 t.Fatal(err) 541 } 542 543 // check renewed contract 544 if contract.EndHeight != c.blockHeight+200 { 545 t.Fatal(contract.EndHeight) 546 } 547 548 // download the renewed contract 549 downloader, err := c.Downloader(contract.HostPublicKey, nil) 550 if err != nil { 551 t.Fatal(err) 552 } 553 retrieved, err := downloader.Download(root, 0, uint32(modules.SectorSize)) 554 if err != nil { 555 t.Fatal(err) 556 } 557 if !bytes.Equal(data, retrieved) { 558 t.Fatal("downloaded data does not match original") 559 } 560 err = downloader.Close() 561 if err != nil { 562 t.Fatal(err) 563 } 564 565 // renew to a lower height 566 err = c.managedAcquireAndUpdateContractUtility(contract.ID, skymodules.ContractUtility{GoodForRenew: true}, false) 567 if err != nil { 568 t.Fatal(err) 569 } 570 contract, err = c.managedRenew(contract.ID, a, contract.HostPublicKey, types.SiacoinPrecision.Mul64(50), c.blockHeight+100, hostSettings) 571 if err != nil { 572 t.Fatal(err) 573 } 574 if contract.EndHeight != c.blockHeight+100 { 575 t.Fatal(contract.EndHeight) 576 } 577 578 // revise the contract 579 editor, err = c.Editor(contract.HostPublicKey, nil) 580 if err != nil { 581 t.Fatal(err) 582 } 583 data = fastrand.Bytes(int(modules.SectorSize)) 584 // insert the sector 585 _, err = editor.Upload(data) 586 if err != nil { 587 t.Fatal(err) 588 } 589 err = editor.Close() 590 if err != nil { 591 t.Fatal(err) 592 } 593 } 594 595 // TestIntegrationDownloaderCaching tests that downloaders are properly cached 596 // by the contractor. When two downloaders are requested for the same 597 // contract, only one underlying downloader should be created. 598 func TestIntegrationDownloaderCaching(t *testing.T) { 599 if testing.Short() { 600 t.SkipNow() 601 } 602 t.Parallel() 603 // create testing trio 604 _, c, m, cf, err := newTestingTrio(t.Name()) 605 if err != nil { 606 t.Fatal(err) 607 } 608 defer tryClose(cf, t) 609 610 // set an allowance and wait for a contract to be formed. 611 if err := c.SetAllowance(skymodules.DefaultAllowance); err != nil { 612 t.Fatal(err) 613 } 614 if err := build.Retry(10, time.Second, func() error { 615 _, err := m.AddBlock() 616 if err != nil { 617 return err 618 } 619 if len(c.Contracts()) == 0 { 620 return errors.New("no contracts were formed") 621 } 622 return nil 623 }); err != nil { 624 t.Fatal(err) 625 } 626 // get the contract 627 contract := c.Contracts()[0] 628 629 // create a downloader 630 d1, err := c.Downloader(contract.HostPublicKey, nil) 631 if err != nil { 632 t.Fatal(err) 633 } 634 635 // create another downloader 636 d2, err := c.Downloader(contract.HostPublicKey, nil) 637 if err != nil { 638 t.Fatal(err) 639 } 640 641 // downloaders should match 642 if d1 != d2 { 643 t.Fatal("downloader was not cached") 644 } 645 646 // close one of the downloaders; it should not fully close, since d1 is 647 // still using it 648 if err := d2.Close(); err != nil { 649 t.Fatal(err) 650 } 651 652 c.mu.RLock() 653 _, ok := c.downloaders[contract.ID] 654 _, sok := c.sessions[contract.ID] 655 c.mu.RUnlock() 656 if !ok && !sok { 657 t.Fatal("expected downloader to still be present") 658 } 659 660 // create another downloader 661 d3, err := c.Downloader(contract.HostPublicKey, nil) 662 if err != nil { 663 t.Fatal(err) 664 } 665 666 // downloaders should match 667 if d3 != d1 { 668 t.Fatal("closing one client should not fully close the downloader") 669 } 670 671 // close both downloaders 672 if err := d1.Close(); err != nil { 673 t.Fatal(err) 674 } 675 if err := d2.Close(); err != nil { 676 t.Fatal(err) 677 } 678 679 c.mu.RLock() 680 _, ok = c.downloaders[contract.ID] 681 _, sok = c.sessions[contract.ID] 682 c.mu.RUnlock() 683 if ok || sok { 684 t.Fatal("did not expect downloader to still be present") 685 } 686 687 // create another downloader 688 d4, err := c.Downloader(contract.HostPublicKey, nil) 689 if err != nil { 690 t.Fatal(err) 691 } 692 693 // downloaders should match 694 if d4 == d1 { 695 t.Fatal("downloader should not have been cached after all clients were closed") 696 } 697 d4.Close() 698 } 699 700 // TestIntegrationEditorCaching tests that editors are properly cached 701 // by the contractor. When two editors are requested for the same 702 // contract, only one underlying editor should be created. 703 func TestIntegrationEditorCaching(t *testing.T) { 704 if testing.Short() { 705 t.SkipNow() 706 } 707 t.Parallel() 708 // create testing trio 709 _, c, m, cf, err := newTestingTrio(t.Name()) 710 if err != nil { 711 t.Fatal(err) 712 } 713 defer tryClose(cf, t) 714 715 // set an allowance and wait for a contract to be formed. 716 if err := c.SetAllowance(skymodules.DefaultAllowance); err != nil { 717 t.Fatal(err) 718 } 719 numRetries := 0 720 if err := build.Retry(2000, 100*time.Millisecond, func() error { 721 if numRetries%10 == 0 { 722 if _, err := m.AddBlock(); err != nil { 723 return err 724 } 725 } 726 numRetries++ 727 if len(c.Contracts()) == 0 { 728 return errors.New("no contracts were formed") 729 } 730 return nil 731 }); err != nil { 732 t.Fatal(err) 733 } 734 // get the contract 735 contract := c.Contracts()[0] 736 737 // create an editor 738 var d1 Editor 739 err = build.Retry(100, 100*time.Millisecond, func() error { 740 d1, err = c.Editor(contract.HostPublicKey, nil) 741 if err != nil { 742 return err 743 } 744 return nil 745 }) 746 if err != nil { 747 t.Fatal(err) 748 } 749 750 // create another editor 751 d2, err := c.Editor(contract.HostPublicKey, nil) 752 if err != nil { 753 t.Fatal(err) 754 } 755 756 // editors should match 757 if d1 != d2 { 758 t.Fatal("editor was not cached") 759 } 760 761 // close one of the editors; it should not fully close, since d1 is 762 // still using it 763 if err := d2.Close(); err != nil { 764 t.Fatal(err) 765 } 766 767 c.mu.RLock() 768 _, ok := c.editors[contract.ID] 769 _, sok := c.sessions[contract.ID] 770 c.mu.RUnlock() 771 if !ok && !sok { 772 t.Fatal("expected editor to still be present") 773 } 774 775 // create another editor 776 d3, err := c.Editor(contract.HostPublicKey, nil) 777 if err != nil { 778 t.Fatal(err) 779 } 780 781 // editors should match 782 if d3 != d1 { 783 t.Fatal("closing one client should not fully close the editor") 784 } 785 786 // close both editors 787 if err := d1.Close(); err != nil { 788 t.Fatal(err) 789 } 790 if err := d2.Close(); err != nil { 791 t.Fatal(err) 792 } 793 794 c.mu.RLock() 795 _, ok = c.editors[contract.ID] 796 _, sok = c.sessions[contract.ID] 797 c.mu.RUnlock() 798 if ok || sok { 799 t.Fatal("did not expect editor to still be present") 800 } 801 802 // create another editor 803 d4, err := c.Editor(contract.HostPublicKey, nil) 804 if err != nil { 805 t.Fatal(err) 806 } 807 808 // editors should match 809 if d4 == d1 { 810 t.Fatal("editor should not have been cached after all clients were closed") 811 } 812 d4.Close() 813 } 814 815 // TestContractPresenceLeak tests that a renter can not tell from the response 816 // of the host to RPCs if the host has the contract if the renter doesn't 817 // own this contract. See https://gitlab.com/NebulousLabs/Sia/issues/2327. 818 func TestContractPresenceLeak(t *testing.T) { 819 if testing.Short() { 820 t.SkipNow() 821 } 822 t.Parallel() 823 // create testing trio 824 h, c, _, cf, err := newTestingTrio(t.Name()) 825 if err != nil { 826 t.Fatal(err) 827 } 828 defer tryClose(cf, t) 829 830 // get the host's entry from the db 831 hostEntry, ok, err := c.staticHDB.Host(h.PublicKey()) 832 if err != nil { 833 t.Fatal(err) 834 } 835 if !ok { 836 t.Fatal("no entry for host in db") 837 } 838 839 // set an allowance but don't use SetAllowance to avoid automatic contract 840 // formation. 841 c.mu.Lock() 842 c.allowance = skymodules.DefaultAllowance 843 a := c.allowance 844 c.mu.Unlock() 845 846 // form a contract with the host 847 _, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(10), c.blockHeight+100) 848 if err != nil { 849 t.Fatal(err) 850 } 851 852 // Connect with bad challenge response. Try correct 853 // and incorrect contract IDs. Compare errors. 854 wrongID := contract.ID 855 wrongID[0] ^= 0x01 856 fcids := []types.FileContractID{contract.ID, wrongID} 857 var errors []error 858 859 for _, fcid := range fcids { 860 var challenge crypto.Hash 861 var signature crypto.Signature 862 conn, err := net.Dial("tcp", string(hostEntry.NetAddress)) 863 if err != nil { 864 t.Fatalf("Couldn't dial tpc connection with host @ %v: %v.", string(hostEntry.NetAddress), err) 865 } 866 if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil { 867 t.Fatalf("Couldn't initiate RPC: %v.", err) 868 } 869 if err := encoding.WriteObject(conn, fcid); err != nil { 870 t.Fatalf("Couldn't send fcid: %v.", err) 871 } 872 if err := encoding.ReadObject(conn, &challenge, 32); err != nil { 873 t.Fatalf("Couldn't read challenge: %v.", err) 874 } 875 if err := encoding.WriteObject(conn, signature); err != nil { 876 t.Fatalf("Couldn't send signature: %v.", err) 877 } 878 err = modules.ReadNegotiationAcceptance(conn) 879 if err == nil { 880 t.Fatal("Expected an error, got success.") 881 } 882 errors = append(errors, err) 883 } 884 if errors[0].Error() != errors[1].Error() { 885 t.Fatalf("Expected to get equal errors, got %q and %q.", errors[0], errors[1]) 886 } 887 }