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  }