gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/host_test.go (about) 1 package host 2 3 import ( 4 // "errors" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "gitlab.com/SiaPrime/SiaPrime/build" 10 "gitlab.com/SiaPrime/SiaPrime/crypto" 11 "gitlab.com/SiaPrime/SiaPrime/modules" 12 "gitlab.com/SiaPrime/SiaPrime/modules/consensus" 13 "gitlab.com/SiaPrime/SiaPrime/modules/gateway" 14 "gitlab.com/SiaPrime/SiaPrime/modules/miner" 15 // "gitlab.com/SiaPrime/SiaPrime/modules/renter" 16 "gitlab.com/SiaPrime/SiaPrime/modules/transactionpool" 17 "gitlab.com/SiaPrime/SiaPrime/modules/wallet" 18 siasync "gitlab.com/SiaPrime/SiaPrime/sync" 19 "gitlab.com/SiaPrime/SiaPrime/types" 20 ) 21 22 // A hostTester is the helper object for host testing, including helper modules 23 // and methods for controlling synchronization. 24 type hostTester struct { 25 cs modules.ConsensusSet 26 gateway modules.Gateway 27 miner modules.TestMiner 28 // renter modules.Renter 29 renting bool 30 tpool modules.TransactionPool 31 wallet modules.Wallet 32 walletKey crypto.CipherKey 33 34 host *Host 35 36 persistDir string 37 } 38 39 /* 40 // initRenting prepares the host tester for uploads and downloads by announcing 41 // the host to the network and performing other preparational tasks. 42 // initRenting takes a while because the renter needs to process the host 43 // announcement, requiring asynchronous network communication between the 44 // renter and host. 45 func (ht *hostTester) initRenting() error { 46 if ht.renting { 47 return nil 48 } 49 50 // Because the renting test takes a long time, it will fail if 51 // testing.Short. 52 if testing.Short() { 53 return errors.New("cannot call initRenting in short tests") 54 } 55 56 // Announce the host. 57 err := ht.host.Announce() 58 if err != nil { 59 return err 60 } 61 62 // Mine a block to get the announcement into the blockchain. 63 _, err = ht.miner.AddBlock() 64 if err != nil { 65 return err 66 } 67 68 // Wait for the renter to see the host announcement. 69 for i := 0; i < 50; i++ { 70 time.Sleep(time.Millisecond * 100) 71 if len(ht.renter.ActiveHosts()) != 0 { 72 break 73 } 74 } 75 if len(ht.renter.ActiveHosts()) == 0 { 76 return errors.New("could not start renting in the host tester") 77 } 78 ht.renting = true 79 return nil 80 } 81 */ 82 83 // initWallet creates a wallet key, initializes the host wallet, unlocks it, 84 // and then stores the key in the host tester. 85 func (ht *hostTester) initWallet() error { 86 // Create the keys for the wallet and unlock it. 87 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 88 ht.walletKey = key 89 _, err := ht.wallet.Encrypt(key) 90 if err != nil { 91 return err 92 } 93 err = ht.wallet.Unlock(key) 94 if err != nil { 95 return err 96 } 97 return nil 98 } 99 100 // blankHostTester creates a host tester where the modules are created but no 101 // extra initialization has been done, for example no blocks have been mined 102 // and the wallet keys have not been created. 103 func blankHostTester(name string) (*hostTester, error) { 104 return blankMockHostTester(modules.ProdDependencies, name) 105 } 106 107 // blankMockHostTester creates a host tester where the modules are created but no 108 // extra initialization has been done, for example no blocks have been mined 109 // and the wallet keys have not been created. 110 func blankMockHostTester(d modules.Dependencies, name string) (*hostTester, error) { 111 testdir := build.TempDir(modules.HostDir, name) 112 113 // Create the modules. 114 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 115 if err != nil { 116 return nil, err 117 } 118 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 119 if err != nil { 120 return nil, err 121 } 122 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 123 if err != nil { 124 return nil, err 125 } 126 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 127 if err != nil { 128 return nil, err 129 } 130 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 131 if err != nil { 132 return nil, err 133 } 134 h, err := newHost(d, cs, g, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 135 if err != nil { 136 return nil, err 137 } 138 /* 139 r, err := renter.New(cs, w, tp, filepath.Join(testdir, modules.RenterDir)) 140 if err != nil { 141 return nil, err 142 } 143 */ 144 145 // Assemble all objects into a hostTester 146 ht := &hostTester{ 147 cs: cs, 148 gateway: g, 149 miner: m, 150 // renter: r, 151 tpool: tp, 152 wallet: w, 153 154 host: h, 155 156 persistDir: testdir, 157 } 158 159 return ht, nil 160 } 161 162 // newHostTester creates a host tester with an initialized wallet and money in 163 // that wallet. 164 func newHostTester(name string) (*hostTester, error) { 165 return newMockHostTester(modules.ProdDependencies, name) 166 } 167 168 // newMockHostTester creates a host tester with an initialized wallet and money 169 // in that wallet, using the dependencies provided. 170 func newMockHostTester(d modules.Dependencies, name string) (*hostTester, error) { 171 // Create a blank host tester. 172 ht, err := blankMockHostTester(d, name) 173 if err != nil { 174 return nil, err 175 } 176 177 // Initialize the wallet and mine blocks until the wallet has money. 178 err = ht.initWallet() 179 if err != nil { 180 return nil, err 181 } 182 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 183 _, err = ht.miner.AddBlock() 184 if err != nil { 185 return nil, err 186 } 187 } 188 189 // Create two storage folder for the host, one the minimum size and one 190 // twice the minimum size. 191 storageFolderOne := filepath.Join(ht.persistDir, "hostTesterStorageFolderOne") 192 err = os.Mkdir(storageFolderOne, 0700) 193 if err != nil { 194 return nil, err 195 } 196 err = ht.host.AddStorageFolder(storageFolderOne, modules.SectorSize*64) 197 if err != nil { 198 return nil, err 199 } 200 storageFolderTwo := filepath.Join(ht.persistDir, "hostTesterStorageFolderTwo") 201 err = os.Mkdir(storageFolderTwo, 0700) 202 if err != nil { 203 return nil, err 204 } 205 err = ht.host.AddStorageFolder(storageFolderTwo, modules.SectorSize*64*2) 206 if err != nil { 207 return nil, err 208 } 209 return ht, nil 210 } 211 212 // Close safely closes the hostTester. It panics if err != nil because there 213 // isn't a good way to errcheck when deferring a close. 214 func (ht *hostTester) Close() error { 215 errs := []error{ 216 ht.host.Close(), 217 ht.miner.Close(), 218 ht.tpool.Close(), 219 ht.cs.Close(), 220 ht.gateway.Close(), 221 } 222 if err := build.JoinErrors(errs, "; "); err != nil { 223 panic(err) 224 } 225 return nil 226 } 227 228 // TestHostInitialization checks that the host initializes to sensible default 229 // values. 230 func TestHostInitialization(t *testing.T) { 231 if testing.Short() { 232 t.SkipNow() 233 } 234 t.Parallel() 235 // Create a blank host tester and check that the height is zero. 236 bht, err := blankHostTester("TestHostInitialization") 237 if err != nil { 238 t.Fatal(err) 239 } 240 defer bht.Close() 241 if bht.host.blockHeight != 0 { 242 t.Error("host initialized to the wrong block height") 243 } 244 245 // Initialize the wallet so that a block can be mined, then mine a block 246 // and check that it sets the host height to 1. 247 err = bht.initWallet() 248 if err != nil { 249 t.Fatal(err) 250 } 251 _, err = bht.miner.AddBlock() 252 if err != nil { 253 t.Fatal(err) 254 } 255 if bht.host.blockHeight != 1 { 256 t.Fatal("block height did not increase correctly after first block mined:", bht.host.blockHeight, 1) 257 } 258 } 259 260 // TestHostMultiClose checks that the host returns an error if Close is called 261 // multiple times on the host. 262 func TestHostMultiClose(t *testing.T) { 263 if testing.Short() { 264 t.SkipNow() 265 } 266 t.Parallel() 267 ht, err := newHostTester("TestHostMultiClose") 268 if err != nil { 269 t.Fatal(err) 270 } 271 defer ht.Close() 272 273 err = ht.host.Close() 274 if err != nil { 275 t.Fatal(err) 276 } 277 err = ht.host.Close() 278 if err != siasync.ErrStopped { 279 t.Fatal(err) 280 } 281 err = ht.host.Close() 282 if err != siasync.ErrStopped { 283 t.Fatal(err) 284 } 285 // Set ht.host to something non-nil - nil was returned because startup was 286 // incomplete. If ht.host is nil at the end of the function, the ht.Close() 287 // operation will fail. 288 ht.host, err = newHost(modules.ProdDependencies, ht.cs, ht.gateway, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir)) 289 if err != nil { 290 t.Fatal(err) 291 } 292 } 293 294 // TestNilValues tries initializing the host with nil values. 295 func TestNilValues(t *testing.T) { 296 if testing.Short() { 297 t.SkipNow() 298 } 299 t.Parallel() 300 ht, err := blankHostTester("TestStartupRescan") 301 if err != nil { 302 t.Fatal(err) 303 } 304 defer ht.Close() 305 306 hostDir := filepath.Join(ht.persistDir, modules.HostDir) 307 _, err = New(nil, ht.gateway, ht.tpool, ht.wallet, "localhost:0", hostDir) 308 if err != errNilCS { 309 t.Fatal("could not trigger errNilCS") 310 } 311 _, err = New(ht.cs, nil, ht.tpool, ht.wallet, "localhost:0", hostDir) 312 if err != errNilGateway { 313 t.Fatal("Could not trigger errNilGateay") 314 } 315 _, err = New(ht.cs, ht.gateway, nil, ht.wallet, "localhost:0", hostDir) 316 if err != errNilTpool { 317 t.Fatal("could not trigger errNilTpool") 318 } 319 _, err = New(ht.cs, ht.gateway, ht.tpool, nil, "localhost:0", hostDir) 320 if err != errNilWallet { 321 t.Fatal("Could not trigger errNilWallet") 322 } 323 } 324 325 // TestSetAndGetInternalSettings checks that the functions for interacting with 326 // the host's internal settings object are working as expected. 327 func TestSetAndGetInternalSettings(t *testing.T) { 328 if testing.Short() { 329 t.SkipNow() 330 } 331 t.Parallel() 332 333 ht, err := newHostTester("TestSetAndGetInternalSettings") 334 if err != nil { 335 t.Fatal(err) 336 } 337 defer ht.Close() 338 339 // Check the default settings get returned at first call. 340 settings := ht.host.InternalSettings() 341 if settings.AcceptingContracts != false { 342 t.Error("settings retrieval did not return default value") 343 } 344 if settings.MaxDuration != defaultMaxDuration { 345 t.Error("settings retrieval did not return default value") 346 } 347 if settings.MaxDownloadBatchSize != uint64(defaultMaxDownloadBatchSize) { 348 t.Error("settings retrieval did not return default value") 349 } 350 if settings.MaxReviseBatchSize != uint64(defaultMaxReviseBatchSize) { 351 t.Error("settings retrieval did not return default value") 352 } 353 if settings.NetAddress != "" { 354 t.Error("settings retrieval did not return default value") 355 } 356 if settings.WindowSize != defaultWindowSize { 357 t.Error("settings retrieval did not return default value") 358 } 359 if !settings.Collateral.Equals(defaultCollateral) { 360 t.Error("settings retrieval did not return default value") 361 } 362 if !settings.CollateralBudget.Equals(defaultCollateralBudget) { 363 t.Error("settings retrieval did not return default value") 364 } 365 if !settings.MaxCollateral.Equals(defaultMaxCollateral) { 366 t.Error("settings retrieval did not return default value") 367 } 368 if !settings.MinContractPrice.Equals(defaultContractPrice) { 369 t.Error("settings retrieval did not return default value") 370 } 371 if !settings.MinDownloadBandwidthPrice.Equals(defaultDownloadBandwidthPrice) { 372 t.Error("settings retrieval did not return default value") 373 } 374 if !settings.MinStoragePrice.Equals(defaultStoragePrice) { 375 t.Error("settings retrieval did not return default value") 376 } 377 if !settings.MinUploadBandwidthPrice.Equals(defaultUploadBandwidthPrice) { 378 t.Error("settings retrieval did not return default value") 379 } 380 381 // Check that calling SetInternalSettings with valid settings updates the settings. 382 settings.AcceptingContracts = true 383 settings.NetAddress = "foo.com:123" 384 err = ht.host.SetInternalSettings(settings) 385 if err != nil { 386 t.Fatal(err) 387 } 388 settings = ht.host.InternalSettings() 389 if settings.AcceptingContracts != true { 390 t.Fatal("SetInternalSettings failed to update settings") 391 } 392 if settings.NetAddress != "foo.com:123" { 393 t.Fatal("SetInternalSettings failed to update settings") 394 } 395 396 // Check that calling SetInternalSettings with invalid settings does not update the settings. 397 settings.NetAddress = "invalid" 398 err = ht.host.SetInternalSettings(settings) 399 if err == nil { 400 t.Fatal("expected SetInternalSettings to error with invalid settings") 401 } 402 settings = ht.host.InternalSettings() 403 if settings.NetAddress != "foo.com:123" { 404 t.Fatal("SetInternalSettings should not modify the settings if the new settings are invalid") 405 } 406 407 // Reload the host and verify that the altered settings persisted. 408 err = ht.host.Close() 409 if err != nil { 410 t.Fatal(err) 411 } 412 rebootHost, err := New(ht.cs, ht.gateway, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir)) 413 if err != nil { 414 t.Fatal(err) 415 } 416 rebootSettings := rebootHost.InternalSettings() 417 if rebootSettings.AcceptingContracts != settings.AcceptingContracts { 418 t.Error("settings retrieval did not return updated value") 419 } 420 if rebootSettings.NetAddress != settings.NetAddress { 421 t.Error("settings retrieval did not return updated value") 422 } 423 424 // Set ht.host to 'rebootHost' so that the 'ht.Close()' method will close 425 // everything cleanly. 426 ht.host = rebootHost 427 } 428 429 /* 430 // TestSetAndGetSettings checks that the functions for interacting with the 431 // hosts settings object are working as expected. 432 func TestSetAndGetSettings(t *testing.T) { 433 if testing.Short() { 434 t.SkipNow() 435 } 436 ht, err := newHostTester("TestSetAndGetSettings") 437 if err != nil { 438 t.Fatal(err) 439 } 440 defer ht.Close() 441 442 // Check the default settings get returned at first call. 443 settings := ht.host.Settings() 444 if settings.MaxDuration != defaultMaxDuration { 445 t.Error("settings retrieval did not return default value") 446 } 447 if settings.WindowSize != defaultWindowSize { 448 t.Error("settings retrieval did not return default value") 449 } 450 if settings.Price.Cmp(defaultPrice) != 0 { 451 t.Error("settings retrieval did not return default value") 452 } 453 if settings.Collateral.Cmp(defaultCollateral) != 0 { 454 t.Error("settings retrieval did not return default value") 455 } 456 457 // Submit updated settings and check that the changes stuck. 458 settings.TotalStorage += 15 459 settings.MaxDuration += 16 460 settings.WindowSize += 17 461 settings.Price = settings.Price.Add(types.NewCurrency64(18)) 462 settings.Collateral = settings.Collateral.Add(types.NewCurrency64(19)) 463 err = ht.host.SetSettings(settings) 464 if err != nil { 465 t.Fatal(err) 466 } 467 newSettings := ht.host.Settings() 468 if settings.MaxDuration != newSettings.MaxDuration { 469 t.Error("settings retrieval did not return updated value") 470 } 471 if settings.WindowSize != newSettings.WindowSize { 472 t.Error("settings retrieval did not return updated value") 473 } 474 if settings.Price.Cmp(newSettings.Price) != 0 { 475 t.Error("settings retrieval did not return updated value") 476 } 477 if settings.Collateral.Cmp(newSettings.Collateral) != 0 { 478 t.Error("settings retrieval did not return updated value") 479 } 480 481 // Reload the host and verify that the altered settings persisted. 482 err = ht.host.Close() 483 if err != nil { 484 t.Fatal(err) 485 } 486 rebootHost, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir)) 487 if err != nil { 488 t.Fatal(err) 489 } 490 rebootSettings := rebootHost.Settings() 491 if settings.TotalStorage != rebootSettings.TotalStorage { 492 t.Error("settings retrieval did not return updated value") 493 } 494 if settings.MaxDuration != rebootSettings.MaxDuration { 495 t.Error("settings retrieval did not return updated value") 496 } 497 if settings.WindowSize != rebootSettings.WindowSize { 498 t.Error("settings retrieval did not return updated value") 499 } 500 if settings.Price.Cmp(rebootSettings.Price) != 0 { 501 t.Error("settings retrieval did not return updated value") 502 } 503 if settings.Collateral.Cmp(rebootSettings.Collateral) != 0 { 504 t.Error("settings retrieval did not return updated value") 505 } 506 } 507 508 // TestPersistentSettings checks that settings persist between instances of the 509 // host. 510 func TestPersistentSettings(t *testing.T) { 511 if testing.Short() { 512 t.SkipNow() 513 } 514 ht, err := newHostTester("TestSetPersistentSettings") 515 if err != nil { 516 t.Fatal(err) 517 } 518 defer ht.Close() 519 520 // Submit updated settings. 521 settings := ht.host.Settings() 522 settings.TotalStorage += 25 523 settings.MaxDuration += 36 524 settings.WindowSize += 47 525 settings.Price = settings.Price.Add(types.NewCurrency64(38)) 526 settings.Collateral = settings.Collateral.Add(types.NewCurrency64(99)) 527 err = ht.host.SetSettings(settings) 528 if err != nil { 529 t.Fatal(err) 530 } 531 532 // Reboot the host and verify that the new settings stuck. 533 err = ht.host.Close() // host saves upon closing 534 if err != nil { 535 t.Fatal(err) 536 } 537 h, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir)) 538 if err != nil { 539 t.Fatal(err) 540 } 541 newSettings := h.Settings() 542 if settings.TotalStorage != newSettings.TotalStorage { 543 t.Error("settings retrieval did not return updated value:", settings.TotalStorage, "vs", newSettings.TotalStorage) 544 } 545 if settings.MaxDuration != newSettings.MaxDuration { 546 t.Error("settings retrieval did not return updated value") 547 } 548 if settings.WindowSize != newSettings.WindowSize { 549 t.Error("settings retrieval did not return updated value") 550 } 551 if settings.Price.Cmp(newSettings.Price) != 0 { 552 t.Error("settings retrieval did not return updated value") 553 } 554 if settings.Collateral.Cmp(newSettings.Collateral) != 0 { 555 t.Error("settings retrieval did not return updated value") 556 } 557 } 558 */