From a662afab91836910d109adba05384b37f940b1ad Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 28 Mar 2024 14:46:17 +0100 Subject: [PATCH 1/4] autopilot: allow for setting optional MinProtocolVersion --- api/autopilot.go | 5 +++++ autopilot/hostscore.go | 27 ++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/api/autopilot.go b/api/autopilot.go index 23425aacf..bea9a68a6 100644 --- a/api/autopilot.go +++ b/api/autopilot.go @@ -2,8 +2,10 @@ package api import ( "errors" + "fmt" "go.sia.tech/core/types" + "go.sia.tech/siad/build" ) const ( @@ -55,6 +57,7 @@ type ( HostsConfig struct { AllowRedundantIPs bool `json:"allowRedundantIPs"` MaxDowntimeHours uint64 `json:"maxDowntimeHours"` + MinProtocolVersion string `json:"minProtocolVersion"` MinRecentScanFailures uint64 `json:"minRecentScanFailures"` ScoreOverrides map[types.PublicKey]float64 `json:"scoreOverrides"` } @@ -123,6 +126,8 @@ type ( func (c AutopilotConfig) Validate() error { if c.Hosts.MaxDowntimeHours > 99*365*24 { return ErrMaxDowntimeHoursTooHigh + } else if c.Hosts.MinProtocolVersion != "" && !build.IsVersion(c.Hosts.MinProtocolVersion) { + return fmt.Errorf("invalid min protocol version '%s'", c.Hosts.MinProtocolVersion) } return nil } diff --git a/autopilot/hostscore.go b/autopilot/hostscore.go index fc98499f1..2791adb3d 100644 --- a/autopilot/hostscore.go +++ b/autopilot/hostscore.go @@ -12,7 +12,15 @@ import ( "go.sia.tech/siad/build" ) -const smallestValidScore = math.SmallestNonzeroFloat64 +const ( + // MinProtocolVersion is the minimum protocol version of a host that we + // accept. + minProtocolVersion = "1.5.9" + + // smallestValidScore is the smallest score that a host can have before + // being ignored. + smallestValidScore = math.SmallestNonzeroFloat64 +) func hostScore(cfg api.AutopilotConfig, h api.Host, storedData uint64, expectedRedundancy float64) api.HostScoreBreakdown { // idealDataPerHost is the amount of data that we would have to put on each @@ -37,7 +45,7 @@ func hostScore(cfg api.AutopilotConfig, h api.Host, storedData uint64, expectedR Prices: priceAdjustmentScore(hostPeriodCost, cfg), StorageRemaining: storageRemainingScore(h.Settings, storedData, allocationPerHost), Uptime: uptimeScore(h), - Version: versionScore(h.Settings), + Version: versionScore(h.Settings, cfg.Hosts.MinProtocolVersion), } } @@ -237,13 +245,22 @@ func uptimeScore(h api.Host) float64 { return math.Pow(ratio, 200*math.Min(1-ratio, 0.30)) } -func versionScore(settings rhpv2.HostSettings) float64 { +func versionScore(settings rhpv2.HostSettings, minVersion string) float64 { + if minVersion == "" { + minVersion = minProtocolVersion + } versions := []struct { version string penalty float64 }{ - {"1.6.0", 0.99}, - {"1.5.9", 0.00}, + // latest protocol version + {"1.6.0", 0.10}, + + // user-defined minimum + {minVersion, 0.00}, + + // absolute minimum + {minProtocolVersion, 0.00}, } weight := 1.0 for _, v := range versions { From bd2aeac7b026747592cf719b96d0ae68e5ab578b Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 28 Mar 2024 15:12:13 +0100 Subject: [PATCH 2/4] e2e: add TestHostMinVersion --- internal/test/e2e/gouging_test.go | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal/test/e2e/gouging_test.go b/internal/test/e2e/gouging_test.go index 68dc264eb..27084ba1f 100644 --- a/internal/test/e2e/gouging_test.go +++ b/internal/test/e2e/gouging_test.go @@ -112,3 +112,43 @@ func TestGouging(t *testing.T) { return err }) } + +func TestHostMinVersion(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() + tt := cluster.tt + + // check number of contracts + contracts, err := cluster.Bus.Contracts(context.Background(), api.ContractsOpts{ + ContractSet: test.AutopilotConfig.Contracts.Set, + }) + tt.OK(err) + if len(contracts) != int(test.AutopilotConfig.Contracts.Amount) { + t.Fatalf("expected %v contracts, got %v", test.AutopilotConfig.Contracts.Amount, len(contracts)) + } + + // set min version to a high value + cfg := test.AutopilotConfig + cfg.Hosts.MinProtocolVersion = "99.99.99" + cluster.UpdateAutopilotConfig(context.Background(), cfg) + + // contracts in set should drop to 0 + tt.Retry(100, time.Millisecond, func() error { + contracts, err := cluster.Bus.Contracts(context.Background(), api.ContractsOpts{ + ContractSet: test.AutopilotConfig.Contracts.Set, + }) + tt.OK(err) + if len(contracts) != int(test.AutopilotConfig.Contracts.Amount) { + return fmt.Errorf("expected 0 contracts, got %v", len(contracts)) + } + return nil + }) +} From 27b848d6e7951b2ab18b94dea9fb3adb45f9e40e Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 4 Apr 2024 10:58:19 +0200 Subject: [PATCH 3/4] autopilot: rename smallestvalidScore --- autopilot/autopilot.go | 2 +- autopilot/contractor.go | 6 +++--- autopilot/hostscore.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/autopilot/autopilot.go b/autopilot/autopilot.go index 4e3023a2f..9f3477b0c 100644 --- a/autopilot/autopilot.go +++ b/autopilot/autopilot.go @@ -797,7 +797,7 @@ func (ap *Autopilot) stateHandlerGET(jc jape.Context) { func countUsableHosts(cfg api.AutopilotConfig, cs api.ConsensusState, fee types.Currency, currentPeriod uint64, rs api.RedundancySettings, gs api.GougingSettings, hosts []api.Host) (usables uint64) { gc := worker.NewGougingChecker(gs, cs, fee, currentPeriod, cfg.Contracts.RenewWindow) for _, host := range hosts { - hc := checkHost(cfg, rs, gc, host, smallestValidScore, 0) + hc := checkHost(cfg, rs, gc, host, minValidScore, 0) if hc.Usability.IsUsable() { usables++ } diff --git a/autopilot/contractor.go b/autopilot/contractor.go index b3dd76ac4..f7033ff30 100644 --- a/autopilot/contractor.go +++ b/autopilot/contractor.go @@ -271,7 +271,7 @@ func (c *contractor) performContractMaintenance(ctx context.Context, w Worker) ( } // fetch candidate hosts - candidates, unusableHosts, err := c.candidateHosts(ctx, hosts, usedHosts, hostData, smallestValidScore) // avoid 0 score hosts + candidates, unusableHosts, err := c.candidateHosts(ctx, hosts, usedHosts, hostData, minValidScore) // avoid 0 score hosts if err != nil { return false, err } @@ -1229,7 +1229,7 @@ func (c *contractor) calculateMinScore(candidates []scoredHost, numContracts uin // return early if there's no hosts if len(candidates) == 0 { c.logger.Warn("min host score is set to the smallest non-zero float because there are no candidate hosts") - return smallestValidScore + return minValidScore } // determine the number of random hosts we fetch per iteration when @@ -1263,7 +1263,7 @@ func (c *contractor) calculateMinScore(candidates []scoredHost, numContracts uin return candidates[i].score > candidates[j].score }) if len(candidates) < int(numContracts) { - return smallestValidScore + return minValidScore } else if cutoff := candidates[numContracts-1].score; minScore > cutoff { minScore = cutoff } diff --git a/autopilot/hostscore.go b/autopilot/hostscore.go index 2791adb3d..a11f96944 100644 --- a/autopilot/hostscore.go +++ b/autopilot/hostscore.go @@ -17,9 +17,9 @@ const ( // accept. minProtocolVersion = "1.5.9" - // smallestValidScore is the smallest score that a host can have before + // minValidScore is the smallest score that a host can have before // being ignored. - smallestValidScore = math.SmallestNonzeroFloat64 + minValidScore = math.SmallestNonzeroFloat64 ) func hostScore(cfg api.AutopilotConfig, h api.Host, storedData uint64, expectedRedundancy float64) api.HostScoreBreakdown { From 1784b7a102ae12a9f5468eabcc6c6a4095b41fcc Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 4 Apr 2024 11:08:27 +0200 Subject: [PATCH 4/4] e2e: address comments --- internal/test/e2e/gouging_test.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/test/e2e/gouging_test.go b/internal/test/e2e/gouging_test.go index 27084ba1f..f5fa2d7fa 100644 --- a/internal/test/e2e/gouging_test.go +++ b/internal/test/e2e/gouging_test.go @@ -120,33 +120,23 @@ func TestHostMinVersion(t *testing.T) { // create a new test cluster cluster := newTestCluster(t, testClusterOptions{ - hosts: int(test.AutopilotConfig.Contracts.Amount), - logger: newTestLoggerCustom(zapcore.ErrorLevel), + hosts: int(test.AutopilotConfig.Contracts.Amount), }) defer cluster.Shutdown() tt := cluster.tt - // check number of contracts - contracts, err := cluster.Bus.Contracts(context.Background(), api.ContractsOpts{ - ContractSet: test.AutopilotConfig.Contracts.Set, - }) - tt.OK(err) - if len(contracts) != int(test.AutopilotConfig.Contracts.Amount) { - t.Fatalf("expected %v contracts, got %v", test.AutopilotConfig.Contracts.Amount, len(contracts)) - } - // set min version to a high value cfg := test.AutopilotConfig cfg.Hosts.MinProtocolVersion = "99.99.99" cluster.UpdateAutopilotConfig(context.Background(), cfg) // contracts in set should drop to 0 - tt.Retry(100, time.Millisecond, func() error { + tt.Retry(100, 100*time.Millisecond, func() error { contracts, err := cluster.Bus.Contracts(context.Background(), api.ContractsOpts{ ContractSet: test.AutopilotConfig.Contracts.Set, }) tt.OK(err) - if len(contracts) != int(test.AutopilotConfig.Contracts.Amount) { + if len(contracts) != 0 { return fmt.Errorf("expected 0 contracts, got %v", len(contracts)) } return nil