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