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 }