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

     1  package hostdb
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  	"time"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  
    11  	"gitlab.com/SkynetLabs/skyd/skymodules"
    12  	"go.sia.tech/siad/modules"
    13  	"go.sia.tech/siad/types"
    14  )
    15  
    16  var (
    17  	// Set the default test allowance
    18  	DefaultTestAllowance = skymodules.Allowance{
    19  		Funds:       types.SiacoinPrecision.Mul64(500),
    20  		Hosts:       uint64(50),
    21  		Period:      3 * types.BlocksPerMonth,
    22  		RenewWindow: types.BlocksPerMonth,
    23  
    24  		ExpectedStorage:    1e12,                                         // 1 TB
    25  		ExpectedUpload:     uint64(200e9) / uint64(types.BlocksPerMonth), // 200 GB per month
    26  		ExpectedDownload:   uint64(100e9) / uint64(types.BlocksPerMonth), // 100 GB per month
    27  		ExpectedRedundancy: 3.0,                                          // default is 10/30 erasure coding
    28  	}
    29  
    30  	// The default entry to use when performing scoring.
    31  	DefaultHostDBEntry = skymodules.HostDBEntry{
    32  		HostExternalSettings: modules.HostExternalSettings{
    33  			AcceptingContracts: true,
    34  			MaxDuration:        26e3,
    35  			RemainingStorage:   250e9,
    36  			WindowSize:         144,
    37  
    38  			Collateral:    types.NewCurrency64(250).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte),
    39  			MaxCollateral: types.NewCurrency64(750).Mul(types.SiacoinPrecision),
    40  
    41  			BaseRPCPrice:           types.SiacoinPrecision.Mul64(100).Div64(1e9),
    42  			ContractPrice:          types.NewCurrency64(5).Mul(types.SiacoinPrecision),
    43  			DownloadBandwidthPrice: types.SiacoinPrecision.Mul64(100).Div64(1e12),
    44  			SectorAccessPrice:      types.SiacoinPrecision.Mul64(2).Div64(1e6),
    45  			StoragePrice:           types.NewCurrency64(100).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte),
    46  			UploadBandwidthPrice:   types.SiacoinPrecision.Mul64(100).Div64(1e12),
    47  
    48  			Version: modules.RHPVersion,
    49  		},
    50  	}
    51  )
    52  
    53  // calculateWeightFromUInt64Price will fill out a host entry with a bunch of
    54  // defaults, and then grab the weight of that host using a set price.
    55  func calculateWeightFromUInt64Price(price, collateral uint64) (weight types.Currency, err error) {
    56  	hdb := bareHostDB()
    57  	err = hdb.SetAllowance(DefaultTestAllowance)
    58  	if err != nil {
    59  		return
    60  	}
    61  	hdb.blockHeight = 0
    62  
    63  	entry := DefaultHostDBEntry
    64  	entry.StoragePrice = types.NewCurrency64(price).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte)
    65  	entry.Collateral = types.NewCurrency64(collateral).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte)
    66  
    67  	return hdb.weightFunc(entry).Score(), nil
    68  }
    69  
    70  // TestHostDBBasePriceAdjustment ensures that the basePriceAdjustment is impacted by
    71  // changes to BaseRPCPrice, SectorAccessPrice, and MinDownloadBandwidthPrice.
    72  func TestHostDBBasePriceAdjustment(t *testing.T) {
    73  	if testing.Short() {
    74  		t.SkipNow()
    75  	}
    76  	t.Parallel()
    77  	hdb := bareHostDB()
    78  	entry := DefaultHostDBEntry
    79  
    80  	// Confirm default entry has score of 1
    81  	bpa := hdb.basePriceAdjustments(entry)
    82  	if bpa != 1 {
    83  		t.Error("BasePriceAdjustment for default entry should be 1 but was", bpa)
    84  	}
    85  
    86  	// Confirm a higher BaseRPCPrice results in an almost zero score
    87  	entry.BaseRPCPrice = entry.MaxBaseRPCPrice().Mul64(2)
    88  	bpa = hdb.basePriceAdjustments(entry)
    89  	if bpa != math.SmallestNonzeroFloat64 {
    90  		t.Errorf("BasePriceAdjustment should have been %v but was %v", math.SmallestNonzeroFloat64, bpa)
    91  	}
    92  	entry.BaseRPCPrice = DefaultHostDBEntry.BaseRPCPrice
    93  
    94  	// Confirm a higher SectorAccessPrice results in an almost zero score
    95  	entry.SectorAccessPrice = entry.MaxSectorAccessPrice().Mul64(2)
    96  	bpa = hdb.basePriceAdjustments(entry)
    97  	if bpa != math.SmallestNonzeroFloat64 {
    98  		t.Errorf("BasePriceAdjustment should have been %v but was %v", math.SmallestNonzeroFloat64, bpa)
    99  	}
   100  	entry.SectorAccessPrice = DefaultHostDBEntry.SectorAccessPrice
   101  
   102  	// Confirm a lower DownloadBandwidthPrice results in an almost zero score.
   103  	// Check by adjusting the price with both constants
   104  	entry.DownloadBandwidthPrice = DefaultHostDBEntry.DownloadBandwidthPrice.Div64(modules.MaxBaseRPCPriceVsBandwidth)
   105  	bpa = hdb.basePriceAdjustments(entry)
   106  	if bpa != math.SmallestNonzeroFloat64 {
   107  		t.Errorf("BasePriceAdjustment should have been %v but was %v", math.SmallestNonzeroFloat64, bpa)
   108  	}
   109  	entry.DownloadBandwidthPrice = DefaultHostDBEntry.DownloadBandwidthPrice.Div64(modules.MaxSectorAccessPriceVsBandwidth)
   110  	bpa = hdb.basePriceAdjustments(entry)
   111  	if bpa != math.SmallestNonzeroFloat64 {
   112  		t.Errorf("BasePriceAdjustment should have been %v but was %v", math.SmallestNonzeroFloat64, bpa)
   113  	}
   114  }
   115  
   116  // TestHostWeightBasePrice checks that a host with an unacceptable BaseRPCPrice
   117  // or SectorAccessPrice has a lower score.
   118  func TestHostWeightBasePrice(t *testing.T) {
   119  	if testing.Short() {
   120  		t.SkipNow()
   121  	}
   122  	t.Parallel()
   123  	hdb := bareHostDB()
   124  
   125  	entry := DefaultHostDBEntry
   126  	entry2 := DefaultHostDBEntry
   127  	entry2.BaseRPCPrice = entry.MaxBaseRPCPrice().Mul64(2)
   128  	entry3 := DefaultHostDBEntry
   129  	entry3.SectorAccessPrice = entry.MaxSectorAccessPrice().Mul64(2)
   130  
   131  	sDefault := hdb.weightFunc(entry).Score()
   132  	sInsaneBRPCPrice := hdb.weightFunc(entry2).Score()
   133  	sInsaneSAPrice := hdb.weightFunc(entry3).Score()
   134  	if sDefault.Cmp(sInsaneBRPCPrice) <= 0 {
   135  		t.Log("Default Score", sDefault)
   136  		t.Log("Bad BaseRPCPrice Score", sInsaneBRPCPrice)
   137  		t.Error("Default host should have higher score")
   138  	}
   139  	if sDefault.Cmp(sInsaneSAPrice) <= 0 {
   140  		t.Log("Default Score", sDefault)
   141  		t.Log("Bad SectorAccess Score", sInsaneSAPrice)
   142  		t.Error("Default host should have higher score")
   143  	}
   144  	if sInsaneBRPCPrice.Cmp(sInsaneSAPrice) != 0 {
   145  		t.Log("Bad BaseRPCPrice Score", sInsaneBRPCPrice)
   146  		t.Log("Bad SectorAccess Score", sInsaneSAPrice)
   147  		t.Error("Hosts should have the same score")
   148  	}
   149  }
   150  
   151  // TestHostWeightDistinctPrices ensures that the host weight is different if the
   152  // prices are different, and that a higher price has a lower score.
   153  func TestHostWeightDistinctPrices(t *testing.T) {
   154  	if testing.Short() {
   155  		t.SkipNow()
   156  	}
   157  	t.Parallel()
   158  	weight1, err1 := calculateWeightFromUInt64Price(300, 100)
   159  	weight2, err2 := calculateWeightFromUInt64Price(301, 100)
   160  	if err := errors.Compose(err1, err2); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	if weight1.Cmp(weight2) <= 0 {
   164  		t.Log(weight1)
   165  		t.Log(weight2)
   166  		t.Error("Weight of expensive host is not the correct value.")
   167  	}
   168  }
   169  
   170  // TestHostWeightDistinctCollateral ensures that the host weight is different if
   171  // the collaterals are different, and that a higher collateral has a higher
   172  // score.
   173  func TestHostWeightDistinctCollateral(t *testing.T) {
   174  	if testing.Short() {
   175  		t.SkipNow()
   176  	}
   177  	t.Parallel()
   178  	weight1, err1 := calculateWeightFromUInt64Price(300, 100)
   179  	weight2, err2 := calculateWeightFromUInt64Price(300, 99)
   180  	if err := errors.Compose(err1, err2); err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if weight1.Cmp(weight2) <= 0 {
   184  		t.Log(weight1)
   185  		t.Log(weight2)
   186  		t.Error("Weight of expensive host is not the correct value.")
   187  	}
   188  }
   189  
   190  // When the collateral is below the cutoff, the collateral should be more
   191  // important than the price.
   192  func TestHostWeightCollateralBelowCutoff(t *testing.T) {
   193  	if testing.Short() {
   194  		t.SkipNow()
   195  	}
   196  	t.Parallel()
   197  	weight1, err1 := calculateWeightFromUInt64Price(300, 10)
   198  	weight2, err2 := calculateWeightFromUInt64Price(150, 5)
   199  	if err := errors.Compose(err1, err2); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	if weight1.Cmp(weight2) <= 0 {
   203  		t.Log(weight1)
   204  		t.Log(weight2)
   205  		t.Error("Weight of expensive host is not the correct value.")
   206  	}
   207  }
   208  
   209  // When the collateral is below the cutoff, the price should be more important
   210  // than the collateral.
   211  func TestHostWeightCollateralAboveCutoff(t *testing.T) {
   212  	if testing.Short() {
   213  		t.SkipNow()
   214  	}
   215  	t.Parallel()
   216  	weight1, err1 := calculateWeightFromUInt64Price(300, 1000)
   217  	weight2, err2 := calculateWeightFromUInt64Price(150, 500)
   218  	if err := errors.Compose(err1, err2); err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	if weight1.Cmp(weight2) >= 0 {
   222  		t.Log(weight1)
   223  		t.Log(weight2)
   224  		t.Error("Weight of expensive host is not the correct value.")
   225  	}
   226  }
   227  
   228  // TestHostWeightIdenticalPrices checks that the weight function is
   229  // deterministic for two hosts that have identical settings - each should get
   230  // the same score.
   231  func TestHostWeightIdenticalPrices(t *testing.T) {
   232  	if testing.Short() {
   233  		t.SkipNow()
   234  	}
   235  	t.Parallel()
   236  	weight1, err1 := calculateWeightFromUInt64Price(42, 100)
   237  	weight2, err2 := calculateWeightFromUInt64Price(42, 100)
   238  	if err := errors.Compose(err1, err2); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	if weight1.Cmp(weight2) != 0 {
   242  		t.Error("Weight of identically priced hosts should be equal.")
   243  	}
   244  }
   245  
   246  // TestHostWeightWithOnePricedZero checks that nothing unexpected happens when
   247  // there is a zero price, and also checks that the zero priced host scores
   248  // higher  than the host that charges money.
   249  func TestHostWeightWithOnePricedZero(t *testing.T) {
   250  	if testing.Short() {
   251  		t.SkipNow()
   252  	}
   253  	t.Parallel()
   254  	weight1, err1 := calculateWeightFromUInt64Price(5, 10)
   255  	weight2, err2 := calculateWeightFromUInt64Price(0, 10)
   256  	if err := errors.Compose(err1, err2); err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	if weight1.Cmp(weight2) >= 0 {
   260  		t.Log(weight1)
   261  		t.Log(weight2)
   262  		t.Error("Zero-priced host should have higher weight than nonzero-priced host.")
   263  	}
   264  }
   265  
   266  // TestHostWeightBothPricesZero checks that there is nondeterminism in the
   267  // weight function even with zero value prices.
   268  func TestHostWeightWithBothPricesZero(t *testing.T) {
   269  	if testing.Short() {
   270  		t.SkipNow()
   271  	}
   272  	t.Parallel()
   273  	weight1, err1 := calculateWeightFromUInt64Price(0, 100)
   274  	weight2, err2 := calculateWeightFromUInt64Price(0, 100)
   275  	if err := errors.Compose(err1, err2); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	if weight1.Cmp(weight2) != 0 {
   279  		t.Error("Weight of two zero-priced hosts should be equal.")
   280  	}
   281  }
   282  
   283  // TestHostWeightWithNoCollateral checks that nothing bad (like a panic) happens
   284  // when the collateral is set to zero.
   285  func TestHostWeightWithNoCollateral(t *testing.T) {
   286  	if testing.Short() {
   287  		t.SkipNow()
   288  	}
   289  	t.Parallel()
   290  	weight1, err1 := calculateWeightFromUInt64Price(300, 1)
   291  	weight2, err2 := calculateWeightFromUInt64Price(300, 0)
   292  	if err := errors.Compose(err1, err2); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	if weight1.Cmp(weight2) <= 0 {
   296  		t.Log(weight1)
   297  		t.Log(weight2)
   298  		t.Error("Weight of lower priced host should be higher")
   299  	}
   300  }
   301  
   302  // TestHostWeightMaxDuration checks that the host with an unacceptable duration
   303  // has a lower score.
   304  func TestHostWeightMaxDuration(t *testing.T) {
   305  	if testing.Short() {
   306  		t.SkipNow()
   307  	}
   308  	t.Parallel()
   309  	hdb := bareHostDB()
   310  	err := hdb.SetAllowance(DefaultTestAllowance)
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  
   315  	entry := DefaultHostDBEntry
   316  	entry2 := DefaultHostDBEntry
   317  	entry2.MaxDuration = DefaultTestAllowance.Period + DefaultTestAllowance.RenewWindow
   318  
   319  	// Entry2 is exactly at the limit. Weights should match.
   320  	w1 := hdb.weightFunc(entry).Score()
   321  	w2 := hdb.weightFunc(entry2).Score()
   322  	if w1.Cmp(w2) != 0 {
   323  		t.Error("Entries should have same weight", w1, w2)
   324  	}
   325  
   326  	// Entry2 is just below the limit. Should have smallest weight possible.
   327  	entry2.MaxDuration--
   328  	w2 = hdb.weightFunc(entry2).Score()
   329  	if w1.Cmp(w2) <= 0 {
   330  		t.Error("Entry2 should have smaller weight", w1, w2)
   331  	}
   332  	if w2.Cmp64(1) != 0 {
   333  		t.Error("Entry2 should have smallest weight")
   334  	}
   335  }
   336  
   337  // TestHostWeightStorageRemainingDifferences checks that the host with more
   338  // collateral has more weight.
   339  func TestHostWeightCollateralDifferences(t *testing.T) {
   340  	if testing.Short() {
   341  		t.SkipNow()
   342  	}
   343  	t.Parallel()
   344  	hdb := bareHostDB()
   345  
   346  	entry := DefaultHostDBEntry
   347  	entry2 := DefaultHostDBEntry
   348  	entry2.Collateral = entry.Collateral.Mul64(2)
   349  	entry2.MaxCollateral = entry.MaxCollateral.Mul64(2)
   350  
   351  	w1 := hdb.weightFunc(entry).Score()
   352  	w2 := hdb.weightFunc(entry2).Score()
   353  	if w1.Cmp(w2) <= 0 {
   354  		t.Log("w1:", w1)
   355  		t.Log("w2:", w2)
   356  		t.Error("Larger collateral should have more weight")
   357  	}
   358  }
   359  
   360  // TestHostWeightStorageRemainingDifferences checks that hosts with less storage
   361  // remaining have a lower weight.
   362  func TestHostWeightStorageRemainingDifferences(t *testing.T) {
   363  	if testing.Short() {
   364  		t.SkipNow()
   365  	}
   366  	t.Parallel()
   367  	hdb := bareHostDB()
   368  
   369  	// Create two entries with different host keys.
   370  	entry := DefaultHostDBEntry
   371  	entry.PublicKey.Key = fastrand.Bytes(16)
   372  	entry2 := DefaultHostDBEntry
   373  	entry2.PublicKey.Key = fastrand.Bytes(16)
   374  
   375  	// The first entry has more storage remaining than the second.
   376  	entry.RemainingStorage = skymodules.DefaultAllowance.ExpectedStorage // 1e12
   377  	entry2.RemainingStorage = 1e3
   378  
   379  	// The entry with more storage should have the higher score.
   380  	w1 := hdb.weightFunc(entry).Score()
   381  	w2 := hdb.weightFunc(entry2).Score()
   382  	if w1.Cmp(w2) <= 0 {
   383  		t.Log(w1)
   384  		t.Log(w2)
   385  		t.Error("Larger storage remaining should have more weight")
   386  	}
   387  
   388  	// Change both entries to have the same remaining storage but add contractInfo
   389  	// to the HostDB to make it think that we already uploaded some data to one of
   390  	// the entries. This entry should have the higher score.
   391  	entry.RemainingStorage = 1e3
   392  	entry2.RemainingStorage = 1e3
   393  	hdb.knownContracts[entry.PublicKey.String()] = contractInfo{
   394  		HostPublicKey: entry.PublicKey,
   395  		StoredData:    hdb.allowance.ExpectedStorage,
   396  	}
   397  	w1 = hdb.weightFunc(entry).Score()
   398  	w2 = hdb.weightFunc(entry2).Score()
   399  	if w1.Cmp(w2) <= 0 {
   400  		t.Log(w1)
   401  		t.Log(w2)
   402  		t.Error("Entry with uploaded data should have higher score")
   403  	}
   404  }
   405  
   406  // TestHostWeightVersionDifferences checks that a host with an out of date
   407  // version has a lower score than a host with a more recent version.
   408  func TestHostWeightVersionDifferences(t *testing.T) {
   409  	if testing.Short() {
   410  		t.SkipNow()
   411  	}
   412  	t.Parallel()
   413  	hdb := bareHostDB()
   414  
   415  	entry := DefaultHostDBEntry
   416  	entry2 := DefaultHostDBEntry
   417  	entry2.Version = "v1.3.2"
   418  	w1 := hdb.weightFunc(entry)
   419  	w2 := hdb.weightFunc(entry2)
   420  
   421  	if w1.Score().Cmp(w2.Score()) <= 0 {
   422  		t.Log(w1)
   423  		t.Log(w2)
   424  		t.Error("Higher version should have more weight")
   425  	}
   426  }
   427  
   428  // TestHostWeightLifetimeDifferences checks that a host that has been on the
   429  // chain for more time has a higher weight than a host that is newer.
   430  func TestHostWeightLifetimeDifferences(t *testing.T) {
   431  	if testing.Short() {
   432  		t.SkipNow()
   433  	}
   434  	t.Parallel()
   435  	hdb := bareHostDB()
   436  	hdb.blockHeight = 10000
   437  
   438  	entry := DefaultHostDBEntry
   439  	entry2 := DefaultHostDBEntry
   440  	entry2.FirstSeen = 8100
   441  	w1 := hdb.weightFunc(entry).Score()
   442  	w2 := hdb.weightFunc(entry2).Score()
   443  
   444  	if w1.Cmp(w2) <= 0 {
   445  		t.Log(w1)
   446  		t.Log(w2)
   447  		t.Error("Been around longer should have more weight")
   448  	}
   449  }
   450  
   451  // TestHostWeightUptimeDifferences checks that hosts with poorer uptimes have
   452  // lower weights.
   453  func TestHostWeightUptimeDifferences(t *testing.T) {
   454  	if testing.Short() {
   455  		t.SkipNow()
   456  	}
   457  	t.Parallel()
   458  	hdb := bareHostDB()
   459  	hdb.blockHeight = 10000
   460  
   461  	entry := DefaultHostDBEntry
   462  	entry.ScanHistory = skymodules.HostDBScans{
   463  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   464  		{Timestamp: time.Now().Add(time.Hour * -80), Success: true},
   465  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   466  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   467  		{Timestamp: time.Now().Add(time.Hour * -20), Success: true},
   468  	}
   469  
   470  	entry2 := entry
   471  	entry2.ScanHistory = skymodules.HostDBScans{
   472  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   473  		{Timestamp: time.Now().Add(time.Hour * -80), Success: true},
   474  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   475  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   476  		{Timestamp: time.Now().Add(time.Hour * -20), Success: false},
   477  	}
   478  	w1 := hdb.weightFunc(entry).Score()
   479  	w2 := hdb.weightFunc(entry2).Score()
   480  
   481  	if w1.Cmp(w2) <= 0 {
   482  		t.Log(w1)
   483  		t.Log(w2)
   484  		t.Error("A host with recorded downtime should have a lower score")
   485  	}
   486  }
   487  
   488  // TestHostWeightUptimeDifferences2 checks that hosts with poorer uptimes have
   489  // lower weights.
   490  func TestHostWeightUptimeDifferences2(t *testing.T) {
   491  	t.Skip("Hostdb is not currently doing exponentiation on uptime")
   492  	if testing.Short() {
   493  		t.SkipNow()
   494  	}
   495  	t.Parallel()
   496  	hdb := bareHostDB()
   497  	hdb.blockHeight = 10000
   498  
   499  	entry := DefaultHostDBEntry
   500  	entry.ScanHistory = skymodules.HostDBScans{
   501  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   502  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   503  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   504  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   505  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   506  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   507  		{Timestamp: time.Now().Add(time.Hour * -80), Success: false},
   508  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   509  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   510  		{Timestamp: time.Now().Add(time.Hour * -20), Success: true},
   511  	}
   512  
   513  	entry2 := entry
   514  	entry2.ScanHistory = skymodules.HostDBScans{
   515  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   516  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   517  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   518  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   519  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   520  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   521  		{Timestamp: time.Now().Add(time.Hour * -80), Success: true},
   522  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   523  		{Timestamp: time.Now().Add(time.Hour * -40), Success: false},
   524  		{Timestamp: time.Now().Add(time.Hour * -20), Success: true},
   525  	}
   526  	w1 := hdb.weightFunc(entry).Score()
   527  	w2 := hdb.weightFunc(entry2).Score()
   528  
   529  	if w1.Cmp(w2) <= 0 {
   530  		t.Log(w1)
   531  		t.Log(w2)
   532  		t.Errorf("Downtime that's further in the past should be penalized less")
   533  	}
   534  }
   535  
   536  // TestHostWeightUptimeDifferences3 checks that hosts with poorer uptimes have
   537  // lower weights.
   538  func TestHostWeightUptimeDifferences3(t *testing.T) {
   539  	if testing.Short() {
   540  		t.SkipNow()
   541  	}
   542  	t.Parallel()
   543  	hdb := bareHostDB()
   544  	hdb.blockHeight = 10000
   545  
   546  	entry := DefaultHostDBEntry
   547  	entry.ScanHistory = skymodules.HostDBScans{
   548  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   549  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   550  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   551  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   552  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   553  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   554  		{Timestamp: time.Now().Add(time.Hour * -80), Success: false},
   555  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   556  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   557  		{Timestamp: time.Now().Add(time.Hour * -20), Success: true},
   558  	}
   559  
   560  	entry2 := entry
   561  	entry2.ScanHistory = skymodules.HostDBScans{
   562  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   563  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   564  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   565  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   566  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   567  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   568  		{Timestamp: time.Now().Add(time.Hour * -80), Success: false},
   569  		{Timestamp: time.Now().Add(time.Hour * -60), Success: false},
   570  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   571  		{Timestamp: time.Now().Add(time.Hour * -20), Success: true},
   572  	}
   573  	w1 := hdb.weightFunc(entry).Score()
   574  	w2 := hdb.weightFunc(entry2).Score()
   575  
   576  	if w1.Cmp(w2) <= 0 {
   577  		t.Log(w1)
   578  		t.Log(w2)
   579  		t.Error("A host with longer downtime should have a lower score")
   580  	}
   581  }
   582  
   583  // TestHostWeightUptimeDifferences4 checks that hosts with poorer uptimes have
   584  // lower weights.
   585  func TestHostWeightUptimeDifferences4(t *testing.T) {
   586  	if testing.Short() {
   587  		t.SkipNow()
   588  	}
   589  	t.Parallel()
   590  	hdb := bareHostDB()
   591  	hdb.blockHeight = 10000
   592  
   593  	entry := DefaultHostDBEntry
   594  	entry.ScanHistory = skymodules.HostDBScans{
   595  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   596  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   597  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   598  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   599  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   600  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   601  		{Timestamp: time.Now().Add(time.Hour * -80), Success: true},
   602  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   603  		{Timestamp: time.Now().Add(time.Hour * -40), Success: true},
   604  		{Timestamp: time.Now().Add(time.Hour * -20), Success: false},
   605  	}
   606  
   607  	entry2 := entry
   608  	entry2.ScanHistory = skymodules.HostDBScans{
   609  		{Timestamp: time.Now().Add(time.Hour * -200), Success: true},
   610  		{Timestamp: time.Now().Add(time.Hour * -180), Success: true},
   611  		{Timestamp: time.Now().Add(time.Hour * -160), Success: true},
   612  		{Timestamp: time.Now().Add(time.Hour * -140), Success: true},
   613  		{Timestamp: time.Now().Add(time.Hour * -120), Success: true},
   614  		{Timestamp: time.Now().Add(time.Hour * -100), Success: true},
   615  		{Timestamp: time.Now().Add(time.Hour * -80), Success: true},
   616  		{Timestamp: time.Now().Add(time.Hour * -60), Success: true},
   617  		{Timestamp: time.Now().Add(time.Hour * -40), Success: false},
   618  		{Timestamp: time.Now().Add(time.Hour * -20), Success: false},
   619  	}
   620  	w1 := hdb.weightFunc(entry).Score()
   621  	w2 := hdb.weightFunc(entry2).Score()
   622  
   623  	if w1.Cmp(w2) <= 0 {
   624  		t.Log(w1)
   625  		t.Log(w2)
   626  		t.Error("longer tail downtime should have a lower score")
   627  	}
   628  }
   629  
   630  // TestHostWeightConstants checks a few relationships between the constants in
   631  // the hostdb.
   632  func TestHostWeightConstants(t *testing.T) {
   633  	// Becaues we no longer use a large base weight, we require that the
   634  	// collateral floor be higher than the price floor, and also that the
   635  	// collateralExponentiationSmall be larger than the
   636  	// priceExponentiationSmall. This protects most hosts from going anywhere
   637  	// near a 0 score.
   638  	if collateralFloor < priceFloor {
   639  		t.Error("Collateral floor should be greater than or equal to price floor")
   640  	}
   641  	if collateralExponentiationSmall < priceExponentiationSmall {
   642  		t.Error("small collateral exponentiation should be larger than small price exponentiation")
   643  	}
   644  
   645  	// Try a few hosts and make sure we always end up with a score that is
   646  	// greater than 1 million.
   647  	weight, err := calculateWeightFromUInt64Price(300, 100)
   648  	if weight.Cmp(types.NewCurrency64(1e9)) < 0 {
   649  		t.Error("weight is not sufficiently high for hosts")
   650  	}
   651  	if err != nil {
   652  		t.Fatal(err)
   653  	}
   654  	weight, err = calculateWeightFromUInt64Price(1000, 1)
   655  	if weight.Cmp(types.NewCurrency64(1e9)) < 0 {
   656  		t.Error("weight is not sufficiently high for hosts")
   657  	}
   658  	if err != nil {
   659  		t.Fatal(err)
   660  	}
   661  
   662  	hdb := bareHostDB()
   663  	err = hdb.SetAllowance(DefaultTestAllowance)
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	hdb.blockHeight = 0
   668  
   669  	entry := DefaultHostDBEntry
   670  	weight = hdb.weightFunc(entry).Score()
   671  	if weight.Cmp(types.NewCurrency64(1e9)) < 0 {
   672  		t.Error("weight is not sufficiently high for hosts")
   673  	}
   674  }
   675  
   676  // TestHostWeightExtraPriceAdjustment tests the affects of changing
   677  // BaseRPCPrice and SectorAccessPrice on the score.
   678  func TestHostWeightExtraPriceAdjustments(t *testing.T) {
   679  	if testing.Short() {
   680  		t.SkipNow()
   681  	}
   682  	t.Parallel()
   683  	hdb := bareHostDB()
   684  
   685  	allowance := DefaultTestAllowance
   686  	err := hdb.SetAllowance(allowance)
   687  	if err != nil {
   688  		t.Fatal(err)
   689  	}
   690  	entry := DefaultHostDBEntry
   691  	defaultScore := hdb.weightFunc(entry).Score()
   692  
   693  	// Increasing Base RPC Price should decrease the score.
   694  	entry.BaseRPCPrice = DefaultHostDBEntry.BaseRPCPrice.Mul64(2)
   695  	higherBasePrice := hdb.weightFunc(entry).Score()
   696  	if defaultScore.Cmp(higherBasePrice) <= 0 {
   697  		t.Fatal("Expected score decrease with higher base price.")
   698  	}
   699  
   700  	// Increasing Base RPC Price should decrease the score.
   701  	entry.BaseRPCPrice = DefaultHostDBEntry.BaseRPCPrice.Mul64(10)
   702  	highestBasePrice := hdb.weightFunc(entry).Score()
   703  	if higherBasePrice.Cmp(highestBasePrice) <= 0 {
   704  		t.Fatal("Expected score decrease with higher base price.")
   705  	}
   706  
   707  	// Increasing SectorAccessPrice should decrease the score.
   708  	entry = DefaultHostDBEntry // reset entry
   709  	entry.SectorAccessPrice = DefaultHostDBEntry.SectorAccessPrice.Mul64(2)
   710  	higherSectorPrice := hdb.weightFunc(entry).Score()
   711  	if defaultScore.Cmp(higherSectorPrice) <= 0 {
   712  		t.Fatal("Expected score decrease with higher sector access price")
   713  	}
   714  
   715  	// Increasing SectorAccessPrice should decrease the score.
   716  	entry.SectorAccessPrice = DefaultHostDBEntry.SectorAccessPrice.Mul64(10)
   717  	highestSectorPrice := hdb.weightFunc(entry).Score()
   718  	if higherSectorPrice.Cmp(highestSectorPrice) <= 0 {
   719  		t.Fatal("Expected score decrease with higher sector access price")
   720  	}
   721  }
   722  
   723  // TestHostWeightAcceptContract checks that the host that doesn't accept
   724  // contracts has a worse score than the one that does.
   725  func TestHostWeightAcceptContract(t *testing.T) {
   726  	if testing.Short() {
   727  		t.SkipNow()
   728  	}
   729  	t.Parallel()
   730  	hdb := bareHostDB()
   731  	err := hdb.SetAllowance(DefaultTestAllowance)
   732  	if err != nil {
   733  		t.Fatal(err)
   734  	}
   735  
   736  	entry := DefaultHostDBEntry
   737  	entry2 := DefaultHostDBEntry
   738  	entry2.AcceptingContracts = false
   739  
   740  	// Entry2 is not accepting contracts. Should have smallest weight possible.
   741  	entry2.MaxDuration--
   742  	w1 := hdb.weightFunc(entry).Score()
   743  	w2 := hdb.weightFunc(entry2).Score()
   744  	if w1.Cmp(w2) <= 0 {
   745  		t.Error("Entry2 should have smaller weight", w1, w2)
   746  	}
   747  	if w2.Cmp64(1) != 0 {
   748  		t.Error("Entry2 should have smallest weight")
   749  	}
   750  }
   751  
   752  // TestMaxPriceAdjustments tests that hosts which are not below the max prices
   753  // specified in the allowance are considered bad.
   754  func TestMaxPriceAdjustments(t *testing.T) {
   755  	t.Parallel()
   756  
   757  	hdb := bareHostDB()
   758  	hdb.allowance = DefaultTestAllowance
   759  	entry := DefaultHostDBEntry
   760  
   761  	// Score should be greater than the smallest score.
   762  	score := hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   763  	if score <= math.SmallestNonzeroFloat64 {
   764  		t.Fatal("false")
   765  	}
   766  
   767  	hdb.allowance.MaxRPCPrice = entry.BaseRPCPrice.Sub64(1)
   768  	score = hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   769  	if score != math.SmallestNonzeroFloat64 {
   770  		t.Fatal("false")
   771  	}
   772  	hdb.allowance = DefaultTestAllowance
   773  
   774  	hdb.allowance.MaxContractPrice = entry.ContractPrice.Sub64(1)
   775  	score = hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   776  	if score != math.SmallestNonzeroFloat64 {
   777  		t.Fatal("false")
   778  	}
   779  	hdb.allowance = DefaultTestAllowance
   780  
   781  	hdb.allowance.MaxSectorAccessPrice = entry.SectorAccessPrice.MulTax().Sub64(1)
   782  	score = hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   783  	if score != math.SmallestNonzeroFloat64 {
   784  		t.Fatal("false")
   785  	}
   786  	hdb.allowance = DefaultTestAllowance
   787  
   788  	hdb.allowance.MaxDownloadBandwidthPrice = entry.DownloadBandwidthPrice.Sub64(1)
   789  	score = hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   790  	if score != math.SmallestNonzeroFloat64 {
   791  		t.Fatal("false")
   792  	}
   793  	hdb.allowance = DefaultTestAllowance
   794  
   795  	hdb.allowance.MaxUploadBandwidthPrice = entry.UploadBandwidthPrice.Sub64(1)
   796  	score = hdb.priceAdjustments(entry, hdb.allowance, types.ZeroCurrency)
   797  	if score != math.SmallestNonzeroFloat64 {
   798  		t.Fatal("false")
   799  	}
   800  	hdb.allowance = DefaultTestAllowance
   801  }