gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/hostdb/scan_test.go (about) 1 package hostdb 2 3 import ( 4 "errors" 5 "testing" 6 "time" 7 8 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 9 "gitlab.com/SkynetLabs/skyd/skymodules" 10 "go.sia.tech/siad/types" 11 ) 12 13 // TestUpdateEntry checks that the various components of updateEntry are 14 // working correctly. 15 func TestUpdateEntry(t *testing.T) { 16 if testing.Short() { 17 t.SkipNow() 18 } 19 20 // Create a multi dependency that disables the scan loop in the HostDB and 21 // avoids a critical when we encounter a HostDB entry with an unsorted scan 22 // history. 23 dep1 := &dependencies.DependencyDisableScanLoopDeps{} 24 dep2 := &dependencies.DependencyDisableCriticalOnUnsortedHistory{} 25 hdbt, err := newHDBTesterDeps(t.Name(), dependencies.NewMultiDependency(dep1, dep2)) 26 if err != nil { 27 t.Fatal(err) 28 } 29 30 // Test 1: try calling updateEntry with a blank host. Result should be a 31 // host with len 2 scan history. 32 someErr := errors.New("testing err") 33 entry1 := skymodules.HostDBEntry{ 34 PublicKey: types.SiaPublicKey{ 35 Key: []byte{1}, 36 }, 37 } 38 entry2 := skymodules.HostDBEntry{ 39 PublicKey: types.SiaPublicKey{ 40 Key: []byte{2}, 41 }, 42 } 43 entry3 := skymodules.HostDBEntry{ 44 PublicKey: types.SiaPublicKey{ 45 Key: []byte{3}, 46 }, 47 } 48 entry4 := skymodules.HostDBEntry{ 49 PublicKey: types.SiaPublicKey{ 50 Key: []byte{4}, 51 }, 52 } 53 54 // Try inserting the first entry. Result in the host tree should be a host 55 // with a scan history length of two. 56 hdbt.hdb.updateEntry(entry1, nil) 57 updatedEntry, exists := hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 58 if !exists { 59 t.Fatal("Entry did not get inserted into the host tree") 60 } 61 if len(updatedEntry.ScanHistory) != 2 { 62 t.Fatal("new entry was not given two scanning history entries") 63 } 64 if !updatedEntry.ScanHistory[0].Timestamp.Before(updatedEntry.ScanHistory[1].Timestamp) { 65 t.Error("new entry was not provided with a sorted scanning history") 66 } 67 if !updatedEntry.ScanHistory[0].Success || !updatedEntry.ScanHistory[1].Success { 68 t.Error("new entry was not given success values despite a successful scan") 69 } 70 71 // Try inserting the second entry, but with an error. Results should largely 72 // be the same. 73 hdbt.hdb.updateEntry(entry2, someErr) 74 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry2.PublicKey) 75 if !exists { 76 t.Fatal("Entry did not get inserted into the host tree") 77 } 78 if len(updatedEntry.ScanHistory) != 2 { 79 t.Fatal("new entry was not given two scanning history entries") 80 } 81 if !updatedEntry.ScanHistory[0].Timestamp.Before(updatedEntry.ScanHistory[1].Timestamp) { 82 t.Error("new entry was not provided with a sorted scanning history") 83 } 84 if updatedEntry.ScanHistory[0].Success || updatedEntry.ScanHistory[1].Success { 85 t.Error("new entry was not given success values despite a successful scan") 86 } 87 88 // Try inserting the entry twice rapidly, nothing should change in the scan 89 // history length because it won't accept such a short turnaround. 90 hdbt.hdb.updateEntry(entry1, nil) 91 hdbt.hdb.updateEntry(entry1, nil) 92 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 93 if !exists { 94 t.Fatal("Entry did not get inserted into the host tree") 95 } 96 if len(updatedEntry.ScanHistory) != 2 { 97 t.Fatal("new updates should have been ignored", len(updatedEntry.ScanHistory)) 98 } 99 100 // Insert the first entry twice more, with no error. There should be 4 101 // entries, and the timestamps should be strictly increasing. Sleep for a 102 // bit between each update, because the hostdb during testing will not count 103 // scans if they are added too close together. 104 time.Sleep(3 * scanTimeElapsedRequirement) 105 hdbt.hdb.updateEntry(entry1, nil) 106 time.Sleep(3 * scanTimeElapsedRequirement) 107 hdbt.hdb.updateEntry(entry1, nil) 108 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 109 if !exists { 110 t.Fatal("Entry did not get inserted into the host tree") 111 } 112 if len(updatedEntry.ScanHistory) != 4 { 113 t.Fatal("new entry was not given two scanning history entries", len(updatedEntry.ScanHistory)) 114 } 115 if !updatedEntry.ScanHistory[1].Timestamp.Before(updatedEntry.ScanHistory[2].Timestamp) { 116 t.Error("new entry was not provided with a sorted scanning history") 117 } 118 if !updatedEntry.ScanHistory[2].Timestamp.Before(updatedEntry.ScanHistory[3].Timestamp) { 119 t.Error("new entry was not provided with a sorted scanning history") 120 } 121 if !updatedEntry.ScanHistory[2].Success || !updatedEntry.ScanHistory[3].Success { 122 t.Error("new entries did not get added with successful timestamps") 123 } 124 125 // Add a non-successful scan and verify that it is registered properly. 126 time.Sleep(3 * scanTimeElapsedRequirement) 127 hdbt.hdb.updateEntry(entry1, someErr) 128 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 129 if !exists { 130 t.Fatal("Entry did not get inserted into the host tree") 131 } 132 if len(updatedEntry.ScanHistory) != 5 { 133 t.Fatal("new entry was not given two scanning history entries") 134 } 135 if !updatedEntry.ScanHistory[3].Success || updatedEntry.ScanHistory[4].Success { 136 t.Error("new entries did not get added with successful timestamps") 137 } 138 139 // Prefix an invalid entry to have a scan from more than maxHostDowntime 140 // days ago. At less than minScans total, the host should not be deleted 141 // upon update. 142 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry2.PublicKey) 143 if !exists { 144 t.Fatal("Entry did not get inserted into the host tree") 145 } 146 updatedEntry.ScanHistory = append([]skymodules.HostDBScan{{}}, updatedEntry.ScanHistory...) 147 err = hdbt.hdb.staticHostTree.Modify(updatedEntry) 148 if err != nil { 149 t.Fatal(err) 150 } 151 // Entry should still exist. 152 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry2.PublicKey) 153 if !exists { 154 t.Fatal("Entry did not get inserted into the host tree") 155 } 156 // Add enough entries to get to minScans total length. When that length is 157 // reached, the entry should be deleted. 158 for i := len(updatedEntry.ScanHistory); i < minScans; i++ { 159 time.Sleep(3 * scanTimeElapsedRequirement) 160 hdbt.hdb.updateEntry(entry2, someErr) 161 } 162 // The entry should no longer exist in the hostdb, wiped for being offline. 163 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry2.PublicKey) 164 if exists { 165 t.Fatal("entry should have been purged for being offline for too long") 166 } 167 168 // Trigger compression on entry1 by adding a past scan and then adding 169 // unsuccessful scans until compression happens. 170 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 171 if !exists { 172 t.Fatal("Entry did not get inserted into the host tree") 173 } 174 updatedEntry.ScanHistory = append([]skymodules.HostDBScan{{Timestamp: time.Now().Add(maxHostDowntime * -1).Add(time.Hour * -1)}}, updatedEntry.ScanHistory...) 175 err = hdbt.hdb.staticHostTree.Modify(updatedEntry) 176 if err != nil { 177 t.Fatal(err) 178 } 179 for i := len(updatedEntry.ScanHistory); i <= minScans; i++ { 180 time.Sleep(3 * scanTimeElapsedRequirement) 181 hdbt.hdb.updateEntry(entry1, someErr) 182 } 183 // The result should be compression, and not the entry getting deleted. 184 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 185 if !exists { 186 t.Fatal("entry should not have been purged for being offline for too long") 187 } 188 if len(updatedEntry.ScanHistory) != minScans { 189 t.Error("expecting a different number of scans", len(updatedEntry.ScanHistory)) 190 } 191 if updatedEntry.HistoricDowntime == 0 { 192 t.Error("host reporting historic downtime?") 193 } 194 if updatedEntry.HistoricUptime != 0 { 195 t.Error("host not reporting historic uptime?") 196 } 197 198 // Repeat triggering compression, but with uptime this time. 199 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 200 if !exists { 201 t.Fatal("Entry did not get inserted into the host tree") 202 } 203 updatedEntry.ScanHistory = append([]skymodules.HostDBScan{{Success: true, Timestamp: time.Now().Add(time.Hour * 24 * (maxHostDownTimeInDays + 1) * -1)}}, updatedEntry.ScanHistory...) 204 err = hdbt.hdb.staticHostTree.Modify(updatedEntry) 205 if err != nil { 206 t.Fatal(err) 207 } 208 time.Sleep(3 * scanTimeElapsedRequirement) 209 hdbt.hdb.updateEntry(entry1, someErr) 210 211 // The result should be compression, and not the entry getting deleted. 212 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry1.PublicKey) 213 if !exists { 214 t.Fatal("entry should not have been purged for being offline for too long") 215 } 216 if len(updatedEntry.ScanHistory) != minScans+1 { 217 t.Error("expecting a different number of scans") 218 } 219 if updatedEntry.HistoricUptime == 0 { 220 t.Error("host not reporting historic uptime?") 221 } 222 223 // The initial scan should be ordered even if the entry's 'FirstSeen' 224 // property is greater than the HostDB blockheight 225 hdbt.hdb.blockHeight = types.BlockHeight(10) 226 entry3.FirstSeen = types.BlockHeight(20) 227 hdbt.hdb.updateEntry(entry3, nil) 228 229 // Fetch the updated entry and assert the scan history is in order 230 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry3.PublicKey) 231 if !exists { 232 t.Fatal("Entry did not get inserted into the host tree") 233 } 234 if len(updatedEntry.ScanHistory) != 2 { 235 t.Error("expecting a different number of scans") 236 } 237 if !updatedEntry.ScanHistory[0].Timestamp.Before(updatedEntry.ScanHistory[1].Timestamp) { 238 t.Error("expecting scan entries to be chronologically ordered") 239 } 240 241 // Entries with a corrupted scan history should be fixed 242 entry4.ScanHistory = skymodules.HostDBScans{ 243 {Timestamp: time.Now(), Success: true}, 244 {Timestamp: time.Now().Add(scanTimeElapsedRequirement * -3), Success: true}, 245 {Timestamp: time.Now().Add(scanTimeElapsedRequirement * -2), Success: true}, 246 } 247 err = hdbt.hdb.insert(entry4) 248 if err != nil { 249 t.Fatal("Entry did not get inserted into the host db", err) 250 } 251 hdbt.hdb.updateEntry(entry4, nil) 252 253 // Assert the scan history got fixed and a new entry was added 254 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry4.PublicKey) 255 if !exists { 256 t.Fatal("Entry did not get inserted into the host tree") 257 } 258 if len(updatedEntry.ScanHistory) != 4 { 259 t.Error("expecting a different number of scans") 260 } 261 if !updatedEntry.ScanHistory[0].Timestamp.Before(updatedEntry.ScanHistory[1].Timestamp) { 262 t.Error("expecting scan entries to be chronologically ordered") 263 } 264 } 265 266 // TestFeeChangeSignificant is a unit test for the feeChangeSignificant 267 // function. 268 func TestFeeChangeSignificant(t *testing.T) { 269 // If the difference is exactly txnFeesUpdateRatio it is significant. 270 n1 := uint64(100) 271 n2 := uint64(100 * (1 + txnFeesUpdateRatio)) 272 s := feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 273 if !s { 274 t.Fatalf("should be significant but wasn't") 275 } 276 n1 = uint64(100) 277 n2 = uint64(100 * (1 - txnFeesUpdateRatio)) 278 s = feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 279 if !s { 280 t.Fatalf("should be significant but wasn't") 281 } 282 // If the difference is bigger than txnFeesUpdateRatio it is significant. 283 n1 = uint64(100) 284 n2 = uint64(100*(1+txnFeesUpdateRatio) + 1) 285 s = feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 286 if !s { 287 t.Fatalf("should be significant but wasn't") 288 } 289 n1 = uint64(100) 290 n2 = uint64(100*(1-txnFeesUpdateRatio) - 1) 291 s = feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 292 if !s { 293 t.Fatalf("should be significant but wasn't") 294 } 295 // If the difference is a bit less then it shouldn't be significant. 296 n1 = uint64(100) 297 n2 = uint64(100*(1+txnFeesUpdateRatio) - 1) 298 s = feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 299 if s { 300 t.Fatalf("shouldn't be significant but was") 301 } 302 n1 = uint64(100) 303 n2 = uint64(100*(1-txnFeesUpdateRatio) + 1) 304 s = feeChangeSignificant(types.NewCurrency64(n1), types.NewCurrency64(n2)) 305 if s { 306 t.Fatalf("shouldn't be significant but was") 307 } 308 } 309 310 // TestUpdateEntryWithKnown host checks that a host from a knownContract (i.e. a 311 // host we have a contract with currently) is never deleted from the host tree. 312 func TestUpdateEntryWithKnownHost(t *testing.T) { 313 if testing.Short() { 314 t.SkipNow() 315 } 316 hdbt, err := newHDBTesterDeps(t.Name(), &dependencies.DependencyDisableScanLoopDeps{}) 317 if err != nil { 318 t.Fatal(err) 319 } 320 321 entry := skymodules.HostDBEntry{ 322 PublicKey: types.SiaPublicKey{ 323 Key: []byte{1}, 324 }, 325 } 326 // we need an err in updateEntry to make all interactions unsuccessful. 327 someErr := errors.New("testing err") 328 329 // Add the host from that entry to the knownContracts map. 330 hdbt.hdb.mu.Lock() 331 hdbt.hdb.knownContracts[entry.PublicKey.String()] = contractInfo{HostPublicKey: entry.PublicKey} 332 hdbt.hdb.mu.Unlock() 333 334 time.Sleep(3 * scanTimeElapsedRequirement) 335 hdbt.hdb.updateEntry(entry, someErr) 336 updatedEntry, exists := hdbt.hdb.staticHostTree.Select(entry.PublicKey) 337 if !exists { 338 t.Fatal("Entry did not get inserted into the host tree") 339 } 340 if len(updatedEntry.ScanHistory) != 2 { 341 t.Fatal("new entry was not given two scanning history entries") 342 } 343 344 // Prefix an invalid entry to have a scan from more than maxHostDowntime 345 // days ago. At less than minScans total, the host should not be deleted 346 // upon update. 347 updatedEntry.ScanHistory = append([]skymodules.HostDBScan{{}}, updatedEntry.ScanHistory...) 348 err = hdbt.hdb.staticHostTree.Modify(updatedEntry) 349 if err != nil { 350 t.Fatal(err) 351 } 352 // Entry should still exist. 353 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry.PublicKey) 354 if !exists { 355 t.Fatal("Entry did not get inserted into the host tree") 356 } 357 358 // Add enough entries to get to minScans total length. 359 for i := len(updatedEntry.ScanHistory); i < minScans; i++ { 360 time.Sleep(3 * scanTimeElapsedRequirement) 361 hdbt.hdb.updateEntry(entry, someErr) 362 } 363 // The entry should **still** exist in the hostdb, despite being offline. 364 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry.PublicKey) 365 if !exists { 366 t.Fatal("entry should not have been purged for being offline for too long") 367 } 368 369 // Remove the host from the hostContracts map and update with the same entry. It should 370 // now be deleted. 371 hdbt.hdb.mu.Lock() 372 delete(hdbt.hdb.knownContracts, updatedEntry.PublicKey.String()) 373 hdbt.hdb.mu.Unlock() 374 time.Sleep(3 * scanTimeElapsedRequirement) 375 hdbt.hdb.updateEntry(entry, someErr) 376 377 // Entry should not exist. 378 updatedEntry, exists = hdbt.hdb.staticHostTree.Select(entry.PublicKey) 379 if exists { 380 t.Fatal("Entry did not get removed from the host tree") 381 } 382 } 383 384 // Regression test to make sure managedWaitForScans shuts down at some point if 385 // the scanList has elements in it. 386 func TestWaitForScansShutdown(t *testing.T) { 387 hdb := &HostDB{ 388 scanList: []skymodules.HostDBEntry{{}}, 389 } 390 go func() { 391 if err := hdb.tg.Stop(); err != nil { 392 t.Error(err) 393 } 394 }() 395 hdb.managedWaitForScans() 396 } 397 398 // TestCompatv164SortEntryScans is a unit test that asserts the entry scans are 399 // sorted properly. 400 func TestCompatv164SortEntryScans(t *testing.T) { 401 t.Parallel() 402 403 // isSorted returns whether the given scans are sorted 404 isSorted := func(scans []skymodules.HostDBScan) bool { 405 if len(scans) == 0 { 406 return true 407 } 408 prev := scans[0].Timestamp 409 for _, scan := range scans[1:] { 410 if !scan.Timestamp.After(prev) { 411 return false 412 } 413 prev = scan.Timestamp 414 } 415 return true 416 } 417 418 // base case 419 entry := DefaultHostDBEntry 420 entry.ScanHistory = skymodules.HostDBScans{ 421 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 422 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 423 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 424 } 425 updated := compatv164SortEntryScans(&entry) 426 if updated { 427 t.Fatal("unexpected") 428 } 429 430 // assert they're properly sorted 431 if !isSorted(entry.ScanHistory) { 432 t.Fatalf("unsorted %+v", entry.ScanHistory) 433 } 434 435 // unsorted case basic 436 entry = DefaultHostDBEntry 437 entry.ScanHistory = skymodules.HostDBScans{ 438 {Timestamp: time.Now().Add(time.Hour), Success: true}, 439 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 440 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 441 } 442 updated = compatv164SortEntryScans(&entry) 443 if !updated { 444 t.Fatal("unexpected") 445 } 446 447 // assert they're properly sorted 448 if !isSorted(entry.ScanHistory) { 449 t.Fatalf("unsorted %+v", entry.ScanHistory) 450 } 451 452 // unsorted case all future 453 entry = DefaultHostDBEntry 454 entry.ScanHistory = skymodules.HostDBScans{ 455 {Timestamp: time.Now().Add(time.Hour), Success: true}, 456 {Timestamp: time.Now().Add(time.Hour * 2), Success: true}, 457 {Timestamp: time.Now().Add(time.Hour * 3), Success: true}, 458 } 459 updated = compatv164SortEntryScans(&entry) 460 if !updated { 461 t.Fatal("unexpected") 462 } 463 464 // assert they're properly sorted 465 if !isSorted(entry.ScanHistory) { 466 t.Fatalf("unsorted %+v", entry.ScanHistory) 467 } 468 469 // unsorted case some future 470 entry = DefaultHostDBEntry 471 entry.ScanHistory = skymodules.HostDBScans{ 472 {Timestamp: time.Now().Add(time.Hour), Success: true}, 473 {Timestamp: time.Now().Add(time.Hour * 2), Success: true}, 474 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 475 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 476 } 477 updated = compatv164SortEntryScans(&entry) 478 if !updated { 479 t.Fatal("unexpected") 480 } 481 482 // assert they're properly sorted 483 if !isSorted(entry.ScanHistory) { 484 t.Fatalf("unsorted %+v", entry.ScanHistory) 485 } 486 487 // unsorted case some future 488 entry = DefaultHostDBEntry 489 entry.ScanHistory = skymodules.HostDBScans{ 490 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 491 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 492 {Timestamp: time.Now().Add(time.Hour), Success: true}, 493 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 494 {Timestamp: time.Now().Add(time.Hour), Success: true}, 495 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 496 } 497 updated = compatv164SortEntryScans(&entry) 498 if !updated { 499 t.Fatal("unexpected") 500 } 501 502 // assert they're properly sorted 503 if !isSorted(entry.ScanHistory) { 504 for _, scan := range entry.ScanHistory { 505 t.Log(scan) 506 } 507 t.Fatal("unsorted") 508 } 509 }