github.com/nebulouslabs/sia@v1.3.7/modules/renter/contractor/host_integration_test.go (about) 1 package contractor 2 3 import ( 4 "bytes" 5 "errors" 6 "net" 7 "os" 8 "path/filepath" 9 "testing" 10 "time" 11 12 "github.com/NebulousLabs/Sia/build" 13 "github.com/NebulousLabs/Sia/crypto" 14 "github.com/NebulousLabs/Sia/encoding" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/modules/consensus" 17 "github.com/NebulousLabs/Sia/modules/gateway" 18 "github.com/NebulousLabs/Sia/modules/host" 19 "github.com/NebulousLabs/Sia/modules/miner" 20 "github.com/NebulousLabs/Sia/modules/renter/hostdb" 21 "github.com/NebulousLabs/Sia/modules/transactionpool" 22 modWallet "github.com/NebulousLabs/Sia/modules/wallet" 23 "github.com/NebulousLabs/Sia/types" 24 "github.com/NebulousLabs/fastrand" 25 ) 26 27 // newTestingWallet is a helper function that creates a ready-to-use wallet 28 // and mines some coins into it. 29 func newTestingWallet(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Wallet, error) { 30 w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 31 if err != nil { 32 return nil, err 33 } 34 key := crypto.GenerateTwofishKey() 35 encrypted, err := w.Encrypted() 36 if err != nil { 37 return nil, err 38 } 39 if !encrypted { 40 _, err = w.Encrypt(key) 41 if err != nil { 42 return nil, err 43 } 44 } 45 err = w.Unlock(key) 46 if err != nil { 47 return nil, err 48 } 49 // give it some money 50 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 51 if err != nil { 52 return nil, err 53 } 54 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 55 _, err := m.AddBlock() 56 if err != nil { 57 return nil, err 58 } 59 } 60 return w, nil 61 } 62 63 // newTestingHost is a helper function that creates a ready-to-use host. 64 func newTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Host, error) { 65 w, err := newTestingWallet(testdir, cs, tp) 66 if err != nil { 67 return nil, err 68 } 69 h, err := host.New(cs, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 70 if err != nil { 71 return nil, err 72 } 73 74 // configure host to accept contracts 75 settings := h.InternalSettings() 76 settings.AcceptingContracts = true 77 err = h.SetInternalSettings(settings) 78 if err != nil { 79 return nil, err 80 } 81 82 // add storage to host 83 storageFolder := filepath.Join(testdir, "storage") 84 err = os.MkdirAll(storageFolder, 0700) 85 if err != nil { 86 return nil, err 87 } 88 err = h.AddStorageFolder(storageFolder, modules.SectorSize*64) 89 if err != nil { 90 return nil, err 91 } 92 93 return h, nil 94 } 95 96 // newTestingContractor is a helper function that creates a ready-to-use 97 // contractor. 98 func newTestingContractor(testdir string, g modules.Gateway, cs modules.ConsensusSet, tp modules.TransactionPool) (*Contractor, error) { 99 w, err := newTestingWallet(testdir, cs, tp) 100 if err != nil { 101 return nil, err 102 } 103 hdb, err := hostdb.New(g, cs, filepath.Join(testdir, "hostdb")) 104 if err != nil { 105 return nil, err 106 } 107 return New(cs, w, tp, hdb, filepath.Join(testdir, "contractor")) 108 } 109 110 // newTestingTrio creates a Host, Contractor, and TestMiner that can be used 111 // for testing host/renter interactions. 112 func newTestingTrio(name string) (modules.Host, *Contractor, modules.TestMiner, error) { 113 testdir := build.TempDir("contractor", name) 114 115 // create miner 116 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 117 if err != nil { 118 return nil, nil, nil, err 119 } 120 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 121 if err != nil { 122 return nil, nil, nil, err 123 } 124 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 125 if err != nil { 126 return nil, nil, nil, err 127 } 128 w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 129 if err != nil { 130 return nil, nil, nil, err 131 } 132 key := crypto.GenerateTwofishKey() 133 encrypted, err := w.Encrypted() 134 if err != nil { 135 return nil, nil, nil, err 136 } 137 if !encrypted { 138 _, err = w.Encrypt(key) 139 if err != nil { 140 return nil, nil, nil, err 141 } 142 } 143 err = w.Unlock(key) 144 if err != nil { 145 return nil, nil, nil, err 146 } 147 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 148 if err != nil { 149 return nil, nil, nil, err 150 } 151 152 // create host and contractor, using same consensus set and gateway 153 h, err := newTestingHost(filepath.Join(testdir, "Host"), cs, tp) 154 if err != nil { 155 return nil, nil, nil, build.ExtendErr("error creating testing host", err) 156 } 157 c, err := newTestingContractor(filepath.Join(testdir, "Contractor"), g, cs, tp) 158 if err != nil { 159 return nil, nil, nil, err 160 } 161 162 // announce the host 163 err = h.Announce() 164 if err != nil { 165 return nil, nil, nil, build.ExtendErr("error announcing host", err) 166 } 167 168 // mine a block, processing the announcement 169 _, err = m.AddBlock() 170 if err != nil { 171 return nil, nil, nil, err 172 } 173 174 // wait for hostdb to scan host 175 for i := 0; i < 50 && len(c.hdb.ActiveHosts()) == 0; i++ { 176 time.Sleep(time.Millisecond * 100) 177 } 178 if len(c.hdb.ActiveHosts()) == 0 { 179 return nil, nil, nil, errors.New("host did not make it into the contractor hostdb in time") 180 } 181 182 return h, c, m, nil 183 } 184 185 // TestIntegrationFormContract tests that the contractor can form contracts 186 // with the host module. 187 func TestIntegrationFormContract(t *testing.T) { 188 if testing.Short() { 189 t.SkipNow() 190 } 191 t.Parallel() 192 h, c, _, err := newTestingTrio(t.Name()) 193 if err != nil { 194 t.Fatal(err) 195 } 196 defer h.Close() 197 defer c.Close() 198 199 // get the host's entry from the db 200 hostEntry, ok := c.hdb.Host(h.PublicKey()) 201 if !ok { 202 t.Fatal("no entry for host in db") 203 } 204 205 // form a contract with the host 206 _, err = c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 207 if err != nil { 208 t.Fatal(err) 209 } 210 } 211 212 // TestIntegrationReviseContract tests that the contractor can revise a 213 // contract previously formed with a host. 214 func TestIntegrationReviseContract(t *testing.T) { 215 if testing.Short() { 216 t.SkipNow() 217 } 218 t.Parallel() 219 // create testing trio 220 h, c, _, err := newTestingTrio(t.Name()) 221 if err != nil { 222 t.Fatal(err) 223 } 224 defer h.Close() 225 defer c.Close() 226 227 // get the host's entry from the db 228 hostEntry, ok := c.hdb.Host(h.PublicKey()) 229 if !ok { 230 t.Fatal("no entry for host in db") 231 } 232 233 // form a contract with the host 234 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 // revise the contract 240 editor, err := c.Editor(contract.HostPublicKey, nil) 241 if err != nil { 242 t.Fatal(err) 243 } 244 data := fastrand.Bytes(int(modules.SectorSize)) 245 _, err = editor.Upload(data) 246 if err != nil { 247 t.Fatal(err) 248 } 249 err = editor.Close() 250 if err != nil { 251 t.Fatal(err) 252 } 253 } 254 255 // TestIntegrationUploadDownload tests that the contractor can upload data to 256 // a host and download it intact. 257 func TestIntegrationUploadDownload(t *testing.T) { 258 if testing.Short() { 259 t.SkipNow() 260 } 261 t.Parallel() 262 // create testing trio 263 h, c, _, err := newTestingTrio(t.Name()) 264 if err != nil { 265 t.Fatal(err) 266 } 267 defer h.Close() 268 defer c.Close() 269 270 // get the host's entry from the db 271 hostEntry, ok := c.hdb.Host(h.PublicKey()) 272 if !ok { 273 t.Fatal("no entry for host in db") 274 } 275 276 // form a contract with the host 277 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 278 if err != nil { 279 t.Fatal(err) 280 } 281 282 // revise the contract 283 editor, err := c.Editor(contract.HostPublicKey, nil) 284 if err != nil { 285 t.Fatal(err) 286 } 287 data := fastrand.Bytes(int(modules.SectorSize)) 288 root, err := editor.Upload(data) 289 if err != nil { 290 t.Fatal(err) 291 } 292 err = editor.Close() 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 // download the data 298 downloader, err := c.Downloader(contract.HostPublicKey, nil) 299 if err != nil { 300 t.Fatal(err) 301 } 302 retrieved, err := downloader.Sector(root) 303 if err != nil { 304 t.Fatal(err) 305 } 306 if !bytes.Equal(data, retrieved) { 307 t.Fatal("downloaded data does not match original") 308 } 309 err = downloader.Close() 310 if err != nil { 311 t.Fatal(err) 312 } 313 } 314 315 // TestIntegrationRenew tests that the contractor can renew a previously- 316 // formed file contract. 317 func TestIntegrationRenew(t *testing.T) { 318 if testing.Short() { 319 t.SkipNow() 320 } 321 t.Parallel() 322 // create testing trio 323 h, c, _, err := newTestingTrio(t.Name()) 324 if err != nil { 325 t.Fatal(err) 326 } 327 defer h.Close() 328 defer c.Close() 329 330 // get the host's entry from the db 331 hostEntry, ok := c.hdb.Host(h.PublicKey()) 332 if !ok { 333 t.Fatal("no entry for host in db") 334 } 335 336 // form a contract with the host 337 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 338 if err != nil { 339 t.Fatal(err) 340 } 341 342 // revise the contract 343 editor, err := c.Editor(contract.HostPublicKey, nil) 344 if err != nil { 345 t.Fatal(err) 346 } 347 data := fastrand.Bytes(int(modules.SectorSize)) 348 // insert the sector 349 root, err := editor.Upload(data) 350 if err != nil { 351 t.Fatal(err) 352 } 353 err = editor.Close() 354 if err != nil { 355 t.Fatal(err) 356 } 357 358 // renew the contract 359 err = c.managedUpdateContractUtility(contract.ID, modules.ContractUtility{GoodForRenew: true}) 360 if err != nil { 361 t.Fatal(err) 362 } 363 oldContract, ok := c.staticContracts.Acquire(contract.ID) 364 if !ok { 365 t.Fatal("failed to acquire contract") 366 } 367 contract, err = c.managedRenew(oldContract, types.SiacoinPrecision.Mul64(50), c.blockHeight+200) 368 if err != nil { 369 t.Fatal(err) 370 } 371 c.staticContracts.Return(oldContract) 372 373 // check renewed contract 374 if contract.EndHeight != c.blockHeight+200 { 375 t.Fatal(contract.EndHeight) 376 } 377 378 // download the renewed contract 379 downloader, err := c.Downloader(contract.HostPublicKey, nil) 380 if err != nil { 381 t.Fatal(err) 382 } 383 retrieved, err := downloader.Sector(root) 384 if err != nil { 385 t.Fatal(err) 386 } 387 if !bytes.Equal(data, retrieved) { 388 t.Fatal("downloaded data does not match original") 389 } 390 err = downloader.Close() 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 // renew to a lower height 396 err = c.managedUpdateContractUtility(contract.ID, modules.ContractUtility{GoodForRenew: true}) 397 if err != nil { 398 t.Fatal(err) 399 } 400 oldContract, _ = c.staticContracts.Acquire(contract.ID) 401 contract, err = c.managedRenew(oldContract, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 402 if err != nil { 403 t.Fatal(err) 404 } 405 c.staticContracts.Return(oldContract) 406 if contract.EndHeight != c.blockHeight+100 { 407 t.Fatal(contract.EndHeight) 408 } 409 410 // revise the contract 411 editor, err = c.Editor(contract.HostPublicKey, nil) 412 if err != nil { 413 t.Fatal(err) 414 } 415 data = fastrand.Bytes(int(modules.SectorSize)) 416 // insert the sector 417 _, err = editor.Upload(data) 418 if err != nil { 419 t.Fatal(err) 420 } 421 err = editor.Close() 422 if err != nil { 423 t.Fatal(err) 424 } 425 } 426 427 // TestIntegrationDownloaderCaching tests that downloaders are properly cached 428 // by the contractor. When two downloaders are requested for the same 429 // contract, only one underlying downloader should be created. 430 func TestIntegrationDownloaderCaching(t *testing.T) { 431 if testing.Short() { 432 t.SkipNow() 433 } 434 t.Parallel() 435 // create testing trio 436 h, c, _, err := newTestingTrio(t.Name()) 437 if err != nil { 438 t.Fatal(err) 439 } 440 defer h.Close() 441 defer c.Close() 442 443 // get the host's entry from the db 444 hostEntry, ok := c.hdb.Host(h.PublicKey()) 445 if !ok { 446 t.Fatal("no entry for host in db") 447 } 448 449 // form a contract with the host 450 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 451 if err != nil { 452 t.Fatal(err) 453 } 454 455 // create a downloader 456 d1, err := c.Downloader(contract.HostPublicKey, nil) 457 if err != nil { 458 t.Fatal(err) 459 } 460 461 // create another downloader 462 d2, err := c.Downloader(contract.HostPublicKey, nil) 463 if err != nil { 464 t.Fatal(err) 465 } 466 467 // downloaders should match 468 if d1 != d2 { 469 t.Fatal("downloader was not cached") 470 } 471 472 // close one of the downloaders; it should not fully close, since d1 is 473 // still using it 474 d2.Close() 475 476 c.mu.RLock() 477 _, ok = c.downloaders[contract.ID] 478 c.mu.RUnlock() 479 if !ok { 480 t.Fatal("expected downloader to still be present") 481 } 482 483 // create another downloader 484 d3, err := c.Downloader(contract.HostPublicKey, nil) 485 if err != nil { 486 t.Fatal(err) 487 } 488 489 // downloaders should match 490 if d3 != d1 { 491 t.Fatal("closing one client should not fully close the downloader") 492 } 493 494 // close both downloaders 495 d1.Close() 496 d2.Close() 497 498 c.mu.RLock() 499 _, ok = c.downloaders[contract.ID] 500 c.mu.RUnlock() 501 if ok { 502 t.Fatal("did not expect downloader to still be present") 503 } 504 505 // create another downloader 506 d4, err := c.Downloader(contract.HostPublicKey, nil) 507 if err != nil { 508 t.Fatal(err) 509 } 510 511 // downloaders should match 512 if d4 == d1 { 513 t.Fatal("downloader should not have been cached after all clients were closed") 514 } 515 d4.Close() 516 } 517 518 // TestIntegrationEditorCaching tests that editors are properly cached 519 // by the contractor. When two editors are requested for the same 520 // contract, only one underlying editor should be created. 521 func TestIntegrationEditorCaching(t *testing.T) { 522 if testing.Short() { 523 t.SkipNow() 524 } 525 t.Parallel() 526 // create testing trio 527 h, c, _, err := newTestingTrio(t.Name()) 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer h.Close() 532 defer c.Close() 533 534 // get the host's entry from the db 535 hostEntry, ok := c.hdb.Host(h.PublicKey()) 536 if !ok { 537 t.Fatal("no entry for host in db") 538 } 539 540 // form a contract with the host 541 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100) 542 if err != nil { 543 t.Fatal(err) 544 } 545 546 // create an editor 547 d1, err := c.Editor(contract.HostPublicKey, nil) 548 if err != nil { 549 t.Fatal(err) 550 } 551 552 // create another editor 553 d2, err := c.Editor(contract.HostPublicKey, nil) 554 if err != nil { 555 t.Fatal(err) 556 } 557 558 // editors should match 559 if d1 != d2 { 560 t.Fatal("editor was not cached") 561 } 562 563 // close one of the editors; it should not fully close, since d1 is 564 // still using it 565 d2.Close() 566 567 c.mu.RLock() 568 _, ok = c.editors[contract.ID] 569 c.mu.RUnlock() 570 if !ok { 571 t.Fatal("expected editor to still be present") 572 } 573 574 // create another editor 575 d3, err := c.Editor(contract.HostPublicKey, nil) 576 if err != nil { 577 t.Fatal(err) 578 } 579 580 // editors should match 581 if d3 != d1 { 582 t.Fatal("closing one client should not fully close the editor") 583 } 584 585 // close both editors 586 d1.Close() 587 d2.Close() 588 589 c.mu.RLock() 590 _, ok = c.editors[contract.ID] 591 c.mu.RUnlock() 592 if ok { 593 t.Fatal("did not expect editor to still be present") 594 } 595 596 // create another editor 597 d4, err := c.Editor(contract.HostPublicKey, nil) 598 if err != nil { 599 t.Fatal(err) 600 } 601 602 // editors should match 603 if d4 == d1 { 604 t.Fatal("editor should not have been cached after all clients were closed") 605 } 606 d4.Close() 607 } 608 609 // TestContractPresenceLeak tests that a renter can not tell from the response 610 // of the host to RPCs if the host has the contract if the renter doesn't 611 // own this contract. See https://github.com/NebulousLabs/Sia/issues/2327. 612 func TestContractPresenceLeak(t *testing.T) { 613 if testing.Short() { 614 t.SkipNow() 615 } 616 t.Parallel() 617 // create testing trio 618 h, c, _, err := newTestingTrio(t.Name()) 619 if err != nil { 620 t.Fatal(err) 621 } 622 defer h.Close() 623 defer c.Close() 624 625 // get the host's entry from the db 626 hostEntry, ok := c.hdb.Host(h.PublicKey()) 627 if !ok { 628 t.Fatal("no entry for host in db") 629 } 630 631 // form a contract with the host 632 contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(10), c.blockHeight+100) 633 if err != nil { 634 t.Fatal(err) 635 } 636 637 // Connect with bad challenge response. Try correct 638 // and incorrect contract IDs. Compare errors. 639 wrongID := contract.ID 640 wrongID[0] ^= 0x01 641 fcids := []types.FileContractID{contract.ID, wrongID} 642 var errors []error 643 644 for _, fcid := range fcids { 645 var challenge crypto.Hash 646 var signature crypto.Signature 647 conn, err := net.Dial("tcp", string(hostEntry.NetAddress)) 648 if err != nil { 649 t.Fatalf("Couldn't dial tpc connection with host @ %v: %v.", string(hostEntry.NetAddress), err) 650 } 651 if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil { 652 t.Fatalf("Couldn't initiate RPC: %v.", err) 653 } 654 if err := encoding.WriteObject(conn, fcid); err != nil { 655 t.Fatalf("Couldn't send fcid: %v.", err) 656 } 657 if err := encoding.ReadObject(conn, &challenge, 32); err != nil { 658 t.Fatalf("Couldn't read challenge: %v.", err) 659 } 660 if err := encoding.WriteObject(conn, signature); err != nil { 661 t.Fatalf("Couldn't send signature: %v.", err) 662 } 663 err = modules.ReadNegotiationAcceptance(conn) 664 if err == nil { 665 t.Fatal("Expected an error, got success.") 666 } 667 errors = append(errors, err) 668 } 669 if errors[0].Error() != errors[1].Error() { 670 t.Fatalf("Expected to get equal errors, got %q and %q.", errors[0], errors[1]) 671 } 672 }