gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/gouging/gouging_test.go (about)

     1  package gouging
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  	"time"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/SkynetLabs/skyd/skymodules"
    10  	"go.sia.tech/siad/modules"
    11  	"go.sia.tech/siad/types"
    12  )
    13  
    14  // TestCheckDownload checks that the fetch backups price gouging
    15  // checker is correctly detecting price gouging from a host.
    16  func TestCheckDownload(t *testing.T) {
    17  	oneCurrency := types.NewCurrency64(1)
    18  
    19  	// minAllowance contains only the fields necessary to test the price gouging
    20  	// function. The min allowance doesn't include any of the max prices,
    21  	// because the function will ignore them if they are not set.
    22  	minAllowance := skymodules.Allowance{
    23  		// Funds is set such that the tests come out to an easy, round number.
    24  		// One siacoin is multiplied by the number of elements that are checked
    25  		// for gouging, and then divided by the gounging denominator.
    26  		Funds: types.SiacoinPrecision.Mul64(2).Add(oneCurrency).Div64(downloadGougingFractionDenom).Sub(oneCurrency),
    27  
    28  		ExpectedDownload: skymodules.StreamDownloadSize, // 1 stream download operation.
    29  	}
    30  	// minHostSettings contains only the fields necessary to test the price
    31  	// gouging function.
    32  	//
    33  	// The cost is set to be exactly equal to the price gouging limit, such that
    34  	// slightly decreasing any of the values evades the price gouging detector.
    35  	minPriceTable := modules.RPCPriceTable{
    36  		ReadBaseCost:          types.SiacoinPrecision,
    37  		ReadLengthCost:        oneCurrency,
    38  		DownloadBandwidthCost: types.SiacoinPrecision.Div64(skymodules.StreamDownloadSize),
    39  	}
    40  
    41  	err := CheckDownload(minAllowance, minPriceTable)
    42  	if err == nil {
    43  		t.Fatal("expecting price gouging check to fail:", err)
    44  	}
    45  
    46  	// Drop the host prices one field at a time.
    47  	newPriceTable := minPriceTable
    48  	newPriceTable.ReadBaseCost = minPriceTable.ReadBaseCost.Mul64(100).Div64(101)
    49  	err = CheckDownload(minAllowance, newPriceTable)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	newPriceTable = minPriceTable
    54  	newPriceTable.DownloadBandwidthCost = minPriceTable.DownloadBandwidthCost.Mul64(100).Div64(101)
    55  	err = CheckDownload(minAllowance, newPriceTable)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	// Set min settings on the allowance that are just below what should be
    61  	// acceptable.
    62  	maxAllowance := minAllowance
    63  	maxAllowance.Funds = maxAllowance.Funds.Add(oneCurrency)
    64  	maxAllowance.MaxRPCPrice = modules.MDMReadCost(&minPriceTable, skymodules.StreamDownloadSize).Add(oneCurrency)
    65  	maxAllowance.MaxContractPrice = oneCurrency
    66  	maxAllowance.MaxDownloadBandwidthPrice = minPriceTable.DownloadBandwidthCost.Add(oneCurrency)
    67  	maxAllowance.MaxStoragePrice = oneCurrency
    68  	maxAllowance.MaxUploadBandwidthPrice = oneCurrency
    69  
    70  	// The max allowance should have no issues with price gouging.
    71  	err = CheckDownload(maxAllowance, minPriceTable)
    72  	if err != nil {
    73  		t.Fatal(err)
    74  	}
    75  
    76  	// Should fail if the MaxRPCPrice is dropped.
    77  	failAllowance := maxAllowance
    78  	failAllowance.MaxRPCPrice = oneCurrency
    79  	err = CheckDownload(failAllowance, minPriceTable)
    80  	if err == nil {
    81  		t.Fatal("expecting price gouging check to fail")
    82  	}
    83  
    84  	// Should fail if the MaxDownloadBandwidthPrice is dropped.
    85  	failAllowance = maxAllowance
    86  	failAllowance.MaxDownloadBandwidthPrice = minPriceTable.DownloadBandwidthCost.Sub(oneCurrency)
    87  	err = CheckDownload(failAllowance, minPriceTable)
    88  	if err == nil {
    89  		t.Fatal("expecting price gouging check to fail")
    90  	}
    91  }
    92  
    93  // TestCheckPCWS checks that the PCWS gouging check is triggering at the right
    94  // times.
    95  func TestCheckPCWS(t *testing.T) {
    96  	// Create some defaults to get some intuitive ideas for gouging.
    97  	//
    98  	// 100 workers and 1e9 expected download means ~2e6 HasSector queries will
    99  	// be performed.
   100  	pt := modules.RPCPriceTable{
   101  		InitBaseCost:          types.NewCurrency64(1e3),
   102  		DownloadBandwidthCost: types.NewCurrency64(1e3),
   103  		UploadBandwidthCost:   types.NewCurrency64(1e3),
   104  		HasSectorBaseCost:     types.NewCurrency64(1e6),
   105  	}
   106  	allowance := skymodules.Allowance{
   107  		MaxDownloadBandwidthPrice: types.NewCurrency64(2e3),
   108  		MaxUploadBandwidthPrice:   types.NewCurrency64(2e3),
   109  
   110  		Funds: types.NewCurrency64(1e18),
   111  
   112  		ExpectedDownload: 1e9, // 1 GiB
   113  	}
   114  	numWorkers := 100
   115  	numRoots := 30
   116  
   117  	// Check that the gouging passes for normal values.
   118  	err := CheckPCWS(allowance, pt, numWorkers, numRoots)
   119  	if err != nil {
   120  		t.Error(err)
   121  	}
   122  
   123  	// Check with high init base cost.
   124  	pt.InitBaseCost = types.NewCurrency64(1e12)
   125  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   126  	if err == nil {
   127  		t.Error("bad")
   128  	}
   129  	pt.InitBaseCost = types.NewCurrency64(1e3)
   130  
   131  	// Check with high upload bandwidth cost.
   132  	pt.UploadBandwidthCost = types.NewCurrency64(1e12)
   133  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   134  	if err == nil {
   135  		t.Error("bad")
   136  	}
   137  	pt.UploadBandwidthCost = types.NewCurrency64(1e3)
   138  
   139  	// Check with high download bandwidth cost.
   140  	pt.DownloadBandwidthCost = types.NewCurrency64(1e12)
   141  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   142  	if err == nil {
   143  		t.Error("bad")
   144  	}
   145  	pt.DownloadBandwidthCost = types.NewCurrency64(1e3)
   146  
   147  	// Check with high HasSector cost.
   148  	pt.HasSectorBaseCost = types.NewCurrency64(1e12)
   149  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   150  	if err == nil {
   151  		t.Error("bad")
   152  	}
   153  	pt.HasSectorBaseCost = types.NewCurrency64(1e6)
   154  
   155  	// Check with low MaxDownloadBandwidthPrice.
   156  	allowance.MaxDownloadBandwidthPrice = types.NewCurrency64(100)
   157  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   158  	if err == nil {
   159  		t.Error("bad")
   160  	}
   161  	allowance.MaxDownloadBandwidthPrice = types.NewCurrency64(2e3)
   162  
   163  	// Check with low MaxUploadBandwidthPrice.
   164  	allowance.MaxUploadBandwidthPrice = types.NewCurrency64(100)
   165  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   166  	if err == nil {
   167  		t.Error("bad")
   168  	}
   169  	allowance.MaxUploadBandwidthPrice = types.NewCurrency64(2e3)
   170  
   171  	// Check with reduced funds.
   172  	allowance.Funds = types.NewCurrency64(1e15)
   173  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   174  	if err == nil {
   175  		t.Error("bad")
   176  	}
   177  	allowance.Funds = types.NewCurrency64(1e18)
   178  
   179  	// Check with increased expected download.
   180  	allowance.ExpectedDownload = 1e12
   181  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   182  	if err == nil {
   183  		t.Error("bad")
   184  	}
   185  	allowance.ExpectedDownload = 1e9
   186  
   187  	// Check that the base allowanace still passes. (ensures values have been
   188  	// reset correctly)
   189  	err = CheckPCWS(allowance, pt, numWorkers, numRoots)
   190  	if err != nil {
   191  		t.Error(err)
   192  	}
   193  }
   194  
   195  // TestCheckProjectDownload checks that `CheckProjectDownloadGouging` is
   196  // correctly detecting price gouging from a host.
   197  func TestCheckProjectDownload(t *testing.T) {
   198  	t.Parallel()
   199  
   200  	// allowance contains only the fields necessary to test the price gouging
   201  	hes := modules.DefaultHostExternalSettings()
   202  	allowance := skymodules.Allowance{
   203  		Funds:                     types.SiacoinPrecision.Mul64(1e3),
   204  		MaxDownloadBandwidthPrice: hes.DownloadBandwidthPrice.Mul64(10),
   205  		MaxUploadBandwidthPrice:   hes.UploadBandwidthPrice.Mul64(10),
   206  	}
   207  
   208  	// verify happy case
   209  	pt := newDefaultPriceTable()
   210  	err := CheckProjectDownload(allowance, pt)
   211  	if err != nil {
   212  		t.Fatal("unexpected price gouging failure", err)
   213  	}
   214  
   215  	// verify max download bandwidth price gouging
   216  	pt = newDefaultPriceTable()
   217  	pt.DownloadBandwidthCost = allowance.MaxDownloadBandwidthPrice.Add64(1)
   218  	err = CheckProjectDownload(allowance, pt)
   219  	if err == nil || !strings.Contains(err.Error(), "download bandwidth price") {
   220  		t.Fatalf("expected download bandwidth price gouging error, instead error was '%v'", err)
   221  	}
   222  
   223  	// verify max upload bandwidth price gouging
   224  	pt = newDefaultPriceTable()
   225  	pt.UploadBandwidthCost = allowance.MaxUploadBandwidthPrice.Add64(1)
   226  	err = CheckProjectDownload(allowance, pt)
   227  	if err == nil || !strings.Contains(err.Error(), "upload bandwidth price") {
   228  		t.Fatalf("expected upload bandwidth price gouging error, instead error was '%v'", err)
   229  	}
   230  
   231  	// update the expected download to be non zero and verify the default prices
   232  	allowance.ExpectedDownload = 1 << 30 // 1GiB
   233  	pt = newDefaultPriceTable()
   234  	err = CheckProjectDownload(allowance, pt)
   235  	if err != nil {
   236  		t.Fatal("unexpected price gouging failure", err)
   237  	}
   238  
   239  	// verify gouging of MDM related costs, in order to verify if gouging
   240  	// detection kicks in we need to ensure the cost of executing enough PDBRs
   241  	// to fulfil the expected download exceeds the allowance
   242  
   243  	// we do this by maxing out the upload and bandwidth costs and setting all
   244  	// default cost components to 250 pS, note that this value is arbitrary,
   245  	// setting those costs at 250 pS simply proved to push the price per PDBR
   246  	// just over the allowed limit.
   247  	//
   248  	// Cost breakdown:
   249  	// - cost per PDBR 266.4 mS
   250  	// - total cost to fulfil expected download 4.365 KS
   251  	// - reduced cost after applying downloadGougingFractionDenom: 1.091 KS
   252  	// - exceeding the allowance of 1 KS, which is what we are after
   253  	pt.UploadBandwidthCost = allowance.MaxUploadBandwidthPrice
   254  	pt.DownloadBandwidthCost = allowance.MaxDownloadBandwidthPrice
   255  	pS := types.SiacoinPrecision.MulFloat(1e-12)
   256  	pt.InitBaseCost = pt.InitBaseCost.Add(pS.Mul64(250))
   257  	pt.ReadBaseCost = pt.ReadBaseCost.Add(pS.Mul64(250))
   258  	pt.MemoryTimeCost = pt.MemoryTimeCost.Add(pS.Mul64(250))
   259  	err = CheckProjectDownload(allowance, pt)
   260  	if err == nil || !strings.Contains(err.Error(), "combined PDBR pricing of host yields") {
   261  		t.Fatalf("expected PDBR price gouging error, instead error was '%v'", err)
   262  	}
   263  
   264  	// verify these checks are ignored if the funds are 0
   265  	allowance.Funds = types.ZeroCurrency
   266  	err = CheckProjectDownload(allowance, pt)
   267  	if err != nil {
   268  		t.Fatal("unexpected price gouging failure", err)
   269  	}
   270  
   271  	allowance.Funds = types.SiacoinPrecision.Mul64(1e3) // reset
   272  
   273  	// verify bumping every individual cost component to an insane value results
   274  	// in a price gouging error
   275  	pt = newDefaultPriceTable()
   276  	pt.InitBaseCost = types.SiacoinPrecision.Mul64(100)
   277  	err = CheckProjectDownload(allowance, pt)
   278  	if err == nil || !strings.Contains(err.Error(), "combined PDBR pricing of host yields") {
   279  		t.Fatalf("expected PDBR price gouging error, instead error was '%v'", err)
   280  	}
   281  
   282  	pt = newDefaultPriceTable()
   283  	pt.ReadBaseCost = types.SiacoinPrecision
   284  	err = CheckProjectDownload(allowance, pt)
   285  	if err == nil || !strings.Contains(err.Error(), "combined PDBR pricing of host yields") {
   286  		t.Fatalf("expected PDBR price gouging error, instead error was '%v'", err)
   287  	}
   288  
   289  	pt = newDefaultPriceTable()
   290  	pt.ReadLengthCost = types.SiacoinPrecision
   291  	err = CheckProjectDownload(allowance, pt)
   292  	if err == nil || !strings.Contains(err.Error(), "combined PDBR pricing of host yields") {
   293  		t.Fatalf("expected PDBR price gouging error, instead error was '%v'", err)
   294  	}
   295  
   296  	pt = newDefaultPriceTable()
   297  	pt.MemoryTimeCost = types.SiacoinPrecision
   298  	err = CheckProjectDownload(allowance, pt)
   299  	if err == nil || !strings.Contains(err.Error(), "combined PDBR pricing of host yields") {
   300  		t.Fatalf("expected PDBR price gouging error, instead error was '%v'", err)
   301  	}
   302  }
   303  
   304  // TestCheckSnapshot checks that the download snapshot price gouging checker is
   305  // correctly detecting price gouging from a host.
   306  func TestCheckSnapshot(t *testing.T) {
   307  	hes := modules.DefaultHostExternalSettings()
   308  
   309  	allowance := skymodules.DefaultAllowance
   310  	allowance.Funds = types.SiacoinPrecision.Mul64(1e3)
   311  	allowance.MaxDownloadBandwidthPrice = hes.DownloadBandwidthPrice.Mul64(2)
   312  	allowance.MaxUploadBandwidthPrice = hes.UploadBandwidthPrice.Mul64(2)
   313  	allowance.ExpectedDownload = 1 << 30 // 1GiB
   314  
   315  	priceTable := modules.RPCPriceTable{
   316  		ReadBaseCost:          hes.SectorAccessPrice,
   317  		ReadLengthCost:        types.NewCurrency64(1),
   318  		InitBaseCost:          hes.BaseRPCPrice,
   319  		DownloadBandwidthCost: hes.DownloadBandwidthPrice,
   320  		UploadBandwidthCost:   hes.UploadBandwidthPrice,
   321  	}
   322  
   323  	// verify basic case
   324  	err := CheckSnapshot(allowance, priceTable)
   325  	if err != nil {
   326  		t.Fatal("unexpected failure", err)
   327  	}
   328  
   329  	// verify high init costs
   330  	gougingPriceTable := priceTable
   331  	gougingPriceTable.InitBaseCost = types.SiacoinPrecision
   332  	gougingPriceTable.ReadBaseCost = types.SiacoinPrecision
   333  	err = CheckSnapshot(allowance, gougingPriceTable)
   334  	if err == nil {
   335  		t.Fatal("unexpected outcome", err)
   336  	}
   337  
   338  	// verify DL bandwidth gouging
   339  	gougingPriceTable = priceTable
   340  	gougingPriceTable.DownloadBandwidthCost = allowance.MaxDownloadBandwidthPrice.Mul64(2)
   341  	err = CheckSnapshot(allowance, gougingPriceTable)
   342  	if err == nil {
   343  		t.Fatal("unexpected outcome", err)
   344  	}
   345  }
   346  
   347  // TestCheckSubscription is a unit test for CheckSubscriptionGouging.
   348  func TestCheckSubscription(t *testing.T) {
   349  	// Create a pricetable and allowance which are reasonable.
   350  	a := skymodules.DefaultAllowance
   351  	pt := modules.RPCPriceTable{
   352  		SubscriptionMemoryCost:       types.NewCurrency64(1),
   353  		SubscriptionNotificationCost: types.NewCurrency64(1),
   354  	}
   355  
   356  	// Should work.
   357  	if err := CheckSubscription(a, pt); err != nil {
   358  		t.Fatal(err)
   359  	}
   360  
   361  	// High memory cost shouldn't work.
   362  	ptHighMemCost := pt
   363  	ptHighMemCost.SubscriptionMemoryCost = types.NewCurrency64(2)
   364  	if err := CheckSubscription(a, ptHighMemCost); !errors.Contains(err, errHighSubscriptionMemoryCost) {
   365  		t.Fatal(err)
   366  	}
   367  
   368  	// High notification cost shouldn't work.
   369  	ptHighNotificationCost := pt
   370  	ptHighNotificationCost.SubscriptionNotificationCost = types.NewCurrency64(2)
   371  	if err := CheckSubscription(a, ptHighNotificationCost); !errors.Contains(err, errHighSubscriptionNotificationCost) {
   372  		t.Fatal(err)
   373  	}
   374  
   375  	// High bandwidth cost should be caught by CheckProjectDownloadGouging
   376  	// at the end.
   377  	ptHighBandwidthCost := pt
   378  	ptHighBandwidthCost.DownloadBandwidthCost = types.SiacoinPrecision.Mul64(1e9)
   379  	if err := CheckSubscription(a, ptHighBandwidthCost); err == nil {
   380  		t.Fatal("should fail")
   381  	}
   382  }
   383  
   384  // TestCheckUpload checks that the upload price gouging checker is correctly
   385  // detecting price gouging from a host.
   386  func TestCheckUpload(t *testing.T) {
   387  	oneCurrency := types.NewCurrency64(1)
   388  
   389  	// Check default allowance and default PT. Should work.
   390  	defaultPT := newDefaultPriceTable()
   391  	allowance := skymodules.DefaultAllowance
   392  	err := CheckUpload(allowance, defaultPT)
   393  	if err != nil {
   394  		t.Fatal(err)
   395  	}
   396  
   397  	// minAllowance contains only the fields necessary to test the price gouging
   398  	// function. The min allowance doesn't include any of the max prices,
   399  	// because the function will ignore them if they are not set.
   400  	minAllowance := skymodules.Allowance{
   401  		// Funds is set such that the tests come out to an easy, round number.
   402  		// One siacoin is multiplied by the number of elements that are checked
   403  		// for gouging, and then divided by the gounging denominator.
   404  		Funds:  types.SiacoinPrecision.Mul64(4).Div64(uploadGougingFractionDenom).Sub(oneCurrency),
   405  		Period: 1, // 1 block.
   406  
   407  		ExpectedStorage: modules.SectorSize, // 1 append
   408  
   409  		MaxRPCPrice:          types.SiacoinPrecision,
   410  		MaxSectorAccessPrice: types.SiacoinPrecision,
   411  		MaxStoragePrice:      types.SiacoinPrecision,
   412  	}
   413  
   414  	// Compute the right funds for the minAllowance given the price table.
   415  	tb := modules.NewProgramBuilder(&defaultPT, minAllowance.Period)
   416  	err = tb.AddAppendInstruction(make([]byte, modules.SectorSize), true, 0)
   417  	if err != nil {
   418  		t.Fatal(err)
   419  	}
   420  	cost, _, _ := tb.Cost(true)
   421  	bandwidthCost := modules.MDMBandwidthCost(defaultPT, modules.SectorSize+2920, 2920)
   422  	cost = cost.Add(bandwidthCost)
   423  
   424  	fullCostPerByte := cost.Div64(modules.SectorSize)
   425  	allowanceStorageCost := fullCostPerByte.Mul64(minAllowance.ExpectedStorage)
   426  	minAllowance.Funds = allowanceStorageCost.Div64(uploadGougingFractionDenom)
   427  
   428  	// Check the minAllowance and defaultPT. Should work.
   429  	err = CheckUpload(minAllowance, defaultPT)
   430  	if err != nil {
   431  		t.Fatal(err)
   432  	}
   433  
   434  	// Increase each setting in the price table by a delta to cause
   435  	// the gouging check to fail. The delta is set to gouging denominator to
   436  	// avoid integer rounding errors.
   437  	delta := uint64(uploadGougingFractionDenom)
   438  	failPT := defaultPT
   439  	failPT.InitBaseCost = failPT.InitBaseCost.Add64(delta)
   440  	err = CheckUpload(minAllowance, failPT)
   441  	if err == nil {
   442  		t.Fatal("should fail")
   443  	}
   444  	failPT = defaultPT
   445  	failPT.WriteBaseCost = failPT.WriteBaseCost.Add64(delta)
   446  	err = CheckUpload(minAllowance, failPT)
   447  	if err == nil {
   448  		t.Fatal("should fail")
   449  	}
   450  	failPT = defaultPT
   451  	failPT.WriteStoreCost = failPT.WriteStoreCost.Add64(delta)
   452  	err = CheckUpload(minAllowance, failPT)
   453  	if err == nil {
   454  		t.Fatal("should fail")
   455  	}
   456  	failPT = defaultPT
   457  	failPT.UploadBandwidthCost = failPT.UploadBandwidthCost.Add64(delta)
   458  	err = CheckUpload(minAllowance, failPT)
   459  	if err == nil {
   460  		t.Fatal("should fail")
   461  	}
   462  	failPT = defaultPT
   463  	failPT.DownloadBandwidthCost = failPT.DownloadBandwidthCost.Add64(delta)
   464  	err = CheckUpload(minAllowance, failPT)
   465  	if err == nil {
   466  		t.Fatal("should fail")
   467  	}
   468  
   469  	// minPT is set to exactly the limits in the allowance set by the user.
   470  	// Increasing any of the fields will result in gouging failures.
   471  	minPT := modules.RPCPriceTable{
   472  		MemoryTimeCost:        oneCurrency,
   473  		WriteLengthCost:       oneCurrency,
   474  		InitBaseCost:          minAllowance.MaxRPCPrice,
   475  		WriteBaseCost:         minAllowance.MaxSectorAccessPrice,
   476  		WriteStoreCost:        minAllowance.MaxStoragePrice,
   477  		UploadBandwidthCost:   minAllowance.MaxUploadBandwidthPrice,
   478  		DownloadBandwidthCost: minAllowance.MaxDownloadBandwidthPrice,
   479  	}
   480  
   481  	failPT = minPT
   482  	failPT.MemoryTimeCost = failPT.MemoryTimeCost.Add64(1)
   483  	err = CheckUpload(minAllowance, failPT)
   484  	if err == nil {
   485  		t.Fatal("should fail")
   486  	}
   487  	failPT = minPT
   488  	failPT.WriteLengthCost = failPT.WriteLengthCost.Add64(1)
   489  	err = CheckUpload(minAllowance, failPT)
   490  	if err == nil {
   491  		t.Fatal("should fail")
   492  	}
   493  	failPT = minPT
   494  	failPT.InitBaseCost = failPT.InitBaseCost.Add64(1)
   495  	err = CheckUpload(minAllowance, failPT)
   496  	if err == nil {
   497  		t.Fatal("should fail")
   498  	}
   499  	failPT = minPT
   500  	failPT.WriteBaseCost = failPT.WriteBaseCost.Add64(1)
   501  	err = CheckUpload(minAllowance, failPT)
   502  	if err == nil {
   503  		t.Fatal("should fail")
   504  	}
   505  	failPT = minPT
   506  	failPT.WriteStoreCost = failPT.WriteStoreCost.Add64(1)
   507  	err = CheckUpload(minAllowance, failPT)
   508  	if err == nil {
   509  		t.Fatal("should fail")
   510  	}
   511  	failPT = minPT
   512  	failPT.UploadBandwidthCost = failPT.UploadBandwidthCost.Add64(1)
   513  	err = CheckUpload(minAllowance, failPT)
   514  	if err == nil {
   515  		t.Fatal("should fail")
   516  	}
   517  	failPT = minPT
   518  	failPT.DownloadBandwidthCost = failPT.DownloadBandwidthCost.Add64(1)
   519  	err = CheckUpload(minAllowance, failPT)
   520  	if err == nil {
   521  		t.Fatal("should fail")
   522  	}
   523  
   524  	// Make download gouging check fail once to verify we are calling it.
   525  	failPT = minPT
   526  	failPT.ReadLengthCost = types.NewCurrency64(2)
   527  	err = CheckUpload(minAllowance, failPT)
   528  	if err == nil || !strings.Contains(err.Error(), "failed download gouging check") {
   529  		t.Error("expecting price gouging check to fail")
   530  	}
   531  }
   532  
   533  // TestCheckUploadHeS checks that the upload price gouging checker is
   534  // correctly detecting price gouging from a host.
   535  func TestCheckUploadHeS(t *testing.T) {
   536  	oneCurrency := types.NewCurrency64(1)
   537  
   538  	// minAllowance contains only the fields necessary to test the price gouging
   539  	// function. The min allowance doesn't include any of the max prices,
   540  	// because the function will ignore them if they are not set.
   541  	minAllowance := skymodules.Allowance{
   542  		// Funds is set such that the tests come out to an easy, round number.
   543  		// One siacoin is multiplied by the number of elements that are checked
   544  		// for gouging, and then divided by the gounging denominator.
   545  		Funds:  types.SiacoinPrecision.Mul64(4).Div64(uploadGougingFractionDenom).Sub(oneCurrency),
   546  		Period: 1, // 1 block.
   547  
   548  		ExpectedStorage: skymodules.StreamUploadSize, // 1 stream upload operation.
   549  	}
   550  	// minHostSettings contains only the fields necessary to test the price
   551  	// gouging function.
   552  	//
   553  	// The cost is set to be exactly equal to the price gouging limit, such that
   554  	// slightly decreasing any of the values evades the price gouging detector.
   555  	minHostSettings := modules.HostExternalSettings{
   556  		BaseRPCPrice:         types.SiacoinPrecision,
   557  		SectorAccessPrice:    types.SiacoinPrecision,
   558  		UploadBandwidthPrice: types.SiacoinPrecision.Div64(skymodules.StreamUploadSize),
   559  		StoragePrice:         types.SiacoinPrecision.Div64(skymodules.StreamUploadSize),
   560  	}
   561  
   562  	// minPriceTable has all of the costs set to 0 (except ReadLengthCost)
   563  	// to make sure the download gouging check within the upload gouging
   564  	// check doesn't fail.
   565  	minPriceTable := &modules.RPCPriceTable{
   566  		ReadLengthCost: oneCurrency,
   567  	}
   568  
   569  	err := CheckUploadHES(minAllowance, minPriceTable, minHostSettings, false)
   570  	if err == nil {
   571  		t.Fatal("expecting price gouging check to fail:", err)
   572  	}
   573  
   574  	// Drop the host prices one field at a time.
   575  	newHostSettings := minHostSettings
   576  	newHostSettings.BaseRPCPrice = minHostSettings.BaseRPCPrice.Mul64(100).Div64(101)
   577  	err = CheckUploadHES(minAllowance, minPriceTable, newHostSettings, false)
   578  	if err != nil {
   579  		t.Fatal(err)
   580  	}
   581  	newHostSettings = minHostSettings
   582  	newHostSettings.SectorAccessPrice = minHostSettings.SectorAccessPrice.Mul64(100).Div64(101)
   583  	err = CheckUploadHES(minAllowance, minPriceTable, newHostSettings, false)
   584  	if err != nil {
   585  		t.Fatal(err)
   586  	}
   587  	newHostSettings = minHostSettings
   588  	newHostSettings.UploadBandwidthPrice = minHostSettings.UploadBandwidthPrice.Mul64(100).Div64(101)
   589  	err = CheckUploadHES(minAllowance, minPriceTable, newHostSettings, false)
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	newHostSettings = minHostSettings
   594  	newHostSettings.StoragePrice = minHostSettings.StoragePrice.Mul64(100).Div64(101)
   595  	err = CheckUploadHES(minAllowance, minPriceTable, newHostSettings, false)
   596  	if err != nil {
   597  		t.Fatal(err)
   598  	}
   599  
   600  	// Set min settings on the allowance that are just below that should be
   601  	// acceptable.
   602  	maxAllowance := minAllowance
   603  	maxAllowance.Funds = maxAllowance.Funds.Add(oneCurrency)
   604  	maxAllowance.MaxRPCPrice = types.SiacoinPrecision.Add(oneCurrency)
   605  	maxAllowance.MaxContractPrice = oneCurrency
   606  	maxAllowance.MaxDownloadBandwidthPrice = oneCurrency
   607  	maxAllowance.MaxSectorAccessPrice = types.SiacoinPrecision.Add(oneCurrency)
   608  	maxAllowance.MaxStoragePrice = types.SiacoinPrecision.Div64(skymodules.StreamUploadSize).Add(oneCurrency)
   609  	maxAllowance.MaxUploadBandwidthPrice = types.SiacoinPrecision.Div64(skymodules.StreamUploadSize).Add(oneCurrency)
   610  
   611  	// The max allowance should have no issues with price gouging.
   612  	err = CheckUploadHES(maxAllowance, minPriceTable, minHostSettings, false)
   613  	if err != nil {
   614  		t.Fatal(err)
   615  	}
   616  
   617  	// Should fail if the MaxRPCPrice is dropped.
   618  	failAllowance := maxAllowance
   619  	failAllowance.MaxRPCPrice = types.SiacoinPrecision.Sub(oneCurrency)
   620  	err = CheckUploadHES(failAllowance, minPriceTable, minHostSettings, false)
   621  	if err == nil {
   622  		t.Error("expecting price gouging check to fail")
   623  	}
   624  
   625  	// Should fail if the MaxSectorAccessPrice is dropped.
   626  	failAllowance = maxAllowance
   627  	failAllowance.MaxSectorAccessPrice = types.SiacoinPrecision.Sub(oneCurrency)
   628  	err = CheckUploadHES(failAllowance, minPriceTable, minHostSettings, false)
   629  	if err == nil {
   630  		t.Error("expecting price gouging check to fail")
   631  	}
   632  
   633  	// Should fail if the MaxStoragePrice is dropped.
   634  	failAllowance = maxAllowance
   635  	failAllowance.MaxStoragePrice = types.SiacoinPrecision.Div64(skymodules.StreamUploadSize).Sub(oneCurrency)
   636  	err = CheckUploadHES(failAllowance, minPriceTable, minHostSettings, false)
   637  	if err == nil {
   638  		t.Error("expecting price gouging check to fail")
   639  	}
   640  
   641  	// Should fail if the MaxUploadBandwidthPrice is dropped.
   642  	failAllowance = maxAllowance
   643  	failAllowance.MaxUploadBandwidthPrice = types.SiacoinPrecision.Div64(skymodules.StreamUploadSize).Sub(oneCurrency)
   644  	err = CheckUploadHES(failAllowance, minPriceTable, minHostSettings, false)
   645  	if err == nil {
   646  		t.Error("expecting price gouging check to fail")
   647  	}
   648  
   649  	// Make download gouging check fail once to verify we are calling it.
   650  	failPT := minPriceTable
   651  	failPT.ReadLengthCost = types.NewCurrency64(2)
   652  	failAllowance.MaxSectorAccessPrice = types.SiacoinPrecision.Sub(oneCurrency)
   653  	err = CheckUploadHES(minAllowance, failPT, minHostSettings, false)
   654  	if err == nil || !strings.Contains(err.Error(), "failed download gouging check") {
   655  		t.Error("expecting price gouging check to fail")
   656  	}
   657  
   658  	// Download gouging check can't fail without pricetable.
   659  	newHostSettings = minHostSettings
   660  	newHostSettings.SectorAccessPrice = minHostSettings.SectorAccessPrice.Mul64(100).Div64(101)
   661  	err = CheckUploadHES(minAllowance, nil, newHostSettings, true)
   662  	if err != nil {
   663  		t.Error(err)
   664  	}
   665  
   666  	// Should fail if no pricetable was provided when allowSkipPT is
   667  	// 'false'.
   668  	failPT.ReadLengthCost = types.NewCurrency64(2)
   669  	err = CheckUploadHES(minAllowance, nil, newHostSettings, false)
   670  	if err == nil || !strings.Contains(err.Error(), "pricetable missing") {
   671  		t.Error("expecting price gouging check to fail")
   672  	}
   673  }
   674  
   675  // newDefaultPriceTable is a helper function that returns a price table with
   676  // default prices for all fields
   677  func newDefaultPriceTable() modules.RPCPriceTable {
   678  	hes := modules.DefaultHostExternalSettings()
   679  	oneCurrency := types.NewCurrency64(1)
   680  	return modules.RPCPriceTable{
   681  		Validity:             time.Minute,
   682  		FundAccountCost:      oneCurrency,
   683  		UpdatePriceTableCost: oneCurrency,
   684  
   685  		HasSectorBaseCost: oneCurrency,
   686  		InitBaseCost:      oneCurrency,
   687  		MemoryTimeCost:    oneCurrency,
   688  		ReadBaseCost:      oneCurrency,
   689  		ReadLengthCost:    oneCurrency,
   690  		SwapSectorCost:    oneCurrency,
   691  
   692  		DownloadBandwidthCost: hes.DownloadBandwidthPrice,
   693  		UploadBandwidthCost:   hes.UploadBandwidthPrice,
   694  
   695  		WriteBaseCost:   oneCurrency,
   696  		WriteLengthCost: oneCurrency,
   697  		WriteStoreCost:  oneCurrency,
   698  	}
   699  }