From f8a4f19085a8671294fde3e5b59ec580fb27276f Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 16 Apr 2024 10:55:28 +0200 Subject: [PATCH 1/3] worker: allow for downloading from hosts which are gouging as long as they are not download gouging --- internal/test/e2e/gouging_test.go | 62 +++++++++++++++++++++++++++++++ worker/host.go | 4 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/internal/test/e2e/gouging_test.go b/internal/test/e2e/gouging_test.go index f5fa2d7fa..e62022bc4 100644 --- a/internal/test/e2e/gouging_test.go +++ b/internal/test/e2e/gouging_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "testing" "time" @@ -142,3 +143,64 @@ func TestHostMinVersion(t *testing.T) { return nil }) } + +func TestDownloadGouging(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + // create a new test cluster + cluster := newTestCluster(t, testClusterOptions{ + hosts: int(test.AutopilotConfig.Contracts.Amount), + logger: newTestLoggerCustom(zapcore.ErrorLevel), + }) + defer cluster.Shutdown() + + cfg := test.AutopilotConfig.Contracts + b := cluster.Bus + w := cluster.Worker + tt := cluster.tt + + // build a hosts map + hostsMap := make(map[string]*Host) + for _, h := range cluster.hosts { + hostsMap[h.PublicKey().String()] = h + } + + // upload and download some data, asserting we have a working contract set + data := make([]byte, rhpv2.SectorSize/12) + tt.OKAll(frand.Read(data)) + + // upload some data + path := fmt.Sprintf("data_%v", len(data)) + tt.OKAll(w.UploadObject(context.Background(), bytes.NewReader(data), api.DefaultBucketName, path, api.UploadObjectOptions{})) + + // update the gouging settings to cause hosts to be gouging but not download gouging + gs := test.GougingSettings + gs.MaxContractPrice = types.NewCurrency64(1) + if err := b.UpdateSetting(context.Background(), api.SettingGouging, gs); err != nil { + t.Fatal(err) + } + + // wait for hosts to drop out of the set + tt.Retry(100, 100*time.Millisecond, func() error { + contracts, err := b.Contracts(context.Background(), api.ContractsOpts{ContractSet: cfg.Set}) + tt.OK(err) + if len(contracts) > 0 { + return fmt.Errorf("still got contracts in set") + } + return nil + }) + + // download the data + tt.OK(w.DownloadObject(context.Background(), io.Discard, api.DefaultBucketName, path, api.DownloadObjectOptions{})) + + // update the gouging settings to cause hosts to be download gouging + gs.MaxDownloadPrice = types.NewCurrency64(1) + if err := b.UpdateSetting(context.Background(), api.SettingGouging, gs); err != nil { + t.Fatal(err) + } + + // downloading should fail now + tt.FailAll(w.DownloadObject(context.Background(), io.Discard, api.DefaultBucketName, path, api.DownloadObjectOptions{})) +} diff --git a/worker/host.go b/worker/host.go index 4f4e97496..6faba7b15 100644 --- a/worker/host.go +++ b/worker/host.go @@ -88,8 +88,8 @@ func (h *host) DownloadSector(ctx context.Context, w io.Writer, root types.Hash2 if err != nil { return err } - if breakdown := gc.Check(nil, &hpt); breakdown.Gouging() { - return fmt.Errorf("%w: %v", errPriceTableGouging, breakdown) + if breakdown := gc.Check(nil, &hpt); breakdown.DownloadErr != "" { + return fmt.Errorf("%w: %v", errPriceTableGouging, breakdown.DownloadErr) } // return errBalanceInsufficient if balance insufficient From 76f324b5668ac4fd4991af5fd85b64d813a14743 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 16 Apr 2024 11:03:49 +0200 Subject: [PATCH 2/3] worker: use hardcoded 1H for account fund cost and account balance cost --- worker/host.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/worker/host.go b/worker/host.go index 6faba7b15..f092534a0 100644 --- a/worker/host.go +++ b/worker/host.go @@ -235,10 +235,11 @@ func (h *host) FundAccount(ctx context.Context, balance types.Currency, rev *typ } // check whether we have money left in the contract - if pt.FundAccountCost.Cmp(rev.ValidRenterPayout()) >= 0 { - return fmt.Errorf("insufficient funds to fund account: %v <= %v", rev.ValidRenterPayout(), pt.FundAccountCost) + cost := types.NewCurrency64(1) + if cost.Cmp(rev.ValidRenterPayout()) >= 0 { + return fmt.Errorf("insufficient funds to fund account: %v <= %v", rev.ValidRenterPayout(), cost) } - availableFunds := rev.ValidRenterPayout().Sub(pt.FundAccountCost) + availableFunds := rev.ValidRenterPayout().Sub(cost) // cap the deposit amount by the money that's left in the contract if deposit.Cmp(availableFunds) > 0 { @@ -246,7 +247,7 @@ func (h *host) FundAccount(ctx context.Context, balance types.Currency, rev *typ } // create the payment - amount := deposit.Add(pt.FundAccountCost) + amount := deposit.Add(cost) payment, err := payByContract(rev, amount, rhpv3.Account{}, h.renterKey) // no account needed for funding if err != nil { return err @@ -254,7 +255,7 @@ func (h *host) FundAccount(ctx context.Context, balance types.Currency, rev *typ // fund the account if err := RPCFundAccount(ctx, t, &payment, h.acc.id, pt.UID); err != nil { - return fmt.Errorf("failed to fund account with %v (excluding cost %v);%w", deposit, pt.FundAccountCost, err) + return fmt.Errorf("failed to fund account with %v (excluding cost %v);%w", deposit, cost, err) } // record the spend @@ -277,7 +278,7 @@ func (h *host) SyncAccount(ctx context.Context, rev *types.FileContractRevision) return h.acc.WithSync(ctx, func() (types.Currency, error) { var balance types.Currency err := h.transportPool.withTransportV3(ctx, h.hk, h.siamuxAddr, func(ctx context.Context, t *transportV3) error { - payment, err := payByContract(rev, pt.AccountBalanceCost, h.acc.id, h.renterKey) + payment, err := payByContract(rev, types.NewCurrency64(1), h.acc.id, h.renterKey) if err != nil { return err } From ab518e9f1a1959b2ed74b0f96fc3ab00071d0bca Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 16 Apr 2024 11:28:11 +0200 Subject: [PATCH 3/3] e2e: fix TestGouging --- internal/test/e2e/gouging_test.go | 68 +------------------------------ 1 file changed, 2 insertions(+), 66 deletions(-) diff --git a/internal/test/e2e/gouging_test.go b/internal/test/e2e/gouging_test.go index e62022bc4..657ef6722 100644 --- a/internal/test/e2e/gouging_test.go +++ b/internal/test/e2e/gouging_test.go @@ -91,11 +91,8 @@ func TestGouging(t *testing.T) { // again, this is necessary for the host to be considered price gouging time.Sleep(defaultHostSettings.PriceTableValidity) - // download the data - should fail - buffer.Reset() - if err := w.DownloadObject(context.Background(), &buffer, api.DefaultBucketName, path, api.DownloadObjectOptions{}); err == nil { - t.Fatal("expected download to fail", err) - } + // download the data - should still work + tt.OKAll(w.DownloadObject(context.Background(), io.Discard, api.DefaultBucketName, path, api.DownloadObjectOptions{})) // try optimising gouging settings resp, err := cluster.Autopilot.EvaluateConfig(context.Background(), test.AutopilotConfig, gs, test.RedundancySettings) @@ -143,64 +140,3 @@ func TestHostMinVersion(t *testing.T) { return nil }) } - -func TestDownloadGouging(t *testing.T) { - if testing.Short() { - t.SkipNow() - } - - // create a new test cluster - cluster := newTestCluster(t, testClusterOptions{ - hosts: int(test.AutopilotConfig.Contracts.Amount), - logger: newTestLoggerCustom(zapcore.ErrorLevel), - }) - defer cluster.Shutdown() - - cfg := test.AutopilotConfig.Contracts - b := cluster.Bus - w := cluster.Worker - tt := cluster.tt - - // build a hosts map - hostsMap := make(map[string]*Host) - for _, h := range cluster.hosts { - hostsMap[h.PublicKey().String()] = h - } - - // upload and download some data, asserting we have a working contract set - data := make([]byte, rhpv2.SectorSize/12) - tt.OKAll(frand.Read(data)) - - // upload some data - path := fmt.Sprintf("data_%v", len(data)) - tt.OKAll(w.UploadObject(context.Background(), bytes.NewReader(data), api.DefaultBucketName, path, api.UploadObjectOptions{})) - - // update the gouging settings to cause hosts to be gouging but not download gouging - gs := test.GougingSettings - gs.MaxContractPrice = types.NewCurrency64(1) - if err := b.UpdateSetting(context.Background(), api.SettingGouging, gs); err != nil { - t.Fatal(err) - } - - // wait for hosts to drop out of the set - tt.Retry(100, 100*time.Millisecond, func() error { - contracts, err := b.Contracts(context.Background(), api.ContractsOpts{ContractSet: cfg.Set}) - tt.OK(err) - if len(contracts) > 0 { - return fmt.Errorf("still got contracts in set") - } - return nil - }) - - // download the data - tt.OK(w.DownloadObject(context.Background(), io.Discard, api.DefaultBucketName, path, api.DownloadObjectOptions{})) - - // update the gouging settings to cause hosts to be download gouging - gs.MaxDownloadPrice = types.NewCurrency64(1) - if err := b.UpdateSetting(context.Background(), api.SettingGouging, gs); err != nil { - t.Fatal(err) - } - - // downloading should fail now - tt.FailAll(w.DownloadObject(context.Background(), io.Discard, api.DefaultBucketName, path, api.DownloadObjectOptions{})) -}