gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/hostdb/hostdb_test.go (about)

     1  package hostdb
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"math"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  	"time"
    12  
    13  	"gitlab.com/NebulousLabs/errors"
    14  	"gitlab.com/SkynetLabs/skyd/build"
    15  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    16  	"gitlab.com/SkynetLabs/skyd/skymodules"
    17  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb/hosttree"
    18  	"go.sia.tech/siad/crypto"
    19  	"go.sia.tech/siad/modules"
    20  	"go.sia.tech/siad/modules/consensus"
    21  	"go.sia.tech/siad/modules/gateway"
    22  	"go.sia.tech/siad/modules/miner"
    23  	"go.sia.tech/siad/modules/transactionpool"
    24  	"go.sia.tech/siad/modules/wallet"
    25  	"go.sia.tech/siad/persist"
    26  	"go.sia.tech/siad/types"
    27  
    28  	"gitlab.com/NebulousLabs/siamux"
    29  )
    30  
    31  // hdbTester contains a hostdb and all dependencies.
    32  type hdbTester struct {
    33  	cs        modules.ConsensusSet
    34  	gateway   modules.Gateway
    35  	miner     modules.TestMiner
    36  	tpool     modules.TransactionPool
    37  	mux       *siamux.SiaMux
    38  	wallet    modules.Wallet
    39  	walletKey crypto.CipherKey
    40  
    41  	hdb *HostDB
    42  
    43  	persistDir string
    44  }
    45  
    46  // bareHostDB returns a HostDB with its fields initialized, but without any
    47  // dependencies or scanning threads. It is only intended for use in unit tests.
    48  func bareHostDB() *HostDB {
    49  	logger, err := persist.NewLogger(ioutil.Discard)
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  	hdb := &HostDB{
    54  		allowance:      skymodules.DefaultAllowance,
    55  		staticLog:      logger,
    56  		knownContracts: make(map[string]contractInfo),
    57  		priceTables:    make(map[string]modules.RPCPriceTable),
    58  	}
    59  	hdb.weightFunc = hdb.managedCalculateHostWeightFn(hdb.allowance)
    60  	hdb.staticHostTree = hosttree.New(hdb.weightFunc, &modules.ProductionResolver{})
    61  	hdb.staticFilteredTree = hosttree.New(hdb.weightFunc, &modules.ProductionResolver{})
    62  	return hdb
    63  }
    64  
    65  // makeHostDBEntry makes a new host entry with a random public key
    66  func makeHostDBEntry() skymodules.HostDBEntry {
    67  	dbe := DefaultHostDBEntry
    68  	_, pk := crypto.GenerateKeyPair()
    69  
    70  	dbe.PublicKey = types.Ed25519PublicKey(pk)
    71  	dbe.ScanHistory = skymodules.HostDBScans{{
    72  		Timestamp: time.Now(),
    73  		Success:   true,
    74  	}}
    75  	return dbe
    76  }
    77  
    78  // newHDBTester returns a tester object wrapping a HostDB and some extra
    79  // information for testing.
    80  func newHDBTester(name string) (*hdbTester, error) {
    81  	return newHDBTesterDeps(name, modules.ProdDependencies)
    82  }
    83  
    84  // newHDBTesterDeps returns a tester object wrapping a HostDB and some extra
    85  // information for testing, using the provided dependencies for the hostdb.
    86  func newHDBTesterDeps(name string, deps modules.Dependencies) (*hdbTester, error) {
    87  	if testing.Short() {
    88  		panic("should not be calling newHDBTester during short tests")
    89  	}
    90  	testDir := build.TempDir("HostDB", name)
    91  
    92  	g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir))
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	cs, errChan := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir))
    97  	if err := <-errChan; err != nil {
    98  		return nil, err
    99  	}
   100  	tp, err := transactionpool.New(cs, g, filepath.Join(testDir, modules.TransactionPoolDir))
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	mux, err := modules.NewSiaMux(filepath.Join(testDir, modules.SiaMuxDir), testDir, "localhost:0", "localhost:0")
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	w, err := wallet.New(cs, tp, filepath.Join(testDir, modules.WalletDir))
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	m, err := miner.New(cs, tp, w, filepath.Join(testDir, modules.MinerDir))
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	hdb, errChan := NewCustomHostDB(g, cs, tp, mux, filepath.Join(testDir, skymodules.RenterDir), deps)
   117  	if err := <-errChan; err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	hdbt := &hdbTester{
   122  		cs:      cs,
   123  		gateway: g,
   124  		miner:   m,
   125  		tpool:   tp,
   126  		wallet:  w,
   127  		mux:     mux,
   128  
   129  		hdb: hdb,
   130  
   131  		persistDir: testDir,
   132  	}
   133  
   134  	err = hdbt.initWallet()
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return hdbt, nil
   140  }
   141  
   142  // initWallet creates a wallet key, then initializes and unlocks the wallet.
   143  func (hdbt *hdbTester) initWallet() error {
   144  	hdbt.walletKey = crypto.GenerateSiaKey(crypto.TypeDefaultWallet)
   145  	_, err := hdbt.wallet.Encrypt(hdbt.walletKey)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	err = hdbt.wallet.Unlock(hdbt.walletKey)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	return nil
   154  }
   155  
   156  // TestNew tests the New function.
   157  func TestNew(t *testing.T) {
   158  	if testing.Short() {
   159  		t.SkipNow()
   160  	}
   161  	t.Parallel()
   162  	testDir := build.TempDir("HostDB", t.Name())
   163  	g, err := gateway.New("localhost:0", false, filepath.Join(testDir, modules.GatewayDir))
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	cs, errChan := consensus.New(g, false, filepath.Join(testDir, modules.ConsensusDir))
   168  	if err := <-errChan; err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	tp, err := transactionpool.New(cs, g, filepath.Join(testDir, modules.TransactionPoolDir))
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  	mux, err := modules.NewSiaMux(filepath.Join(testDir, modules.SiaMuxDir), testDir, "localhost:0", "localhost:0")
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	// Vanilla HDB, nothing should go wrong.
   181  	hdbName := filepath.Join(testDir, skymodules.RenterDir)
   182  	_, errChan = New(g, cs, tp, mux, hdbName+"1")
   183  	if err := <-errChan; err != nil {
   184  		t.Fatal(err)
   185  	}
   186  
   187  	// Nil gateway.
   188  	_, errChan = New(nil, cs, tp, mux, hdbName+"2")
   189  	if err := <-errChan; !errors.Contains(err, errNilGateway) {
   190  		t.Fatalf("expected %v, got %v", errNilGateway, err)
   191  	}
   192  	// Nil consensus set.
   193  	_, errChan = New(g, nil, tp, mux, hdbName+"3")
   194  	if err := <-errChan; !errors.Contains(err, errNilCS) {
   195  		t.Fatalf("expected %v, got %v", errNilCS, err)
   196  	}
   197  	// Nil tpool.
   198  	_, errChan = New(g, cs, nil, mux, hdbName+"3")
   199  	if err := <-errChan; !errors.Contains(err, errNilTPool) {
   200  		t.Fatalf("expected %v, got %v", errNilTPool, err)
   201  	}
   202  	// TODO: Nil siamux?
   203  	// Bad persistDir.
   204  	_, errChan = New(g, cs, tp, mux, "")
   205  	if err := <-errChan; !os.IsNotExist(err) {
   206  		t.Fatalf("expected invalid directory, got %v", err)
   207  	}
   208  }
   209  
   210  // TestRandomHosts tests the hostdb's exported RandomHosts method.
   211  func TestRandomHosts(t *testing.T) {
   212  	if testing.Short() {
   213  		t.SkipNow()
   214  	}
   215  	t.Parallel()
   216  	hdbt, err := newHDBTesterDeps(t.Name(), &dependencies.DependencyDisableScanLoopDeps{})
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	entries := make(map[string]skymodules.HostDBEntry)
   222  	nEntries := int(1e3)
   223  	for i := 0; i < nEntries; i++ {
   224  		entry := makeHostDBEntry()
   225  		entries[entry.PublicKey.String()] = entry
   226  		err := hdbt.hdb.staticFilteredTree.Insert(entry)
   227  		if err != nil {
   228  			t.Error(err)
   229  		}
   230  	}
   231  
   232  	// Check that all hosts can be queried.
   233  	for i := 0; i < 25; i++ {
   234  		hosts, err := hdbt.hdb.RandomHosts(nEntries, nil, nil)
   235  		if err != nil {
   236  			t.Fatal("Failed to get hosts", err)
   237  		}
   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]skymodules.HostDBEntry)
   242  		for _, host := range hosts {
   243  			_, exists := entries[host.PublicKey.String()]
   244  			if !exists {
   245  				t.Error("hostdb returning host that doesn't exist.")
   246  			}
   247  			_, exists = dupCheck[host.PublicKey.String()]
   248  			if exists {
   249  				t.Error("RandomHosts returning duplicates")
   250  			}
   251  			dupCheck[host.PublicKey.String()] = host
   252  		}
   253  	}
   254  
   255  	// Base case, fill out a map exposing hosts from a single RH query.
   256  	dupCheck1 := make(map[string]skymodules.HostDBEntry)
   257  	hosts, err := hdbt.hdb.RandomHosts(nEntries/2, nil, nil)
   258  	if err != nil {
   259  		t.Fatal("Failed to get hosts", err)
   260  	}
   261  	if len(hosts) != nEntries/2 {
   262  		t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2)
   263  	}
   264  	for _, host := range hosts {
   265  		_, exists := entries[host.PublicKey.String()]
   266  		if !exists {
   267  			t.Error("hostdb returning host that doesn't exist.")
   268  		}
   269  		_, exists = dupCheck1[host.PublicKey.String()]
   270  		if exists {
   271  			t.Error("RandomHosts returning duplicates")
   272  		}
   273  		dupCheck1[host.PublicKey.String()] = host
   274  	}
   275  
   276  	// Iterative case. Check that every time you query for random hosts, you
   277  	// get different responses.
   278  	for i := 0; i < 10; i++ {
   279  		dupCheck2 := make(map[string]skymodules.HostDBEntry)
   280  		var overlap, disjoint bool
   281  		hosts, err = hdbt.hdb.RandomHosts(nEntries/2, nil, nil)
   282  		if err != nil {
   283  			t.Fatal("Failed to get hosts", err)
   284  		}
   285  		if len(hosts) != nEntries/2 {
   286  			t.Fatalf("RandomHosts returned few entries. got %v wanted %v\n", len(hosts), nEntries/2)
   287  		}
   288  		for _, host := range hosts {
   289  			_, exists := entries[host.PublicKey.String()]
   290  			if !exists {
   291  				t.Error("hostdb returning host that doesn't exist.")
   292  			}
   293  			_, exists = dupCheck2[host.PublicKey.String()]
   294  			if exists {
   295  				t.Error("RandomHosts returning duplicates")
   296  			}
   297  			_, exists = dupCheck1[host.PublicKey.String()]
   298  			if exists {
   299  				overlap = true
   300  			} else {
   301  				disjoint = true
   302  			}
   303  			dupCheck2[host.PublicKey.String()] = host
   304  		}
   305  		if !overlap || !disjoint {
   306  			t.Error("Random hosts does not seem to be random")
   307  		}
   308  		dupCheck1 = dupCheck2
   309  	}
   310  
   311  	// Try exclude list by excluding every host except for the last one, and
   312  	// doing a random select.
   313  	for i := 0; i < 25; i++ {
   314  		hosts, err := hdbt.hdb.RandomHosts(nEntries, nil, nil)
   315  		if err != nil {
   316  			t.Fatal("Failed to get hosts", err)
   317  		}
   318  		var exclude []types.SiaPublicKey
   319  		for j := 1; j < len(hosts); j++ {
   320  			exclude = append(exclude, hosts[j].PublicKey)
   321  		}
   322  		rand, err := hdbt.hdb.RandomHosts(1, exclude, nil)
   323  		if err != nil {
   324  			t.Fatal("Failed to get hosts", err)
   325  		}
   326  		if len(rand) != 1 {
   327  			t.Fatal("wrong number of hosts returned")
   328  		}
   329  		if !rand[0].PublicKey.Equals(hosts[0].PublicKey) {
   330  			t.Error("exclude list seems to be excluding the wrong hosts.")
   331  		}
   332  
   333  		// Try again but request more hosts than are available.
   334  		rand, err = hdbt.hdb.RandomHosts(5, exclude, nil)
   335  		if err != nil {
   336  			t.Fatal("Failed to get hosts", err)
   337  		}
   338  		if len(rand) != 1 {
   339  			t.Fatal("wrong number of hosts returned")
   340  		}
   341  		if !rand[0].PublicKey.Equals(hosts[0].PublicKey) {
   342  			t.Error("exclude list seems to be excluding the wrong hosts.")
   343  		}
   344  
   345  		// Create an include map, and decrease the number of excluded hosts.
   346  		// Make sure all hosts returned by rand function are in the include
   347  		// map.
   348  		includeMap := make(map[string]struct{})
   349  		for j := 0; j < 50; j++ {
   350  			includeMap[hosts[j].PublicKey.String()] = struct{}{}
   351  		}
   352  		exclude = exclude[49:]
   353  
   354  		// Select only 20 hosts.
   355  		dupCheck := make(map[string]struct{})
   356  		rand, err = hdbt.hdb.RandomHosts(20, exclude, nil)
   357  		if err != nil {
   358  			t.Fatal("Failed to get hosts", err)
   359  		}
   360  		if len(rand) != 20 {
   361  			t.Error("random hosts is returning the wrong number of hosts")
   362  		}
   363  		for _, host := range rand {
   364  			_, exists := dupCheck[host.PublicKey.String()]
   365  			if exists {
   366  				t.Error("RandomHosts is selecting duplicates")
   367  			}
   368  			dupCheck[host.PublicKey.String()] = struct{}{}
   369  			_, exists = includeMap[host.PublicKey.String()]
   370  			if !exists {
   371  				t.Error("RandomHosts returning excluded hosts")
   372  			}
   373  		}
   374  
   375  		// Select exactly 50 hosts.
   376  		dupCheck = make(map[string]struct{})
   377  		rand, err = hdbt.hdb.RandomHosts(50, exclude, nil)
   378  		if err != nil {
   379  			t.Fatal("Failed to get hosts", err)
   380  		}
   381  		if len(rand) != 50 {
   382  			t.Error("random hosts is returning the wrong number of hosts")
   383  		}
   384  		for _, host := range rand {
   385  			_, exists := dupCheck[host.PublicKey.String()]
   386  			if exists {
   387  				t.Error("RandomHosts is selecting duplicates")
   388  			}
   389  			dupCheck[host.PublicKey.String()] = struct{}{}
   390  			_, exists = includeMap[host.PublicKey.String()]
   391  			if !exists {
   392  				t.Error("RandomHosts returning excluded hosts")
   393  			}
   394  		}
   395  
   396  		// Select 100 hosts.
   397  		dupCheck = make(map[string]struct{})
   398  		rand, err = hdbt.hdb.RandomHosts(100, exclude, nil)
   399  		if err != nil {
   400  			t.Fatal("Failed to get hosts", err)
   401  		}
   402  		if len(rand) != 50 {
   403  			t.Error("random hosts is returning the wrong number of hosts")
   404  		}
   405  		for _, host := range rand {
   406  			_, exists := dupCheck[host.PublicKey.String()]
   407  			if exists {
   408  				t.Error("RandomHosts is selecting duplicates")
   409  			}
   410  			dupCheck[host.PublicKey.String()] = struct{}{}
   411  			_, exists = includeMap[host.PublicKey.String()]
   412  			if !exists {
   413  				t.Error("RandomHosts returning excluded hosts")
   414  			}
   415  		}
   416  	}
   417  }
   418  
   419  // TestRemoveNonexistingHostFromHostTree checks that the host tree interface
   420  // correctly responds to having a nonexisting host removed from the host tree.
   421  func TestRemoveNonexistingHostFromHostTree(t *testing.T) {
   422  	if testing.Short() {
   423  		t.SkipNow()
   424  	}
   425  	t.Parallel()
   426  	hdbt, err := newHDBTester(t.Name())
   427  	if err != nil {
   428  		t.Fatal(err)
   429  	}
   430  
   431  	// Remove a host that doesn't exist from the tree.
   432  	err = hdbt.hdb.staticHostTree.Remove(types.SiaPublicKey{})
   433  	if err == nil {
   434  		t.Fatal("There should be an error, but not a panic:", err)
   435  	}
   436  }
   437  
   438  // TestUpdateHistoricInteractions is a simple check to ensure that incrementing
   439  // the recent and historic host interactions works
   440  func TestUpdateHistoricInteractions(t *testing.T) {
   441  	if testing.Short() {
   442  		t.SkipNow()
   443  	}
   444  	t.Parallel()
   445  
   446  	// create a HostDB tester without scanloop to be able to manually increment
   447  	// the interactions without interference.
   448  	hdbt, err := newHDBTesterDeps(t.Name(), &dependencies.DependencyDisableScanLoopDeps{})
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  
   453  	// create a HostDBEntry and add it to the tree
   454  	host := makeHostDBEntry()
   455  	err = hdbt.hdb.staticHostTree.Insert(host)
   456  	if err != nil {
   457  		t.Error(err)
   458  	}
   459  
   460  	// increment successful and failed interactions by 100
   461  	interactions := 100.0
   462  	for i := 0.0; i < interactions; i++ {
   463  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   464  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   465  	}
   466  
   467  	// get updated host from hostdb
   468  	host, ok, err := hdbt.hdb.Host(host.PublicKey)
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  	if !ok {
   473  		t.Fatal("Modified host not found in hostdb")
   474  	}
   475  
   476  	// check that recent interactions are exactly 100 and historic interactions are 0
   477  	if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions {
   478  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   479  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   480  	}
   481  	if host.HistoricFailedInteractions != 0 || host.HistoricSuccessfulInteractions != 0 {
   482  		t.Errorf("Historic Interactions should be %v but were %v and %v", 0,
   483  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   484  	}
   485  
   486  	// add single block to consensus
   487  	_, err = hdbt.miner.AddBlock()
   488  	if err != nil {
   489  		t.Fatal(err)
   490  	}
   491  
   492  	// increment interactions again by 100
   493  	for i := 0.0; i < interactions; i++ {
   494  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   495  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   496  	}
   497  
   498  	// get updated host from hostdb
   499  	host, ok, err = hdbt.hdb.Host(host.PublicKey)
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  	if !ok {
   504  		t.Fatal("Modified host not found in hostdb")
   505  	}
   506  
   507  	// historic actions should have incremented slightly, due to the clamp the
   508  	// full interactions should not have made it into the historic group.
   509  	if host.RecentFailedInteractions != interactions || host.RecentSuccessfulInteractions != interactions {
   510  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   511  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   512  	}
   513  	if host.HistoricFailedInteractions == 0 || host.HistoricSuccessfulInteractions == 0 {
   514  		t.Error("historic actions should have updated")
   515  	}
   516  
   517  	// add 200 blocks to consensus, adding large numbers of historic actions
   518  	// each time, so that the clamp does not need to be in effect anymore.
   519  	for i := 0; i < 200; i++ {
   520  		for j := uint64(0); j < 10; j++ {
   521  			hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   522  			hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   523  		}
   524  		_, err = hdbt.miner.AddBlock()
   525  		if err != nil {
   526  			t.Fatal(err)
   527  		}
   528  	}
   529  
   530  	// Add five interactions
   531  	for i := 0; i < 5; i++ {
   532  		hdbt.hdb.IncrementSuccessfulInteractions(host.PublicKey)
   533  		hdbt.hdb.IncrementFailedInteractions(host.PublicKey)
   534  	}
   535  
   536  	// get updated host from hostdb
   537  	host, ok, err = hdbt.hdb.Host(host.PublicKey)
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  	if !ok {
   542  		t.Fatal("Modified host not found in hostdb")
   543  	}
   544  
   545  	// check that recent interactions are exactly 5. Save the historic actions
   546  	// to check that decay is being handled correctly, and that the recent
   547  	// interactions are moved over correctly.
   548  	if host.RecentFailedInteractions != 5 || host.RecentSuccessfulInteractions != 5 {
   549  		t.Errorf("Interactions should be %v but were %v and %v", interactions,
   550  			host.RecentFailedInteractions, host.RecentSuccessfulInteractions)
   551  	}
   552  	historicFailed := host.HistoricFailedInteractions
   553  	if host.HistoricFailedInteractions != host.HistoricSuccessfulInteractions {
   554  		t.Error("historic failed and successful should have the same values")
   555  	}
   556  
   557  	// Add a single block to apply one round of decay.
   558  	_, err = hdbt.miner.AddBlock()
   559  	if err != nil {
   560  		t.Fatal(err)
   561  	}
   562  	host, ok, err = hdbt.hdb.Host(host.PublicKey)
   563  	if err != nil {
   564  		t.Fatal(err)
   565  	}
   566  	if !ok {
   567  		t.Fatal("Modified host not found in hostdb")
   568  	}
   569  
   570  	// Get the historic successful and failed interactions, and see that they
   571  	// are decaying properly.
   572  	expected := historicFailed*math.Pow(historicInteractionDecay, 1) + 5
   573  	if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected {
   574  		t.Errorf("Historic Interactions should be %v but were %v and %v", expected,
   575  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   576  	}
   577  
   578  	// Add 10 more blocks and check the decay again, make sure it's being
   579  	// applied correctly.
   580  	for i := 0; i < 10; i++ {
   581  		_, err := hdbt.miner.AddBlock()
   582  		if err != nil {
   583  			t.Fatal(err)
   584  		}
   585  	}
   586  	host, ok, err = hdbt.hdb.Host(host.PublicKey)
   587  	if err != nil {
   588  		t.Fatal(err)
   589  	}
   590  	if !ok {
   591  		t.Fatal("Modified host not found in hostdb")
   592  	}
   593  	expected = expected * math.Pow(historicInteractionDecay, 10)
   594  	if host.HistoricFailedInteractions != expected || host.HistoricSuccessfulInteractions != expected {
   595  		t.Errorf("Historic Interactions should be %v but were %v and %v", expected,
   596  			host.HistoricFailedInteractions, host.HistoricSuccessfulInteractions)
   597  	}
   598  }
   599  
   600  // testCheckForIPViolationsResolver is a resolver for the TestTwoAddresses test.
   601  type testCheckForIPViolationsResolver struct{}
   602  
   603  func (testCheckForIPViolationsResolver) LookupIP(host string) ([]net.IP, error) {
   604  	switch host {
   605  	case "host1":
   606  		return []net.IP{{127, 0, 0, 1}}, nil
   607  	case "host2":
   608  		return []net.IP{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, nil
   609  	case "host3":
   610  		return []net.IP{{127, 0, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}}, nil
   611  	default:
   612  		panic("shouldn't happen")
   613  	}
   614  }
   615  
   616  // testCheckForIPViolationsDeps is a custom dependency that overrides the
   617  // Resolver method to return a testCheckForIPViolationsResolver.
   618  type testCheckForIPViolationsDeps struct {
   619  	dependencies.DependencyDisableScanLoopDeps
   620  }
   621  
   622  // Resolver returns a testCheckForIPViolationsResolver.
   623  func (*testCheckForIPViolationsDeps) Resolver() modules.Resolver {
   624  	return &testCheckForIPViolationsResolver{}
   625  }
   626  
   627  // TestCheckForIPViolations tests the hostdb's CheckForIPViolations method.
   628  func TestCheckForIPViolations(t *testing.T) {
   629  	if testing.Short() {
   630  		t.SkipNow()
   631  	}
   632  	t.Parallel()
   633  
   634  	// Prepare a few hosts for the test
   635  	entry1 := makeHostDBEntry()
   636  	entry1.NetAddress = "host1:1234"
   637  	entry2 := makeHostDBEntry()
   638  	entry2.NetAddress = "host2:1234"
   639  	entry3 := makeHostDBEntry()
   640  	entry3.NetAddress = "host3:1234"
   641  
   642  	// create a HostDB tester without scanloop to be able to manually increment
   643  	// the interactions without interference.
   644  	hdbt, err := newHDBTesterDeps(t.Name(), &testCheckForIPViolationsDeps{})
   645  	if err != nil {
   646  		t.Fatal(err)
   647  	}
   648  
   649  	// Scan the entries. entry1 should be the 'oldest' and entry3 the
   650  	// 'youngest'. This also inserts the entries into the hosttree.
   651  	hdbt.hdb.managedScanHost(entry1)
   652  	entry1, _, err = hdbt.hdb.Host(entry1.PublicKey)
   653  	if err != nil {
   654  		t.Fatal(err)
   655  	}
   656  	time.Sleep(time.Millisecond)
   657  
   658  	hdbt.hdb.managedScanHost(entry2)
   659  	entry2, _, err = hdbt.hdb.Host(entry2.PublicKey)
   660  	if err != nil {
   661  		t.Fatal(err)
   662  	}
   663  	time.Sleep(time.Millisecond)
   664  
   665  	hdbt.hdb.managedScanHost(entry3)
   666  	entry3, _, err = hdbt.hdb.Host(entry3.PublicKey)
   667  	if err != nil {
   668  		t.Fatal(err)
   669  	}
   670  	time.Sleep(time.Millisecond)
   671  
   672  	// Make sure that the timestamps are not zero and that they entries have
   673  	// subnets associated with them.
   674  	if len(entry1.IPNets) == 0 || entry1.LastIPNetChange.IsZero() {
   675  		t.Fatal("entry1 wasn't updated correctly")
   676  	}
   677  	if len(entry2.IPNets) == 0 || entry2.LastIPNetChange.IsZero() {
   678  		t.Fatal("entry2 wasn't updated correctly")
   679  	}
   680  	if len(entry3.IPNets) == 0 || entry3.LastIPNetChange.IsZero() {
   681  		t.Fatal("entry3 wasn't updated correctly")
   682  	}
   683  
   684  	// Scan all the entries again in reversed order. This is a sanity check. If
   685  	// the code works as expected this shouldn't do anything since the
   686  	// hostnames didn't change. If it doesn't, it will update the timestamps
   687  	// and the following checks will fail.
   688  	time.Sleep(time.Millisecond)
   689  	hdbt.hdb.managedScanHost(entry3)
   690  	entry3, _, err = hdbt.hdb.Host(entry3.PublicKey)
   691  	if err != nil {
   692  		t.Fatal(err)
   693  	}
   694  
   695  	time.Sleep(time.Millisecond)
   696  	hdbt.hdb.managedScanHost(entry2)
   697  	entry2, _, err = hdbt.hdb.Host(entry2.PublicKey)
   698  	if err != nil {
   699  		t.Fatal(err)
   700  	}
   701  
   702  	time.Sleep(time.Millisecond)
   703  	hdbt.hdb.managedScanHost(entry1)
   704  	entry1, _, err = hdbt.hdb.Host(entry1.PublicKey)
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  
   709  	// Add entry1 and entry2. There should be no violation.
   710  	badHosts, err := hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry1.PublicKey, entry2.PublicKey})
   711  	if err != nil {
   712  		t.Fatal(err)
   713  	}
   714  	if len(badHosts) != 0 {
   715  		t.Errorf("Got %v violations, should be 0", len(badHosts))
   716  	}
   717  
   718  	// Add entry3. It should cause a violation for entry 3.
   719  	badHosts, err = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry1.PublicKey, entry2.PublicKey, entry3.PublicKey})
   720  	if err != nil {
   721  		t.Fatal(err)
   722  	}
   723  	if len(badHosts) != 1 {
   724  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   725  	}
   726  	if len(badHosts) > 0 && !badHosts[0].Equals(entry3.PublicKey) {
   727  		t.Error("Hdb returned violation for wrong host")
   728  	}
   729  
   730  	// Calling CheckForIPViolations with entry 2 as the first argument and
   731  	// entry1 as the second should result in entry3 being the bad host again.
   732  	badHosts, err = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry2.PublicKey, entry1.PublicKey, entry3.PublicKey})
   733  	if err != nil {
   734  		t.Fatal(err)
   735  	}
   736  	if len(badHosts) != 1 {
   737  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   738  	}
   739  	if len(badHosts) > 0 && !badHosts[0].Equals(entry3.PublicKey) {
   740  		t.Error("Hdb returned violation for wrong host")
   741  	}
   742  
   743  	// Calling CheckForIPViolations with entry 3 as the first argument should
   744  	// result in 1 bad host, entry3. The reason being that entry3 is the
   745  	// 'youngest' entry.
   746  	badHosts, err = hdbt.hdb.CheckForIPViolations([]types.SiaPublicKey{entry3.PublicKey, entry1.PublicKey, entry2.PublicKey})
   747  	if err != nil {
   748  		t.Fatal(err)
   749  	}
   750  	if len(badHosts) != 1 {
   751  		t.Errorf("Got %v violations, should be 1", len(badHosts))
   752  	}
   753  	if len(badHosts) > 1 || !badHosts[0].Equals(entry3.PublicKey) {
   754  		t.Error("Hdb returned violation for wrong host")
   755  	}
   756  }
   757  
   758  // TestBlockedDomains probes the blockedDomains struct and methods
   759  func TestBlockedDomains(t *testing.T) {
   760  	// Create a blocked domain
   761  	bd := newBlockedDomains(nil)
   762  
   763  	// There should be no blocked domains
   764  	if len(bd.domains) != 0 {
   765  		t.Fatalf("Expected %v domains but found %v", 0, len(bd.domains))
   766  	}
   767  
   768  	// Add blocked domains
   769  	badDomain := "mooo.com"
   770  	badSubDomain := "bad.mooo.com"
   771  	localhost := "localhost"
   772  	localhostIP := "127.0.0.1"
   773  	ipv6 := "1234:1243::1234:1234"
   774  	ipv6Zero := "::"
   775  	tld := "com"
   776  	domains := []string{badDomain, badSubDomain, localhost, localhostIP, ipv6, ipv6Zero, tld}
   777  	bd.managedAddDomains(domains)
   778  
   779  	// Check expected number of blocked domains
   780  	expected := len(domains)
   781  	actual := len(bd.domains)
   782  	if expected != actual {
   783  		t.Fatalf("Expected %v domains but found %v", expected, actual)
   784  	}
   785  
   786  	// Re-add one to verify it isn't duplicated
   787  	bd.managedAddDomains([]string{badDomain})
   788  	actual = len(bd.domains)
   789  	if expected != actual {
   790  		t.Fatalf("Expected %v domains but found %v", expected, actual)
   791  	}
   792  
   793  	// Probe the managedIsBlocked method
   794  	var domaintests = []struct {
   795  		addr    modules.NetAddress
   796  		blocked bool
   797  	}{
   798  		// badDomain Tests
   799  		{modules.NetAddress(fmt.Sprintf("%s:00", badDomain)), true},
   800  		{modules.NetAddress(fmt.Sprintf("sub.%s:00", badDomain)), true},
   801  		{modules.NetAddress(fmt.Sprintf("sub.sub.%s:00", badDomain)), true},
   802  		{modules.NetAddress(fmt.Sprintf("sub.%s:00", badSubDomain)), true},
   803  		{modules.NetAddress(fmt.Sprintf("sub.sub.%s:00", badSubDomain)), true},
   804  
   805  		// localhost test
   806  		{modules.NetAddress(fmt.Sprintf("%s:00", localhost)), true},
   807  		{modules.NetAddress(fmt.Sprintf("%s:00", localhostIP)), true},
   808  
   809  		// ipv6 test
   810  		// NOTE: ipv6 addresses need to be enclosed in square brackets
   811  		{modules.NetAddress(fmt.Sprintf("[%s]:00", ipv6)), true},
   812  		{modules.NetAddress(fmt.Sprintf("[%s]:00", ipv6Zero)), true},
   813  
   814  		// Not supporting blocking localhost subdomains
   815  		{modules.NetAddress(fmt.Sprintf("sub.%s:00", localhost)), false},
   816  
   817  		// Not blocked
   818  		{modules.NetAddress(fmt.Sprintf("%s.weird:00", badDomain)), false},
   819  		{modules.NetAddress("somedomain.com:00"), false},
   820  		{modules.NetAddress("10.10.10.10:00"), false},
   821  		{modules.NetAddress("[123::123]:00"), false},
   822  	}
   823  	for _, domaintest := range domaintests {
   824  		blocked := bd.managedIsBlocked(domaintest.addr)
   825  		if blocked != domaintest.blocked {
   826  			t.Errorf("Incorrect block result for %v; %v != %v", domaintest.addr, domaintest.blocked, blocked)
   827  		}
   828  	}
   829  
   830  	// Remove all domains
   831  	bd.managedRemoveDomains(domains)
   832  
   833  	// There should be no blocked domains
   834  	actual = len(bd.domains)
   835  	if actual != 0 {
   836  		t.Fatalf("Expected %v domains but found %v", 0, actual)
   837  	}
   838  }
   839  
   840  // TestHostDBBlockDomain tests blocking a domain for a host in the hostdb
   841  func TestHostDBBlockDomain(t *testing.T) {
   842  	if testing.Short() {
   843  		t.SkipNow()
   844  	}
   845  	t.Parallel()
   846  
   847  	// Bad domain
   848  	badDomain := "badhost.com"
   849  
   850  	// Prepare two bad hosts with the same root domain
   851  	entry1 := makeHostDBEntry()
   852  	entry1.NetAddress = modules.NetAddress(fmt.Sprintf("%v:1234", badDomain))
   853  	entry2 := makeHostDBEntry()
   854  	entry2.NetAddress = modules.NetAddress(fmt.Sprintf("another.%v:1234", badDomain))
   855  	entry3 := makeHostDBEntry()
   856  	entry3.NetAddress = "goodhost.com:1234"
   857  
   858  	// create a HostDB tester.
   859  	hdbt, err := newHDBTester(t.Name())
   860  	if err != nil {
   861  		t.Fatal(err)
   862  	}
   863  
   864  	// Add the three hosts
   865  	err1 := hdbt.hdb.staticHostTree.Insert(entry1)
   866  	err2 := hdbt.hdb.staticHostTree.Insert(entry2)
   867  	err3 := hdbt.hdb.staticHostTree.Insert(entry3)
   868  	err = errors.Compose(err1, err2, err3)
   869  	if err != nil {
   870  		t.Fatal(err)
   871  	}
   872  
   873  	// Verify all hosts are in the hosttree
   874  	allHosts, err := hdbt.hdb.AllHosts()
   875  	if err != nil {
   876  		t.Fatal(err)
   877  	}
   878  	if len(allHosts) != 3 {
   879  		t.Fatalf("Expected %v hosts but got %v", 3, len(allHosts))
   880  	}
   881  
   882  	// Block the badDomain
   883  	err = hdbt.hdb.BlockDomains([]string{badDomain})
   884  	if err != nil {
   885  		t.Fatal(err)
   886  	}
   887  
   888  	// Verify there is one domain being blocked
   889  	blockedDomains, err := hdbt.hdb.BlockedDomains()
   890  	if err != nil {
   891  		t.Fatal(err)
   892  	}
   893  	if len(blockedDomains) != 1 {
   894  		t.Fatalf("Expected %v blocked domains, got %v", 1, len(blockedDomains))
   895  	}
   896  	if blockedDomains[0] != badDomain {
   897  		t.Fatalf("Expected %v to be blocked domain, got %v", badDomain, blockedDomains[0])
   898  	}
   899  
   900  	// Verify they are blocked
   901  	if !hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry1.NetAddress) {
   902  		t.Fatal("host not blocked")
   903  	}
   904  	if !hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry2.NetAddress) {
   905  		t.Fatal("host not blocked")
   906  	}
   907  	if hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry3.NetAddress) {
   908  		t.Fatal("host should not be blocked")
   909  	}
   910  
   911  	// Verify all bad hosts are removed
   912  	allHosts, err = hdbt.hdb.AllHosts()
   913  	if err != nil {
   914  		t.Fatal(err)
   915  	}
   916  	if len(allHosts) != 1 {
   917  		t.Fatalf("Expected %v hosts but got %v", 1, len(allHosts))
   918  	}
   919  	if allHosts[0].NetAddress != entry3.NetAddress {
   920  		t.Fatal("remaining host is not the good host")
   921  	}
   922  
   923  	// Unblock the badDomain
   924  	err = hdbt.hdb.UnblockDomains([]string{badDomain})
   925  	if err != nil {
   926  		t.Fatal(err)
   927  	}
   928  
   929  	// Verify there are no domains being blocked
   930  	blockedDomains, err = hdbt.hdb.BlockedDomains()
   931  	if err != nil {
   932  		t.Fatal(err)
   933  	}
   934  	if len(blockedDomains) != 0 {
   935  		t.Fatalf("Expected %v blocked domains, got %v", 0, len(blockedDomains))
   936  	}
   937  
   938  	// Verify they are not blocked
   939  	if hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry1.NetAddress) {
   940  		t.Fatal("host should not be blocked")
   941  	}
   942  	if hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry2.NetAddress) {
   943  		t.Fatal("host should not be blocked")
   944  	}
   945  	if hdbt.hdb.staticBlockedDomains.managedIsBlocked(entry3.NetAddress) {
   946  		t.Fatal("host should not be blocked")
   947  	}
   948  }