gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/hostdb/hostdb_test.go (about)

     1  package hostdb
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"math"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  	"time"
    12  
    13  	"gitlab.com/SiaPrime/SiaPrime/build"
    14  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    15  	"gitlab.com/SiaPrime/SiaPrime/modules"
    16  	"gitlab.com/SiaPrime/SiaPrime/modules/consensus"
    17  	"gitlab.com/SiaPrime/SiaPrime/modules/gateway"
    18  	"gitlab.com/SiaPrime/SiaPrime/modules/miner"
    19  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/hostdb/hosttree"
    20  	"gitlab.com/SiaPrime/SiaPrime/modules/transactionpool"
    21  	"gitlab.com/SiaPrime/SiaPrime/modules/wallet"
    22  	"gitlab.com/SiaPrime/SiaPrime/persist"
    23  	"gitlab.com/SiaPrime/SiaPrime/types"
    24  )
    25  
    26  // hdbTester contains a hostdb and all dependencies.
    27  type hdbTester struct {
    28  	cs        modules.ConsensusSet
    29  	gateway   modules.Gateway
    30  	miner     modules.TestMiner
    31  	tpool     modules.TransactionPool
    32  	wallet    modules.Wallet
    33  	walletKey crypto.CipherKey
    34  
    35  	hdb *HostDB
    36  
    37  	persistDir string
    38  }
    39  
    40  // bareHostDB returns a HostDB with its fields initialized, but without any
    41  // dependencies or scanning threads. It is only intended for use in unit tests.
    42  func bareHostDB() *HostDB {
    43  	hdb := &HostDB{
    44  		allowance:      modules.DefaultAllowance,
    45  		log:            persist.NewLogger(ioutil.Discard),
    46  		knownContracts: make(map[string]contractInfo),
    47  	}
    48  	hdb.weightFunc = hdb.managedCalculateHostWeightFn(hdb.allowance)
    49  	hdb.hostTree = hosttree.New(hdb.weightFunc, &modules.ProductionResolver{})
    50  	hdb.filteredTree = hosttree.New(hdb.weightFunc, &modules.ProductionResolver{})
    51  	return hdb
    52  }
    53  
    54  // makeHostDBEntry makes a new host entry with a random public key
    55  func makeHostDBEntry() modules.HostDBEntry {
    56  	dbe := DefaultHostDBEntry
    57  	_, pk := crypto.GenerateKeyPair()
    58  
    59  	dbe.PublicKey = types.Ed25519PublicKey(pk)
    60  	dbe.ScanHistory = modules.HostDBScans{{
    61  		Timestamp: time.Now(),
    62  		Success:   true,
    63  	}}
    64  	return dbe
    65  }
    66  
    67  // newHDBTester returns a tester object wrapping a HostDB and some extra
    68  // information for testing.
    69  func newHDBTester(name string) (*hdbTester, error) {
    70  	return newHDBTesterDeps(name, modules.ProdDependencies)
    71  }
    72  
    73  // newHDBTesterDeps returns a tester object wrapping a HostDB and some extra
    74  // information for testing, using the provided dependencies for the hostdb.
    75  func newHDBTesterDeps(name string, deps modules.Dependencies) (*hdbTester, error) {
    76  	if testing.Short() {
    77  		panic("should not be calling newHDBTester during short tests")
    78  	}
    79  	testDir := build.TempDir("HostDB", name)
    80  
    81  	g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir))
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	cs, err := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir))
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	tp, err := transactionpool.New(cs, g, filepath.Join(testDir, modules.TransactionPoolDir))
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	w, err := wallet.New(cs, tp, filepath.Join(testDir, modules.WalletDir))
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	m, err := miner.New(cs, tp, w, filepath.Join(testDir, modules.MinerDir))
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	hdb, err := NewCustomHostDB(g, cs, tp, filepath.Join(testDir, modules.RenterDir), deps)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	hdbt := &hdbTester{
   107  		cs:      cs,
   108  		gateway: g,
   109  		miner:   m,
   110  		tpool:   tp,
   111  		wallet:  w,
   112  
   113  		hdb: hdb,
   114  
   115  		persistDir: testDir,
   116  	}
   117  
   118  	err = hdbt.initWallet()
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return hdbt, nil
   124  }
   125  
   126  // initWallet creates a wallet key, then initializes and unlocks the wallet.
   127  func (hdbt *hdbTester) initWallet() error {
   128  	hdbt.walletKey = crypto.GenerateSiaKey(crypto.TypeDefaultWallet)
   129  	_, err := hdbt.wallet.Encrypt(hdbt.walletKey)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	err = hdbt.wallet.Unlock(hdbt.walletKey)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	return nil
   138  }
   139  
   140  // TestNew tests the New function.
   141  func TestNew(t *testing.T) {
   142  	if testing.Short() {
   143  		t.SkipNow()
   144  	}
   145  	testDir := build.TempDir("HostDB", t.Name())
   146  	g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir))
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	cs, err := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir))
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	tp, err := transactionpool.New(cs, g, filepath.Join(testDir, modules.TransactionPoolDir))
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  
   159  	// Vanilla HDB, nothing should go wrong.
   160  	hdbName := filepath.Join(testDir, modules.RenterDir)
   161  	_, err = New(g, cs, tp, hdbName+"1")
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	// Nil gateway.
   167  	_, err = New(nil, cs, tp, hdbName+"2")
   168  	if err != errNilGateway {
   169  		t.Fatalf("expected %v, got %v", errNilGateway, err)
   170  	}
   171  	// Nil consensus set.
   172  	_, err = New(g, nil, tp, hdbName+"3")
   173  	if err != errNilCS {
   174  		t.Fatalf("expected %v, got %v", errNilCS, err)
   175  	}
   176  	// Nil tpool.
   177  	_, err = New(g, cs, nil, hdbName+"3")
   178  	if err != errNilTPool {
   179  		t.Fatalf("expected %v, got %v", errNilTPool, err)
   180  	}
   181  	// Bad persistDir.
   182  	_, err = New(g, cs, tp, "")
   183  	if !os.IsNotExist(err) {
   184  		t.Fatalf("expected invalid directory, got %v", err)
   185  	}
   186  }
   187  
   188  // quitAfterLoadDeps will quit startup in newHostDB
   189  type disableScanLoopDeps struct {
   190  	modules.ProductionDependencies
   191  }
   192  
   193  // Send a disrupt signal to the quitAfterLoad codebreak.
   194  func (*disableScanLoopDeps) Disrupt(s string) bool {
   195  	if s == "disableScanLoop" {
   196  		return true
   197  	}
   198  	return false
   199  }
   200  
   201  // TestRandomHosts tests the hostdb's exported RandomHosts method.
   202  func TestRandomHosts(t *testing.T) {
   203  	if testing.Short() {
   204  		t.SkipNow()
   205  	}
   206  	hdbt, err := newHDBTesterDeps(t.Name(), &disableScanLoopDeps{})
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	entries := make(map[string]modules.HostDBEntry)
   212  	nEntries := int(1e3)
   213  	for i := 0; i < nEntries; i++ {
   214  		entry := makeHostDBEntry()
   215  		entries[entry.PublicKey.String()] = entry
   216  		err := hdbt.hdb.filteredTree.Insert(entry)
   217  		if err != nil {
   218  			t.Error(err)
   219  		}
   220  	}
   221  	insertedEntries := hdbt.hdb.filteredTree.All()
   222  	if len(insertedEntries) != len(entries) {
   223  		t.Errorf("Inserted %v entries and got stored %v entries.", len(entries), len(insertedEntries))
   224  	}
   225  
   226  	hdbt.hdb.initialScanComplete = true
   227  	complete, err := hdbt.hdb.InitialScanComplete()
   228  	if !complete {
   229  		t.Error("InitialScanComplete should be true")
   230  	}
   231  	if err != nil {
   232  		t.Fatal("Failed to get InitialScanComplete()", err)
   233  	}
   234  	// Check that all hosts can be queried.
   235  	for i := 0; i < 25; i++ {
   236  		hosts, err := hdbt.hdb.RandomHosts(nEntries, nil, nil)
   237  		if err != nil {
   238  			t.Fatal("Failed to get hosts", err)
   239  		}
   240  		if len(hosts) != nEntries {
   241  			t.Errorf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries)
   242  		}
   243  		dupCheck := make(map[string]modules.HostDBEntry)
   244  		for _, host := range hosts {
   245  			_, exists := entries[host.PublicKey.String()]
   246  			if !exists {
   247  				t.Error("hostdb returning host that doesn't exist.")
   248  			}
   249  			_, exists = dupCheck[host.PublicKey.String()]
   250  			if exists {
   251  				t.Error("RandomHosts returning duplicates")
   252  			}
   253  			dupCheck[host.PublicKey.String()] = host
   254  		}
   255  	}
   256  
   257  	// Base case, fill out a map exposing hosts from a single RH query.
   258  	dupCheck1 := make(map[string]modules.HostDBEntry)
   259  	hosts, err := hdbt.hdb.RandomHosts(nEntries/2, nil, nil)
   260  	if err != nil {
   261  		t.Fatal("Failed to get hosts", err)
   262  	}
   263  	if len(hosts) != nEntries/2 {
   264  		t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2)
   265  	}
   266  	for _, host := range hosts {
   267  		_, exists := entries[host.PublicKey.String()]
   268  		if !exists {
   269  			t.Error("hostdb returning host that doesn't exist.")
   270  		}
   271  		_, exists = dupCheck1[host.PublicKey.String()]
   272  		if exists {
   273  			t.Error("RandomHosts returning duplicates")
   274  		}
   275  		dupCheck1[host.PublicKey.String()] = host
   276  	}
   277  
   278  	// Iterative case. Check that every time you query for random hosts, you
   279  	// get different responses.
   280  	for i := 0; i < 10; i++ {
   281  		dupCheck2 := make(map[string]modules.HostDBEntry)
   282  		var overlap, disjoint bool
   283  		hosts, err = hdbt.hdb.RandomHosts(nEntries/2, nil, nil)
   284  		if err != nil {
   285  			t.Fatal("Failed to get hosts", err)
   286  		}
   287  		if len(hosts) != nEntries/2 {
   288  			t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2)
   289  		}
   290  		for _, host := range hosts {
   291  			_, exists := entries[host.PublicKey.String()]
   292  			if !exists {
   293  				t.Error("hostdb returning host that doesn't exist.")
   294  			}
   295  			_, exists = dupCheck2[host.PublicKey.String()]
   296  			if exists {
   297  				t.Error("RandomHosts returning duplicates")
   298  			}
   299  			_, exists = dupCheck1[host.PublicKey.String()]
   300  			if exists {
   301  				overlap = true
   302  			} else {
   303  				disjoint = true
   304  			}
   305  			dupCheck2[host.PublicKey.String()] = host
   306  
   307  		}
   308  		if !overlap || !disjoint {
   309  			t.Error("Random hosts does not seem to be random")
   310  		}
   311  		dupCheck1 = dupCheck2
   312  	}
   313  
   314  	// Try exclude list by excluding every host except for the last one, and
   315  	// doing a random select.
   316  	for i := 0; i < 25; i++ {
   317  		hosts, err := hdbt.hdb.RandomHosts(nEntries, nil, nil)
   318  		if err != nil {
   319  			t.Fatal("Failed to get hosts", err)
   320  		}
   321  		var exclude []types.SiaPublicKey
   322  		for j := 1; j < len(hosts); j++ {
   323  			exclude = append(exclude, hosts[j].PublicKey)
   324  		}
   325  		rand, err := hdbt.hdb.RandomHosts(1, exclude, nil)
   326  		if err != nil {
   327  			t.Fatal("Failed to get hosts", err)
   328  		}
   329  		if len(rand) != 1 {
   330  			t.Fatal("wrong number of hosts returned")
   331  		}
   332  		if rand[0].PublicKey.String() != hosts[0].PublicKey.String() {
   333  			t.Error("exclude list seems to be excluding the wrong hosts.")
   334  		}
   335  
   336  		// Try again but request more hosts than are available.
   337  		rand, err = hdbt.hdb.RandomHosts(5, exclude, nil)
   338  		if err != nil {
   339  			t.Fatal("Failed to get hosts", err)
   340  		}
   341  		if len(rand) != 1 {
   342  			t.Fatal("wrong number of hosts returned")
   343  		}
   344  		if rand[0].PublicKey.String() != hosts[0].PublicKey.String() {
   345  			t.Error("exclude list seems to be excluding the wrong hosts.")
   346  		}
   347  
   348  		// Create an include map, and decrease the number of excluded hosts.
   349  		// Make sure all hosts returned by rand function are in the include
   350  		// map.
   351  		includeMap := make(map[string]struct{})
   352  		for j := 0; j < 50; j++ {
   353  			includeMap[hosts[j].PublicKey.String()] = struct{}{}
   354  		}
   355  		exclude = exclude[49:]
   356  
   357  		// Select only 20 hosts.
   358  		dupCheck := make(map[string]struct{})
   359  		rand, err = hdbt.hdb.RandomHosts(20, exclude, nil)
   360  		if err != nil {
   361  			t.Fatal("Failed to get hosts", err)
   362  		}
   363  		if len(rand) != 20 {
   364  			t.Error("random hosts is returning the wrong number of hosts")
   365  		}
   366  		for _, host := range rand {
   367  			_, exists := dupCheck[host.PublicKey.String()]
   368  			if exists {
   369  				t.Error("RandomHosts is selecting duplicates")
   370  			}
   371  			dupCheck[host.PublicKey.String()] = struct{}{}
   372  			_, exists = includeMap[host.PublicKey.String()]
   373  			if !exists {
   374  				t.Error("RandomHosts returning excluded hosts")
   375  			}
   376  		}
   377  
   378  		// Select exactly 50 hosts.
   379  		dupCheck = make(map[string]struct{})
   380  		rand, err = hdbt.hdb.RandomHosts(50, exclude, nil)
   381  		if err != nil {
   382  			t.Fatal("Failed to get hosts", err)
   383  		}
   384  		if len(rand) != 50 {
   385  			t.Error("random hosts is returning the wrong number of hosts")
   386  		}
   387  		for _, host := range rand {
   388  			_, exists := dupCheck[host.PublicKey.String()]
   389  			if exists {
   390  				t.Error("RandomHosts is selecting duplicates")
   391  			}
   392  			dupCheck[host.PublicKey.String()] = struct{}{}
   393  			_, exists = includeMap[host.PublicKey.String()]
   394  			if !exists {
   395  				t.Error("RandomHosts returning excluded hosts")
   396  			}
   397  		}
   398  
   399  		// Select 100 hosts.
   400  		dupCheck = make(map[string]struct{})
   401  		rand, err = hdbt.hdb.RandomHosts(100, exclude, nil)
   402  		if err != nil {
   403  			t.Fatal("Failed to get hosts", err)
   404  		}
   405  		if len(rand) != 50 {
   406  			t.Error("random hosts is returning the wrong number of hosts")
   407  		}
   408  		for _, host := range rand {
   409  			_, exists := dupCheck[host.PublicKey.String()]
   410  			if exists {
   411  				t.Error("RandomHosts is selecting duplicates")
   412  			}
   413  			dupCheck[host.PublicKey.String()] = struct{}{}
   414  			_, exists = includeMap[host.PublicKey.String()]
   415  			if !exists {
   416  				t.Error("RandomHosts returning excluded hosts")
   417  			}
   418  		}
   419  	}
   420  }
   421  
   422  // TestRemoveNonexistingHostFromHostTree checks that the host tree interface
   423  // correctly responds to having a nonexisting host removed from the host tree.
   424  func TestRemoveNonexistingHostFromHostTree(t *testing.T) {
   425  	if testing.Short() {
   426  		t.SkipNow()
   427  	}
   428  	hdbt, err := newHDBTester(t.Name())
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  
   433  	// Remove a host that doesn't exist from the tree.
   434  	err = hdbt.hdb.hostTree.Remove(types.SiaPublicKey{})
   435  	if err == nil {
   436  		t.Fatal("There should be an error, but not a panic:", err)
   437  	}
   438  }
   439  
   440  // TestUpdateHistoricInteractions is a simple check to ensure that incrementing
   441  // the recent and historic host interactions works
   442  func TestUpdateHistoricInteractions(t *testing.T) {
   443  	if testing.Short() {
   444  		t.SkipNow()
   445  	}
   446  
   447  	// create a HostDB tester without scanloop to be able to manually increment
   448  	// the interactions without interference.
   449  	hdbt, err := newHDBTesterDeps(t.Name(), &disableScanLoopDeps{})
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  
   454  	// create a HostDBEntry and add it to the tree
   455  	host := makeHostDBEntry()
   456  	err = hdbt.hdb.hostTree.Insert(host)
   457  	if err != nil {
   458  		t.Error(err)
   459  	}
   460  
   461  	// increment successful and failed interactions by 100
   462  	interactions := 100.0
   463  	for i := 0.0; i < interactions; i++ {
   464  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   465  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   466  	}
   467  
   468  	// get updated host from hostdb
   469  	host, ok := hdbt.hdb.Host(host.PublicKey)
   470  	if !ok {
   471  		t.Fatal("Modified host not found in hostdb")
   472  	}
   473  
   474  	// check that recent interactions are exactly 100 and historic interactions are 0
   475  	if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions {
   476  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   477  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   478  	}
   479  	if host.HistoricFailedInteractions != 0 || host.HistoricSuccessfulInteractions != 0 {
   480  		t.Errorf("Historic Interactions should be %v but were %v and %v", 0,
   481  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   482  	}
   483  
   484  	// add single block to consensus
   485  	_, err = hdbt.miner.AddBlock()
   486  	if err != nil {
   487  		t.Fatal(err)
   488  	}
   489  
   490  	// increment interactions again by 100
   491  	for i := 0.0; i < interactions; i++ {
   492  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   493  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   494  	}
   495  
   496  	// get updated host from hostdb
   497  	host, ok = hdbt.hdb.Host(host.PublicKey)
   498  	if !ok {
   499  		t.Fatal("Modified host not found in hostdb")
   500  	}
   501  
   502  	// historic actions should have incremented slightly, due to the clamp the
   503  	// full interactions should not have made it into the historic group.
   504  	if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions {
   505  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   506  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   507  	}
   508  	if host.HistoricFailedInteractions == 0 || host.HistoricSuccessfulInteractions == 0 {
   509  		t.Error("historic actions should have updated")
   510  	}
   511  
   512  	// add 200 blocks to consensus, adding large numbers of historic actions
   513  	// each time, so that the clamp does not need to be in effect anymore.
   514  	for i := 0; i < 200; i++ {
   515  		for j := uint64(0); j < 10; j++ {
   516  			hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   517  			hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   518  		}
   519  		_, err = hdbt.miner.AddBlock()
   520  		if err != nil {
   521  			t.Fatal(err)
   522  		}
   523  	}
   524  
   525  	// Add five interactions
   526  	for i := 0; i < 5; i++ {
   527  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   528  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   529  	}
   530  
   531  	// get updated host from hostdb
   532  	host, ok = hdbt.hdb.Host(host.PublicKey)
   533  	if !ok {
   534  		t.Fatal("Modified host not found in hostdb")
   535  	}
   536  
   537  	// check that recent interactions are exactly 5. Save the historic actions
   538  	// to check that decay is being handled correctly, and that the recent
   539  	// interactions are moved over correctly.
   540  	if host.RecentFailedInteractions != 5 || host.RecentSuccessfulInteractions != 5 {
   541  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   542  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   543  	}
   544  	historicFailed := host.HistoricFailedInteractions
   545  	if host.HistoricFailedInteractions != host.HistoricSuccessfulInteractions {
   546  		t.Error("historic failed and successful should have the same values")
   547  	}
   548  
   549  	// Add a single block to apply one round of decay.
   550  	_, err = hdbt.miner.AddBlock()
   551  	if err != nil {
   552  		t.Fatal(err)
   553  	}
   554  	host, ok = hdbt.hdb.Host(host.PublicKey)
   555  	if !ok {
   556  		t.Fatal("Modified host not found in hostdb")
   557  	}
   558  
   559  	// Get the historic successful and failed interactions, and see that they
   560  	// are decaying properly.
   561  	expected := historicFailed*math.Pow(historicInteractionDecay, 1) + 5
   562  	if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected {
   563  		t.Errorf("Historic Interactions should be %v but were %v and %v", expected,
   564  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   565  	}
   566  
   567  	// Add 10 more blocks and check the decay again, make sure it's being
   568  	// applied correctly.
   569  	for i := 0; i < 10; i++ {
   570  		_, err := hdbt.miner.AddBlock()
   571  		if err != nil {
   572  			t.Fatal(err)
   573  		}
   574  	}
   575  	host, ok = hdbt.hdb.Host(host.PublicKey)
   576  	if !ok {
   577  		t.Fatal("Modified host not found in hostdb")
   578  	}
   579  	expected = expected * math.Pow(historicInteractionDecay, 10)
   580  	if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected {
   581  		t.Errorf("Historic Interactions should be %v but were %v and %v", expected,
   582  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   583  	}
   584  }
   585  
   586  // testCheckForIPViolationsResolver is a resolver for the TestTwoAddresses test.
   587  type testCheckForIPViolationsResolver struct{}
   588  
   589  func (testCheckForIPViolationsResolver) LookupIP(host string) ([]net.IP, error) {
   590  	switch host {
   591  	case "host1":
   592  		return []net.IP{{127, 0, 0, 1}}, nil
   593  	case "host2":
   594  		return []net.IP{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, nil
   595  	case "host3":
   596  		return []net.IP{{127, 0, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}}, nil
   597  	default:
   598  		panic("shouldn't happen")
   599  	}
   600  }
   601  
   602  // testCheckForIPViolationsDeps is a custom dependency that overrides the
   603  // Resolver method to return a testCheckForIPViolationsResolver.
   604  type testCheckForIPViolationsDeps struct {
   605  	disableScanLoopDeps
   606  }
   607  
   608  // Resolver returns a testCheckForIPViolationsResolver.
   609  func (*testCheckForIPViolationsDeps) Resolver() modules.Resolver {
   610  	return &testCheckForIPViolationsResolver{}
   611  }
   612  
   613  // TestCheckForIPViolations tests the hostdb's CheckForIPViolations method.
   614  func TestCheckForIPViolations(t *testing.T) {
   615  	if testing.Short() {
   616  		t.SkipNow()
   617  	}
   618  
   619  	// Prepare a few hosts for the test
   620  	entry1 := makeHostDBEntry()
   621  	entry1.NetAddress = "host1:1234"
   622  	entry2 := makeHostDBEntry()
   623  	entry2.NetAddress = "host2:1234"
   624  	entry3 := makeHostDBEntry()
   625  	entry3.NetAddress = "host3:1234"
   626  
   627  	// create a HostDB tester without scanloop to be able to manually increment
   628  	// the interactions without interference.
   629  	hdbt, err := newHDBTesterDeps(t.Name(), &testCheckForIPViolationsDeps{})
   630  	if err != nil {
   631  		t.Fatal(err)
   632  	}
   633  
   634  	hdbt.hdb.SetIPViolationCheck(true)
   635  
   636  	// Scan the entries. entry1 should be the 'oldest' and entry3 the
   637  	// 'youngest'. This also inserts the entries into the hosttree.
   638  	hdbt.hdb.managedScanHost(entry1)
   639  	entry1, _ = hdbt.hdb.Host(entry1.PublicKey)
   640  	time.Sleep(time.Millisecond)
   641  
   642  	hdbt.hdb.managedScanHost(entry2)
   643  	entry2, _ = hdbt.hdb.Host(entry2.PublicKey)
   644  	time.Sleep(time.Millisecond)
   645  
   646  	hdbt.hdb.managedScanHost(entry3)
   647  	entry3, _ = hdbt.hdb.Host(entry3.PublicKey)
   648  	time.Sleep(time.Millisecond)
   649  
   650  	// Make sure that the timestamps are not zero and that they entries have
   651  	// subnets associated with them.
   652  	if len(entry1.IPNets) == 0 || entry1.LastIPNetChange.IsZero() {
   653  		t.Fatal("entry1 wasn't updated correctly")
   654  	}
   655  	if len(entry2.IPNets) == 0 || entry2.LastIPNetChange.IsZero() {
   656  		t.Fatal("entry2 wasn't updated correctly")
   657  	}
   658  	if len(entry3.IPNets) == 0 || entry3.LastIPNetChange.IsZero() {
   659  		t.Fatal("entry3 wasn't updated correctly")
   660  	}
   661  
   662  	// Scan all the entries again in reversed order. This is a sanity check. If
   663  	// the code works as expected this shouldn't do anything since the
   664  	// hostnames didn't change. If it doesn't, it will update the timestamps
   665  	// and the following checks will fail.
   666  	time.Sleep(time.Millisecond)
   667  	hdbt.hdb.managedScanHost(entry3)
   668  	entry3, _ = hdbt.hdb.Host(entry3.PublicKey)
   669  
   670  	time.Sleep(time.Millisecond)
   671  	hdbt.hdb.managedScanHost(entry2)
   672  	entry2, _ = hdbt.hdb.Host(entry2.PublicKey)
   673  
   674  	time.Sleep(time.Millisecond)
   675  	hdbt.hdb.managedScanHost(entry1)
   676  	entry1, _ = hdbt.hdb.Host(entry1.PublicKey)
   677  
   678  	// Add entry1 and entry2. There should be no violation.
   679  	badHosts := hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry1.PublicKey, entry2.PublicKey})
   680  	if len(badHosts) != 0 {
   681  		t.Errorf("Got %v violations, should be 0", len(badHosts))
   682  	}
   683  
   684  	// Add entry3. It should cause a violation for entry 3.
   685  	badHosts = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry1.PublicKey, entry2.PublicKey, entry3.PublicKey})
   686  	if len(badHosts) != 1 {
   687  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   688  	}
   689  	if len(badHosts) > 0 && !bytes.Equal(badHosts[0].Key, entry3.PublicKey.Key) {
   690  		t.Error("Hdb returned violation for wrong host")
   691  	}
   692  
   693  	// Calling CheckForIPViolations with entry 2 as the first argument and
   694  	// entry1 as the second should result in entry3 being the bad host again.
   695  	badHosts = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry2.PublicKey, entry1.PublicKey, entry3.PublicKey})
   696  	if len(badHosts) != 1 {
   697  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   698  	}
   699  	if len(badHosts) > 0 && !bytes.Equal(badHosts[0].Key, entry3.PublicKey.Key) {
   700  		t.Error("Hdb returned violation for wrong host")
   701  	}
   702  
   703  	// Calling CheckForIPViolations with entry 3 as the first argument should
   704  	// result in 1 bad host, entry3. The reason being that entry3 is the
   705  	// 'youngest' entry.
   706  	badHosts = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry3.PublicKey, entry1.PublicKey, entry2.PublicKey})
   707  	if len(badHosts) != 1 {
   708  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   709  	}
   710  	if len(badHosts) > 1 || !bytes.Equal(badHosts[0].Key, entry3.PublicKey.Key) {
   711  		t.Error("Hdb returned violation for wrong host")
   712  	}
   713  }