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