github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/hostdb/hostdb_test.go (about) 1 package hostdb 2 3 import ( 4 "io/ioutil" 5 "math" 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "github.com/Synthesix/Sia/build" 12 "github.com/Synthesix/Sia/crypto" 13 "github.com/Synthesix/Sia/modules" 14 "github.com/Synthesix/Sia/modules/consensus" 15 "github.com/Synthesix/Sia/modules/gateway" 16 "github.com/Synthesix/Sia/modules/miner" 17 "github.com/Synthesix/Sia/modules/renter/hostdb/hosttree" 18 "github.com/Synthesix/Sia/modules/transactionpool" 19 "github.com/Synthesix/Sia/modules/wallet" 20 "github.com/Synthesix/Sia/persist" 21 "github.com/Synthesix/Sia/types" 22 ) 23 24 // hdbTester contains a hostdb and all dependencies. 25 type hdbTester struct { 26 cs modules.ConsensusSet 27 gateway modules.Gateway 28 miner modules.TestMiner 29 tpool modules.TransactionPool 30 wallet modules.Wallet 31 walletKey crypto.TwofishKey 32 33 hdb *HostDB 34 35 persistDir string 36 } 37 38 // bareHostDB returns a HostDB with its fields initialized, but without any 39 // dependencies or scanning threads. It is only intended for use in unit tests. 40 func bareHostDB() *HostDB { 41 hdb := &HostDB{ 42 log: persist.NewLogger(ioutil.Discard), 43 } 44 hdb.hostTree = hosttree.New(hdb.calculateHostWeight) 45 return hdb 46 } 47 48 // makeHostDBEntry makes a new host entry with a random public key 49 func makeHostDBEntry() modules.HostDBEntry { 50 dbe := modules.HostDBEntry{} 51 _, pk := crypto.GenerateKeyPair() 52 53 dbe.AcceptingContracts = true 54 dbe.PublicKey = types.Ed25519PublicKey(pk) 55 dbe.ScanHistory = modules.HostDBScans{{ 56 Timestamp: time.Now(), 57 Success: true, 58 }} 59 return dbe 60 } 61 62 // newHDBTester returns a tester object wrapping a HostDB and some extra 63 // information for testing. 64 func newHDBTester(name string) (*hdbTester, error) { 65 return newHDBTesterDeps(name, modules.ProdDependencies) 66 } 67 68 // newHDBTesterDeps returns a tester object wrapping a HostDB and some extra 69 // information for testing, using the provided dependencies for the hostdb. 70 func newHDBTesterDeps(name string, deps modules.Dependencies) (*hdbTester, error) { 71 if testing.Short() { 72 panic("should not be calling newHDBTester during short tests") 73 } 74 testDir := build.TempDir("HostDB", name) 75 76 g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir)) 77 if err != nil { 78 return nil, err 79 } 80 cs, err := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir)) 81 if err != nil { 82 return nil, err 83 } 84 tp, err := transactionpool.New(cs, g, filepath.Join(testDir, modules.TransactionPoolDir)) 85 if err != nil { 86 return nil, err 87 } 88 w, err := wallet.New(cs, tp, filepath.Join(testDir, modules.WalletDir)) 89 if err != nil { 90 return nil, err 91 } 92 m, err := miner.New(cs, tp, w, filepath.Join(testDir, modules.MinerDir)) 93 if err != nil { 94 return nil, err 95 } 96 hdb, err := NewCustomHostDB(g, cs, filepath.Join(testDir, modules.RenterDir), deps) 97 if err != nil { 98 return nil, err 99 } 100 101 hdbt := &hdbTester{ 102 cs: cs, 103 gateway: g, 104 miner: m, 105 tpool: tp, 106 wallet: w, 107 108 hdb: hdb, 109 110 persistDir: testDir, 111 } 112 113 err = hdbt.initWallet() 114 if err != nil { 115 return nil, err 116 } 117 118 return hdbt, nil 119 } 120 121 // initWallet creates a wallet key, then initializes and unlocks the wallet. 122 func (hdbt *hdbTester) initWallet() error { 123 hdbt.walletKey = crypto.GenerateTwofishKey() 124 _, err := hdbt.wallet.Encrypt(hdbt.walletKey) 125 if err != nil { 126 return err 127 } 128 err = hdbt.wallet.Unlock(hdbt.walletKey) 129 if err != nil { 130 return err 131 } 132 return nil 133 } 134 135 // TestAverageContractPrice tests the AverageContractPrice method, which also depends on the 136 // randomHosts method. 137 func TestAverageContractPrice(t *testing.T) { 138 hdb := bareHostDB() 139 140 // empty 141 if avg := hdb.AverageContractPrice(); !avg.IsZero() { 142 t.Error("average of empty hostdb should be zero:", avg) 143 } 144 145 // with one host 146 h1 := makeHostDBEntry() 147 h1.ContractPrice = types.NewCurrency64(100) 148 hdb.hostTree.Insert(h1) 149 if avg := hdb.AverageContractPrice(); avg.Cmp(h1.ContractPrice) != 0 { 150 t.Error("average of one host should be that host's price:", avg) 151 } 152 153 // with two hosts 154 h2 := makeHostDBEntry() 155 h2.ContractPrice = types.NewCurrency64(300) 156 hdb.hostTree.Insert(h2) 157 if avg := hdb.AverageContractPrice(); avg.Cmp64(200) != 0 { 158 t.Error("average of two hosts should be their sum/2:", avg) 159 } 160 } 161 162 // TestNew tests the New function. 163 func TestNew(t *testing.T) { 164 if testing.Short() { 165 t.SkipNow() 166 } 167 testDir := build.TempDir("HostDB", t.Name()) 168 g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir)) 169 if err != nil { 170 t.Fatal(err) 171 } 172 cs, err := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir)) 173 if err != nil { 174 t.Fatal(err) 175 } 176 177 // Vanilla HDB, nothing should go wrong. 178 hdbName := filepath.Join(testDir, modules.RenterDir) 179 _, err = New(g, cs, hdbName+"1") 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 // Nil gateway. 185 _, err = New(nil, cs, hdbName+"2") 186 if err != errNilGateway { 187 t.Fatalf("expected %v, got %v", errNilGateway, err) 188 } 189 // Nil consensus set. 190 _, err = New(g, nil, hdbName+"3") 191 if err != errNilCS { 192 t.Fatalf("expected %v, got %v", errNilCS, err) 193 } 194 // Bad persistDir. 195 _, err = New(g, cs, "") 196 if !os.IsNotExist(err) { 197 t.Fatalf("expected invalid directory, got %v", err) 198 } 199 } 200 201 // quitAfterLoadDeps will quit startup in newHostDB 202 type disableScanLoopDeps struct { 203 modules.ProductionDependencies 204 } 205 206 // Send a disrupt signal to the quitAfterLoad codebreak. 207 func (*disableScanLoopDeps) Disrupt(s string) bool { 208 if s == "disableScanLoop" { 209 return true 210 } 211 return false 212 } 213 214 // TestRandomHosts tests the hostdb's exported RandomHosts method. 215 func TestRandomHosts(t *testing.T) { 216 if testing.Short() { 217 t.SkipNow() 218 } 219 hdbt, err := newHDBTesterDeps(t.Name(), &disableScanLoopDeps{}) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 entries := make(map[string]modules.HostDBEntry) 225 nEntries := int(1e3) 226 for i := 0; i < nEntries; i++ { 227 entry := makeHostDBEntry() 228 entries[string(entry.PublicKey.Key)] = entry 229 err := hdbt.hdb.hostTree.Insert(entry) 230 if err != nil { 231 t.Error(err) 232 } 233 } 234 235 // Check that all hosts can be queried. 236 for i := 0; i < 25; i++ { 237 hosts := hdbt.hdb.RandomHosts(nEntries, nil) 238 if len(hosts) != nEntries { 239 t.Errorf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries) 240 } 241 dupCheck := make(map[string]modules.HostDBEntry) 242 for _, host := range hosts { 243 _, exists := entries[string(host.PublicKey.Key)] 244 if !exists { 245 t.Error("hostdb returning host that doesn't exist.") 246 } 247 _, exists = dupCheck[string(host.PublicKey.Key)] 248 if exists { 249 t.Error("RandomHosts returning duplicates") 250 } 251 dupCheck[string(host.PublicKey.Key)] = host 252 } 253 } 254 255 // Base case, fill out a map exposing hosts from a single RH query. 256 dupCheck1 := make(map[string]modules.HostDBEntry) 257 hosts := hdbt.hdb.RandomHosts(nEntries/2, nil) 258 if len(hosts) != nEntries/2 { 259 t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2) 260 } 261 for _, host := range hosts { 262 _, exists := entries[string(host.PublicKey.Key)] 263 if !exists { 264 t.Error("hostdb returning host that doesn't exist.") 265 } 266 _, exists = dupCheck1[string(host.PublicKey.Key)] 267 if exists { 268 t.Error("RandomHosts returning duplicates") 269 } 270 dupCheck1[string(host.PublicKey.Key)] = host 271 } 272 273 // Iterative case. Check that every time you query for random hosts, you 274 // get different responses. 275 for i := 0; i < 10; i++ { 276 dupCheck2 := make(map[string]modules.HostDBEntry) 277 var overlap, disjoint bool 278 hosts = hdbt.hdb.RandomHosts(nEntries/2, nil) 279 if len(hosts) != nEntries/2 { 280 t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2) 281 } 282 for _, host := range hosts { 283 _, exists := entries[string(host.PublicKey.Key)] 284 if !exists { 285 t.Error("hostdb returning host that doesn't exist.") 286 } 287 _, exists = dupCheck2[string(host.PublicKey.Key)] 288 if exists { 289 t.Error("RandomHosts returning duplicates") 290 } 291 _, exists = dupCheck1[string(host.PublicKey.Key)] 292 if exists { 293 overlap = true 294 } else { 295 disjoint = true 296 } 297 dupCheck2[string(host.PublicKey.Key)] = host 298 299 } 300 if !overlap || !disjoint { 301 t.Error("Random hosts does not seem to be random") 302 } 303 dupCheck1 = dupCheck2 304 } 305 306 // Try exclude list by excluding every host except for the last one, and 307 // doing a random select. 308 for i := 0; i < 25; i++ { 309 hosts := hdbt.hdb.RandomHosts(nEntries, nil) 310 var exclude []types.SiaPublicKey 311 for j := 1; j < len(hosts); j++ { 312 exclude = append(exclude, hosts[j].PublicKey) 313 } 314 rand := hdbt.hdb.RandomHosts(1, exclude) 315 if len(rand) != 1 { 316 t.Fatal("wrong number of hosts returned") 317 } 318 if string(rand[0].PublicKey.Key) != string(hosts[0].PublicKey.Key) { 319 t.Error("exclude list seems to be excluding the wrong hosts.") 320 } 321 322 // Try again but request more hosts than are available. 323 rand = hdbt.hdb.RandomHosts(5, exclude) 324 if len(rand) != 1 { 325 t.Fatal("wrong number of hosts returned") 326 } 327 if string(rand[0].PublicKey.Key) != string(hosts[0].PublicKey.Key) { 328 t.Error("exclude list seems to be excluding the wrong hosts.") 329 } 330 331 // Create an include map, and decrease the number of excluded hosts. 332 // Make sure all hosts returned by rand function are in the include 333 // map. 334 includeMap := make(map[string]struct{}) 335 for j := 0; j < 50; j++ { 336 includeMap[string(hosts[j].PublicKey.Key)] = struct{}{} 337 } 338 exclude = exclude[49:] 339 340 // Select only 20 hosts. 341 dupCheck := make(map[string]struct{}) 342 rand = hdbt.hdb.RandomHosts(20, exclude) 343 if len(rand) != 20 { 344 t.Error("random hosts is returning the wrong number of hosts") 345 } 346 for _, host := range rand { 347 _, exists := dupCheck[string(host.PublicKey.Key)] 348 if exists { 349 t.Error("RandomHosts is seleccting duplicates") 350 } 351 dupCheck[string(host.PublicKey.Key)] = struct{}{} 352 _, exists = includeMap[string(host.PublicKey.Key)] 353 if !exists { 354 t.Error("RandomHosts returning excluded hosts") 355 } 356 } 357 358 // Select exactly 50 hosts. 359 dupCheck = make(map[string]struct{}) 360 rand = hdbt.hdb.RandomHosts(50, exclude) 361 if len(rand) != 50 { 362 t.Error("random hosts is returning the wrong number of hosts") 363 } 364 for _, host := range rand { 365 _, exists := dupCheck[string(host.PublicKey.Key)] 366 if exists { 367 t.Error("RandomHosts is seleccting duplicates") 368 } 369 dupCheck[string(host.PublicKey.Key)] = struct{}{} 370 _, exists = includeMap[string(host.PublicKey.Key)] 371 if !exists { 372 t.Error("RandomHosts returning excluded hosts") 373 } 374 } 375 376 // Select 100 hosts. 377 dupCheck = make(map[string]struct{}) 378 rand = hdbt.hdb.RandomHosts(100, exclude) 379 if len(rand) != 50 { 380 t.Error("random hosts is returning the wrong number of hosts") 381 } 382 for _, host := range rand { 383 _, exists := dupCheck[string(host.PublicKey.Key)] 384 if exists { 385 t.Error("RandomHosts is seleccting duplicates") 386 } 387 dupCheck[string(host.PublicKey.Key)] = struct{}{} 388 _, exists = includeMap[string(host.PublicKey.Key)] 389 if !exists { 390 t.Error("RandomHosts returning excluded hosts") 391 } 392 } 393 } 394 } 395 396 // TestRemoveNonexistingHostFromHostTree checks that the host tree interface 397 // correctly responds to having a nonexisting host removed from the host tree. 398 func TestRemoveNonexistingHostFromHostTree(t *testing.T) { 399 if testing.Short() { 400 t.SkipNow() 401 } 402 hdbt, err := newHDBTester(t.Name()) 403 if err != nil { 404 t.Fatal(err) 405 } 406 407 // Remove a host that doesn't exist from the tree. 408 err = hdbt.hdb.hostTree.Remove(types.SiaPublicKey{}) 409 if err == nil { 410 t.Fatal("There should be an error, but not a panic:", err) 411 } 412 } 413 414 // TestUpdateHistoricInteractions is a simple check to ensure that incrementing 415 // the recent and historic host interactions works 416 func TestUpdateHistoricInteractions(t *testing.T) { 417 if testing.Short() { 418 t.SkipNow() 419 } 420 421 // create a HostDB tester without scanloop to be able to manually increment 422 // the interactions without interference. 423 hdbt, err := newHDBTesterDeps(t.Name(), &disableScanLoopDeps{}) 424 if err != nil { 425 t.Fatal(err) 426 } 427 428 // create a HostDBEntry and add it to the tree 429 host := makeHostDBEntry() 430 err = hdbt.hdb.hostTree.Insert(host) 431 if err != nil { 432 t.Error(err) 433 } 434 435 // increment successful and failed interactions by 100 436 interactions := 100.0 437 for i := 0.0; i < interactions; i++ { 438 hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey) 439 hdbt.hdb.IncrementFailedInteractions(host.PublicKey) 440 } 441 442 // get updated host from hostdb 443 host, ok := hdbt.hdb.Host(host.PublicKey) 444 if !ok { 445 t.Fatal("Modified host not found in hostdb") 446 } 447 448 // check that recent interactions are exactly 100 and historic interactions are 0 449 if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions { 450 t.Errorf("Interactions should be %v but were %v and %v", interactions, 451 host.RecentFailedInteractions, host.RecentSuccessfulInteractions) 452 } 453 if host.HistoricFailedInteractions != 0 || host.HistoricSuccessfulInteractions != 0 { 454 t.Errorf("Historic Interactions should be %v but were %v and %v", 0, 455 host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions) 456 } 457 458 // add single block to consensus 459 _, err = hdbt.miner.AddBlock() 460 if err != nil { 461 t.Fatal(err) 462 } 463 464 // increment interactions again by 100 465 for i := 0.0; i < interactions; i++ { 466 hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey) 467 hdbt.hdb.IncrementFailedInteractions(host.PublicKey) 468 } 469 470 // get updated host from hostdb 471 host, ok = hdbt.hdb.Host(host.PublicKey) 472 if !ok { 473 t.Fatal("Modified host not found in hostdb") 474 } 475 476 // historic actions should have incremented slightly, due to the clamp the 477 // full interactions should not have made it into the historic group. 478 if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions { 479 t.Errorf("Interactions should be %v but were %v and %v", interactions, 480 host.RecentFailedInteractions, host.RecentSuccessfulInteractions) 481 } 482 if host.HistoricFailedInteractions == 0 || host.HistoricSuccessfulInteractions == 0 { 483 t.Error("historic actions should have updated") 484 } 485 486 // add 200 blocks to consensus, adding large numbers of historic actions 487 // each time, so that the clamp does not need to be in effect anymore. 488 for i := 0; i < 200; i++ { 489 for j := uint64(0); j < 10; j++ { 490 hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey) 491 hdbt.hdb.IncrementFailedInteractions(host.PublicKey) 492 } 493 _, err = hdbt.miner.AddBlock() 494 if err != nil { 495 t.Fatal(err) 496 } 497 } 498 499 // Add five interactions 500 for i := 0; i < 5; i++ { 501 hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey) 502 hdbt.hdb.IncrementFailedInteractions(host.PublicKey) 503 } 504 505 // get updated host from hostdb 506 host, ok = hdbt.hdb.Host(host.PublicKey) 507 if !ok { 508 t.Fatal("Modified host not found in hostdb") 509 } 510 511 // check that recent interactions are exactly 5. Save the historic actions 512 // to check that decay is being handled correctly, and that the recent 513 // interactions are moved over correctly. 514 if host.RecentFailedInteractions != 5 || host.RecentSuccessfulInteractions != 5 { 515 t.Errorf("Interactions should be %v but were %v and %v", interactions, 516 host.RecentFailedInteractions, host.RecentSuccessfulInteractions) 517 } 518 historicFailed := host.HistoricFailedInteractions 519 if host.HistoricFailedInteractions != host.HistoricSuccessfulInteractions { 520 t.Error("historic failed and successful should have the same values") 521 } 522 523 // Add a single block to apply one round of decay. 524 _, err = hdbt.miner.AddBlock() 525 if err != nil { 526 t.Fatal(err) 527 } 528 host, ok = hdbt.hdb.Host(host.PublicKey) 529 if !ok { 530 t.Fatal("Modified host not found in hostdb") 531 } 532 533 // Get the historic successful and failed interactions, and see that they 534 // are decaying properly. 535 expected := historicFailed*math.Pow(historicInteractionDecay, 1) + 5 536 if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected { 537 t.Errorf("Historic Interactions should be %v but were %v and %v", expected, 538 host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions) 539 } 540 541 // Add 10 more blocks and check the decay again, make sure it's being 542 // applied correctly. 543 for i := 0; i < 10; i++ { 544 _, err := hdbt.miner.AddBlock() 545 if err != nil { 546 t.Fatal(err) 547 } 548 } 549 host, ok = hdbt.hdb.Host(host.PublicKey) 550 if !ok { 551 t.Fatal("Modified host not found in hostdb") 552 } 553 expected = expected * math.Pow(historicInteractionDecay, 10) 554 if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected { 555 t.Errorf("Historic Interactions should be %v but were %v and %v", expected, 556 host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions) 557 } 558 }