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 }