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

     1  package gouging
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"gitlab.com/NebulousLabs/errors"
     7  	"gitlab.com/SkynetLabs/skyd/skymodules"
     8  	"go.sia.tech/siad/crypto"
     9  	"go.sia.tech/siad/modules"
    10  	"go.sia.tech/siad/types"
    11  )
    12  
    13  const (
    14  	// downloadGougingFractionDenom sets the fraction to 1/4 because the renter
    15  	// should have enough money to download at least a fraction of the amount of
    16  	// data they intend to download. In practice, this ends up being a farily
    17  	// weak gouging filter because a massive portion of the allowance tends to
    18  	// be assigned to storage, and this does not account for that.
    19  	downloadGougingFractionDenom = 4
    20  
    21  	// pcwsGougingFractionDenom is used to identify what percentage of the
    22  	// allowance is allowed to be spent on HasSector jobs before a worker is
    23  	// flagged for being too expensive.
    24  	//
    25  	// For example, if the denom is 10, that means that if a worker's HasSector
    26  	// cost multiplied by the total expected number of HasSector jobs to be
    27  	// performed in a period exceeds 10% of the allowance, that worker will be
    28  	// flagged for price gouging. If the denom is 100, the worker will be
    29  	// flagged if the HasSector cost reaches 1% of the total cost of the
    30  	// allowance.
    31  	pcwsGougingFractionDenom = 25
    32  
    33  	// sectorLookupToDownloadRatio is an arbitrary ratio that resembles the
    34  	// amount of lookups vs downloads. It is used in price gouging checks.
    35  	sectorLookupToDownloadRatio = 16
    36  
    37  	// snapshotDownloadGougingFractionDenom sets the fraction to 1/100 because
    38  	// downloading snapshots is important, so there is less sensitivity to
    39  	// gouging. Also, this is a rare operation.
    40  	snapshotDownloadGougingFractionDenom = 100
    41  
    42  	// uploadGougingFractionDenom sets the gouging fraction to 1/4 based on the
    43  	// idea that the user should be able to hit at least some fraction of their
    44  	// desired upload volume using some fraction of hosts.
    45  	uploadGougingFractionDenom = 4
    46  )
    47  
    48  var (
    49  	// errHighSubscriptionMemoryCost is returned if the subscription gouging
    50  	// check fails due to a high memory cost.
    51  	errHighSubscriptionMemoryCost = errors.New("high SubscriptionMemoryCost")
    52  
    53  	// errHighSubscriptionNotificationCost is returned if the subscription
    54  	// gouging check fails due to a high notification cost.
    55  	errHighSubscriptionNotificationCost = errors.New("high SubscriptionNotificationCost")
    56  )
    57  
    58  // CheckDownload looks at the current renter allowance and the active settings
    59  // for a host and determines whether a backup fetch should be halted due to
    60  // price gouging.
    61  //
    62  // NOTE: Currently this function treats all downloads being the stream download
    63  // size and assumes that data is actually being appended to the host. As the
    64  // worker gains more modification actions on the host, this check can be split
    65  // into different checks that vary based on the operation being performed.
    66  func CheckDownload(allowance skymodules.Allowance, pt modules.RPCPriceTable) error {
    67  	// Check whether the base RPC price is too high.
    68  	rpcCost := modules.MDMReadCost(&pt, skymodules.StreamDownloadSize)
    69  	if !allowance.MaxRPCPrice.IsZero() && allowance.MaxRPCPrice.Cmp(rpcCost) < 0 {
    70  		errStr := fmt.Sprintf("rpc price of host is %v, which is above the maximum allowed by the allowance: %v", rpcCost, allowance.MaxRPCPrice)
    71  		return errors.New(errStr)
    72  	}
    73  
    74  	// Check whether the download bandwidth price is too high.
    75  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(pt.DownloadBandwidthCost) < 0 {
    76  		errStr := fmt.Sprintf("download bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v", pt.DownloadBandwidthCost, allowance.MaxDownloadBandwidthPrice)
    77  		return errors.New(errStr)
    78  	}
    79  
    80  	// Check ReadLengthCost. This is unused by hosts right now and should be set to 1H.
    81  	if types.NewCurrency64(1).Cmp(pt.ReadLengthCost) < 0 {
    82  		errStr := fmt.Sprintf("ReadLengthCost of host is %v but should be %v", pt.ReadLengthCost, types.NewCurrency64(1))
    83  		return errors.New(errStr)
    84  	}
    85  
    86  	// If there is no allowance, general price gouging checks have to be
    87  	// disabled, because there is no baseline for understanding what might count
    88  	// as price gouging.
    89  	if allowance.Funds.IsZero() {
    90  		return nil
    91  	}
    92  
    93  	// Make sure the expected download is at least 1 to avoid multiplying
    94  	// with 0 later.
    95  	expectedDownload := allowance.ExpectedDownload
    96  	if expectedDownload == 0 {
    97  		expectedDownload++
    98  	}
    99  
   100  	// Check that the combined prices make sense in the context of the overall
   101  	// allowance. The general idea is to compute the total cost of performing
   102  	// the same action repeatedly until a fraction of the desired total resource
   103  	// consumption established by the allowance has been reached. The fraction
   104  	// is determined on a case-by-case basis. If the host is too expensive to
   105  	// even satisfy a faction of the user's total desired resource consumption,
   106  	// the action will be blocked for price gouging.
   107  	singleDownloadCost := rpcCost.Add(pt.DownloadBandwidthCost.Mul64(skymodules.StreamDownloadSize))
   108  	fullCostPerByte := singleDownloadCost.Div64(skymodules.StreamDownloadSize)
   109  	allowanceDownloadCost := fullCostPerByte.Mul64(allowance.ExpectedDownload)
   110  	reducedCost := allowanceDownloadCost.Div64(downloadGougingFractionDenom)
   111  	if reducedCost.Cmp(allowance.Funds) > 0 {
   112  		errStr := fmt.Sprintf("combined download pricing of host yields %v, which is more than the renter is willing to pay for the download: %v - price gouging protection enabled", reducedCost, allowance.Funds)
   113  		return errors.New(errStr)
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // CheckPCWS verifies the cost of grabbing the HasSector information from a host
   120  // is reasonble. The cost of completing the download is not checked.
   121  //
   122  // NOTE: The logic in this function assumes that every pcws results in just one
   123  // download. The reality is that depending on the type of use case, there may be
   124  // significantly less than 1 download per pcws (for single-user nodes that
   125  // frequently open large movies without watching the full movie), or
   126  // significantly more than one download per pcws (for multi-user nodes where
   127  // users most commonly are using the same file over and over).
   128  func CheckPCWS(allowance skymodules.Allowance, pt modules.RPCPriceTable, numWorkers int, numRoots int) error {
   129  	// Check whether the download bandwidth price is too high.
   130  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(pt.DownloadBandwidthCost) < 0 {
   131  		return fmt.Errorf("download bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v - price gouging protection enabled", pt.DownloadBandwidthCost, allowance.MaxDownloadBandwidthPrice)
   132  	}
   133  	// Check whether the upload bandwidth price is too high.
   134  	if !allowance.MaxUploadBandwidthPrice.IsZero() && allowance.MaxUploadBandwidthPrice.Cmp(pt.UploadBandwidthCost) < 0 {
   135  		return fmt.Errorf("upload bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v - price gouging protection enabled", pt.UploadBandwidthCost, allowance.MaxUploadBandwidthPrice)
   136  	}
   137  	// If there is no allowance, price gouging checks have to be disabled,
   138  	// because there is no baseline for understanding what might count as price
   139  	// gouging.
   140  	if allowance.Funds.IsZero() {
   141  		return nil
   142  	}
   143  
   144  	// Calculate the cost of a has sector job.
   145  	pb := modules.NewProgramBuilder(&pt, 0)
   146  	for i := 0; i < numRoots; i++ {
   147  		pb.AddHasSectorInstruction(crypto.Hash{})
   148  	}
   149  	programCost, _, _ := pb.Cost(true)
   150  	ulbw, dlbw := HasSectorJobExpectedBandwidth(numRoots)
   151  	bandwidthCost := modules.MDMBandwidthCost(pt, ulbw, dlbw)
   152  	costHasSectorJob := programCost.Add(bandwidthCost)
   153  
   154  	// Determine based on the allowance the number of HasSector jobs that would
   155  	// need to be performed under normal conditions to reach the desired amount
   156  	// of total data.
   157  	requiredProjects := allowance.ExpectedDownload / skymodules.StreamDownloadSize
   158  	requiredHasSectorQueries := requiredProjects * uint64(numWorkers)
   159  
   160  	// Determine the total amount that we'd be willing to spend on all of those
   161  	// queries before considering the host complicit in gouging.
   162  	totalCost := costHasSectorJob.Mul64(requiredHasSectorQueries)
   163  	reducedAllowance := allowance.Funds.Div64(pcwsGougingFractionDenom)
   164  
   165  	// Check that we do not consider the host complicit in gouging.
   166  	if totalCost.Cmp(reducedAllowance) > 0 {
   167  		return errors.New("the cost of performing a HasSector job is too high - price gouging protection enabled")
   168  	}
   169  	return nil
   170  }
   171  
   172  // CheckProjectDownload verifies the cost of executing the jobs performed by the
   173  // project download are reasonable in relation to the user's allowance and the
   174  // amount of data they intend to download
   175  func CheckProjectDownload(allowance skymodules.Allowance, pt modules.RPCPriceTable) error {
   176  	// Check whether the download bandwidth price is too high.
   177  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(pt.DownloadBandwidthCost) < 0 {
   178  		return fmt.Errorf("download bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v - price gouging protection enabled", pt.DownloadBandwidthCost, allowance.MaxDownloadBandwidthPrice)
   179  	}
   180  
   181  	// Check whether the upload bandwidth price is too high.
   182  	if !allowance.MaxUploadBandwidthPrice.IsZero() && allowance.MaxUploadBandwidthPrice.Cmp(pt.UploadBandwidthCost) < 0 {
   183  		return fmt.Errorf("upload bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v - price gouging protection enabled", pt.UploadBandwidthCost, allowance.MaxUploadBandwidthPrice)
   184  	}
   185  
   186  	// If there is no allowance, price gouging checks have to be disabled,
   187  	// because there is no baseline for understanding what might count as price
   188  	// gouging.
   189  	if allowance.Funds.IsZero() {
   190  		return nil
   191  	}
   192  
   193  	// In order to decide whether or not the cost of performing a PDBR is too
   194  	// expensive, we make some assumptions with regards to lookup vs download
   195  	// job ratio and avg download size. The total cost is then compared in
   196  	// relation to the allowance, where we verify that a fraction of the cost
   197  	// (which we'll call reduced cost) to download the amount of data the user
   198  	// intends to download does not exceed its allowance.
   199  
   200  	// Calculate the cost of a has sector job
   201  	pb := modules.NewProgramBuilder(&pt, 0)
   202  	pb.AddHasSectorInstruction(crypto.Hash{})
   203  	programCost, _, _ := pb.Cost(true)
   204  
   205  	ulbw, dlbw := HasSectorJobExpectedBandwidth(1)
   206  	bandwidthCost := modules.MDMBandwidthCost(pt, ulbw, dlbw)
   207  	costHasSectorJob := programCost.Add(bandwidthCost)
   208  
   209  	// Calculate the cost of a read sector job, we use StreamDownloadSize as an
   210  	// average download size here which is 64 KiB.
   211  	pb = modules.NewProgramBuilder(&pt, 0)
   212  	pb.AddReadSectorInstruction(skymodules.StreamDownloadSize, 0, crypto.Hash{}, true)
   213  	programCost, _, _ = pb.Cost(true)
   214  
   215  	ulbw, dlbw = ReadSectorJobExpectedBandwidth(skymodules.StreamDownloadSize)
   216  	bandwidthCost = modules.MDMBandwidthCost(pt, ulbw, dlbw)
   217  	costReadSectorJob := programCost.Add(bandwidthCost)
   218  
   219  	// Calculate the cost of a project
   220  	costProject := costReadSectorJob.Add(costHasSectorJob.Mul64(uint64(sectorLookupToDownloadRatio)))
   221  
   222  	// Now that we have the cost of each job, and we estimate a sector lookup to
   223  	// download ratio of 16, all we need to do is calculate the number of
   224  	// projects necessary to download the expected download amount.
   225  	numProjects := allowance.ExpectedDownload / skymodules.StreamDownloadSize
   226  
   227  	// The cost of downloading is considered too expensive if the allowance is
   228  	// insufficient to cover a fraction of the expense to download the amount of
   229  	// data the user intends to download
   230  	totalCost := costProject.Mul64(numProjects)
   231  	reducedCost := totalCost.Div64(downloadGougingFractionDenom)
   232  	if reducedCost.Cmp(allowance.Funds) > 0 {
   233  		return fmt.Errorf("combined PDBR pricing of host yields %v, which is more than the renter is willing to pay for downloads: %v - price gouging protection enabled", reducedCost, allowance.Funds)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  // CheckSnapshot looks at the current renter allowance and the active settings
   240  // for a host and determines whether a snapshot upload should be halted due to
   241  // price gouging.
   242  func CheckSnapshot(allowance skymodules.Allowance, pt modules.RPCPriceTable) error {
   243  	// Check whether the download bandwidth price is too high.
   244  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(pt.DownloadBandwidthCost) < 0 {
   245  		errStr := fmt.Sprintf("download bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v", pt.DownloadBandwidthCost, allowance.MaxDownloadBandwidthPrice)
   246  		return errors.New(errStr)
   247  	}
   248  
   249  	// If there is no allowance, general price gouging checks have to be
   250  	// disabled, because there is no baseline for understanding what might count
   251  	// as price gouging.
   252  	if allowance.Funds.IsZero() {
   253  		return nil
   254  	}
   255  
   256  	// Check that the combined prices make sense in the context of the overall
   257  	// allowance. The general idea is to compute the total cost of performing
   258  	// the same action repeatedly until a fraction of the desired total resource
   259  	// consumption established by the allowance has been reached. The fraction
   260  	// is determined on a case-by-case basis. If the host is too expensive to
   261  	// even satisfy a faction of the user's total desired resource consumption,
   262  	// the action will be blocked for price gouging.
   263  	expectedDL := modules.SectorSize
   264  	rpcCost := modules.MDMInitCost(&pt, 48, 1).Add(modules.MDMReadCost(&pt, expectedDL)) // 48 bytes is the length of a single instruction read program
   265  	bandwidthCost := pt.DownloadBandwidthCost.Mul64(expectedDL)
   266  	fullCostPerByte := rpcCost.Add(bandwidthCost).Div64(expectedDL)
   267  	allowanceDownloadCost := fullCostPerByte.Mul64(allowance.ExpectedDownload)
   268  	reducedCost := allowanceDownloadCost.Div64(snapshotDownloadGougingFractionDenom)
   269  	if reducedCost.Cmp(allowance.Funds) > 0 {
   270  		errStr := fmt.Sprintf("combined download snapshot pricing of host yields %v, which is more than the renter is willing to pay for storage: %v - price gouging protection enabled", reducedCost, allowance.Funds)
   271  		return errors.New(errStr)
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  // CheckSubscription checks that the host has reasonable prices set for
   278  // subscribing to entries.
   279  func CheckSubscription(allowance skymodules.Allowance, pt modules.RPCPriceTable) error {
   280  	// Check the subscription related costs. These are hardcoded to 1 in the
   281  	// host right now so we can just assume that they will always be 1. Once
   282  	// they are configurable in the host, we can update this.
   283  	if !pt.SubscriptionMemoryCost.Equals(types.NewCurrency64(1)) {
   284  		return errors.AddContext(errHighSubscriptionMemoryCost, fmt.Sprintf("%v != 1", pt.SubscriptionMemoryCost))
   285  	}
   286  	if !pt.SubscriptionNotificationCost.Equals(types.NewCurrency64(1)) {
   287  		return errors.AddContext(errHighSubscriptionNotificationCost, fmt.Sprintf("%v != 1", pt.SubscriptionMemoryCost))
   288  	}
   289  	// Use the download gouging check to make sure the bandwidth cost is
   290  	// reasonable.
   291  	return CheckProjectDownload(allowance, pt)
   292  }
   293  
   294  // CheckUpload looks at the current renter allowance and the active
   295  // price table for a host and determines whether an upload should be halted due
   296  // to price gouging.
   297  func CheckUpload(allowance skymodules.Allowance, pt modules.RPCPriceTable) error {
   298  	// Check for download gouging first. If we can't download the chunk
   299  	// there is no point in uploading it either.
   300  	if err := CheckDownload(allowance, pt); err != nil {
   301  		return errors.AddContext(err, "checkUploadGougingPT: failed download gouging check")
   302  	}
   303  	// Check MemoryTimeCost. This is unused by hosts right now and should be set to 1H.
   304  	if types.NewCurrency64(1).Cmp(pt.MemoryTimeCost) < 0 {
   305  		errStr := fmt.Sprintf("MemoryTimeCost of host is %v, which is above the maximum allowed by the allowance: %v", pt.MemoryTimeCost, types.NewCurrency64(1))
   306  		return errors.New(errStr)
   307  	}
   308  	// Check WriteLengthCost. This is unused by hosts right now and should be set to 1H.
   309  	if types.NewCurrency64(1).Cmp(pt.WriteLengthCost) < 0 {
   310  		errStr := fmt.Sprintf("WriteLengthCost of host is %v, which is above the maximum allowed by the allowance: %v", pt.WriteLengthCost, types.NewCurrency64(1))
   311  		return errors.New(errStr)
   312  	}
   313  	// Check InitBaseCost. This equals the BaseRPCPrice in the host settings.
   314  	if !allowance.MaxRPCPrice.IsZero() && allowance.MaxRPCPrice.Cmp(pt.InitBaseCost) < 0 {
   315  		errStr := fmt.Sprintf("InitBaseCost of host is %v, which is above the maximum allowed by the allowance: %v", pt.InitBaseCost, allowance.MaxRPCPrice)
   316  		return errors.New(errStr)
   317  	}
   318  	// Check WriteBaseCost. This equals the SectorAccessPrice in the host settings.
   319  	if !allowance.MaxSectorAccessPrice.IsZero() && allowance.MaxSectorAccessPrice.Cmp(pt.WriteBaseCost) < 0 {
   320  		errStr := fmt.Sprintf("WriteBaseCost of host is %v, which is above the maximum allowed by the allowance: %v", pt.WriteBaseCost, allowance.MaxSectorAccessPrice)
   321  		return errors.New(errStr)
   322  	}
   323  	// Check WriteStoreCost. This equals the StoragePrice in the host settings.
   324  	if !allowance.MaxStoragePrice.IsZero() && allowance.MaxStoragePrice.Cmp(pt.WriteStoreCost) < 0 {
   325  		errStr := fmt.Sprintf("WriteStoreCost of host is %v, which is above the maximum allowed by the allowance: %v", pt.WriteStoreCost, allowance.MaxStoragePrice)
   326  		return errors.New(errStr)
   327  	}
   328  	// Check whether the upload bandwidth price is too high.
   329  	if !allowance.MaxUploadBandwidthPrice.IsZero() && allowance.MaxUploadBandwidthPrice.Cmp(pt.UploadBandwidthCost) < 0 {
   330  		errStr := fmt.Sprintf("UploadBandwidthPrice price of host is %v, which is above the maximum allowed by the allowance: %v", pt.UploadBandwidthCost, allowance.MaxUploadBandwidthPrice)
   331  		return errors.New(errStr)
   332  	}
   333  	// Check whether the download bandwidth price is too high.
   334  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(pt.DownloadBandwidthCost) < 0 {
   335  		errStr := fmt.Sprintf("DownloadBandwidthPrice price of host is %v, which is above the maximum allowed by the allowance: %v", pt.DownloadBandwidthCost, allowance.MaxDownloadBandwidthPrice)
   336  		return errors.New(errStr)
   337  	}
   338  
   339  	// If there is no allowance, general price gouging checks have to be
   340  	// disabled, because there is no baseline for understanding what might count
   341  	// as price gouging.
   342  	if allowance.Funds.IsZero() {
   343  		return nil
   344  	}
   345  
   346  	// Cost of initializing the MDM.
   347  	cost := modules.MDMInitCost(&pt, modules.SectorSize, 1)
   348  
   349  	// Cost of executing a single sector append.
   350  	memory := modules.MDMInitMemory() + modules.MDMAppendMemory()
   351  	cost = cost.Add(modules.MDMMemoryCost(&pt, memory, modules.MDMTimeAppend))
   352  
   353  	// Finalize the program.
   354  	cost = cost.Add(modules.MDMMemoryCost(&pt, memory, modules.MDMTimeCommit))
   355  
   356  	// Cost of storage and bandwidth.
   357  	storageCost, _ := modules.MDMAppendCost(&pt, allowance.Period)
   358  	bandwidthCost := modules.MDMBandwidthCost(pt, modules.SectorSize+2920, 2920) // assume sector data + 2 packets of overhead
   359  	cost = cost.Add(storageCost).Add(bandwidthCost)
   360  
   361  	// Cost of full upload.
   362  	allowanceStorageCost := cost.Mul64(allowance.ExpectedStorage).Div64(modules.SectorSize)
   363  	reducedCost := allowanceStorageCost.Div64(uploadGougingFractionDenom)
   364  	if reducedCost.Cmp(allowance.Funds) > 0 {
   365  		errStr := fmt.Sprintf("combined upload pricing of host yields %v, which is more than the renter is willing to pay for storage: %v - price gouging protection enabled", reducedCost, allowance.Funds)
   366  		return errors.New(errStr)
   367  	}
   368  	return nil
   369  }
   370  
   371  // CheckUploadHES looks at the current renter allowance and the active settings
   372  // for a host and determines whether an upload should be halted due to price
   373  // gouging.
   374  //
   375  // NOTE: Currently this function treats all uploads as being the stream upload
   376  // size and assumes that data is actually being appended to the host. As the
   377  // worker gains more modification actions on the host, this check can be split
   378  // into different checks that vary based on the operation being performed.
   379  func CheckUploadHES(allowance skymodules.Allowance, pt *modules.RPCPriceTable, hostSettings modules.HostExternalSettings, allowSkipPT bool) error {
   380  	// Check for download gouging first. If we can't download the chunk
   381  	// there is no point in uploading it either.
   382  	if pt == nil && !allowSkipPT {
   383  		return errors.New("pricetable missing")
   384  	} else if pt != nil {
   385  		if err := CheckDownload(allowance, *pt); err != nil {
   386  			return errors.AddContext(err, "checkUploadGouging: failed download gouging check")
   387  		}
   388  	}
   389  	// Check whether the base RPC price is too high.
   390  	if !allowance.MaxRPCPrice.IsZero() && allowance.MaxRPCPrice.Cmp(hostSettings.BaseRPCPrice) < 0 {
   391  		errStr := fmt.Sprintf("rpc price of host is %v, which is above the maximum allowed by the allowance: %v", hostSettings.BaseRPCPrice, allowance.MaxRPCPrice)
   392  		return errors.New(errStr)
   393  	}
   394  	// Check whether the sector access price is too high.
   395  	if !allowance.MaxSectorAccessPrice.IsZero() && allowance.MaxSectorAccessPrice.Cmp(hostSettings.SectorAccessPrice) < 0 {
   396  		errStr := fmt.Sprintf("sector access price of host is %v, which is above the maximum allowed by the allowance: %v", hostSettings.SectorAccessPrice, allowance.MaxSectorAccessPrice)
   397  		return errors.New(errStr)
   398  	}
   399  	// Check whether the storage price is too high.
   400  	if !allowance.MaxStoragePrice.IsZero() && allowance.MaxStoragePrice.Cmp(hostSettings.StoragePrice) < 0 {
   401  		errStr := fmt.Sprintf("storage price of host is %v, which is above the maximum allowed by the allowance: %v", hostSettings.StoragePrice, allowance.MaxStoragePrice)
   402  		return errors.New(errStr)
   403  	}
   404  	// Check whether the upload bandwidth price is too high.
   405  	if !allowance.MaxUploadBandwidthPrice.IsZero() && allowance.MaxUploadBandwidthPrice.Cmp(hostSettings.UploadBandwidthPrice) < 0 {
   406  		errStr := fmt.Sprintf("upload bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v", hostSettings.UploadBandwidthPrice, allowance.MaxUploadBandwidthPrice)
   407  		return errors.New(errStr)
   408  	}
   409  	// Check whether the download bandwidth price is too high.
   410  	if !allowance.MaxDownloadBandwidthPrice.IsZero() && allowance.MaxDownloadBandwidthPrice.Cmp(hostSettings.DownloadBandwidthPrice) < 0 {
   411  		errStr := fmt.Sprintf("download bandwidth price of host is %v, which is above the maximum allowed by the allowance: %v", hostSettings.UploadBandwidthPrice, allowance.MaxDownloadBandwidthPrice)
   412  		return errors.New(errStr)
   413  	}
   414  
   415  	// If there is no allowance, general price gouging checks have to be
   416  	// disabled, because there is no baseline for understanding what might count
   417  	// as price gouging.
   418  	if allowance.Funds.IsZero() {
   419  		return nil
   420  	}
   421  
   422  	// Check that the combined prices make sense in the context of the overall
   423  	// allowance. The general idea is to compute the total cost of performing
   424  	// the same action repeatedly until a fraction of the desired total resource
   425  	// consumption established by the allowance has been reached. The fraction
   426  	// is determined on a case-by-case basis. If the host is too expensive to
   427  	// even satisfy a fraction of the user's total desired resource consumption,
   428  	// the action will be blocked for price gouging.
   429  	singleUploadCost := hostSettings.SectorAccessPrice.Add(hostSettings.BaseRPCPrice).Add(hostSettings.UploadBandwidthPrice.Mul64(skymodules.StreamUploadSize)).Add(hostSettings.StoragePrice.Mul64(uint64(allowance.Period)).Mul64(skymodules.StreamUploadSize))
   430  	fullCostPerByte := singleUploadCost.Div64(skymodules.StreamUploadSize)
   431  	allowanceStorageCost := fullCostPerByte.Mul64(allowance.ExpectedStorage)
   432  	reducedCost := allowanceStorageCost.Div64(uploadGougingFractionDenom)
   433  	if reducedCost.Cmp(allowance.Funds) > 0 {
   434  		errStr := fmt.Sprintf("combined upload pricing of host yields %v, which is more than the renter is willing to pay for storage: %v - price gouging protection enabled", reducedCost, allowance.Funds)
   435  		return errors.New(errStr)
   436  	}
   437  
   438  	return nil
   439  }