diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index ffe6bc06d01..8b5e6330f18 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -85,6 +85,7 @@ const ( optionNameStorageIncentivesEnable = "storage-incentives-enable" optionNameStateStoreCacheCapacity = "statestore-cache-capacity" optionNameTargetNeighborhood = "target-neighborhood" + optionNameNeighborhoodSuggester = "neighborhood-suggester" ) // nolint:gochecknoinits @@ -302,6 +303,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Bool(optionNameStorageIncentivesEnable, true, "enable storage incentives feature") cmd.Flags().Uint64(optionNameStateStoreCacheCapacity, 100_000, "lru memory caching capacity in number of statestore entries") cmd.Flags().String(optionNameTargetNeighborhood, "", "neighborhood to target in binary format (ex: 111111001) for mining the initial overlay") + cmd.Flags().String(optionNameNeighborhoodSuggester, "https://api.swarmscan.io/v1/network/neighborhoods/suggestion", "suggester for target neighborhood") } func newLogger(cmd *cobra.Command, verbosity string) (log.Logger, error) { diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index ee287a2104a..60ce54582e7 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -339,6 +339,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo EnableStorageIncentives: c.config.GetBool(optionNameStorageIncentivesEnable), StatestoreCacheCapacity: c.config.GetUint64(optionNameStateStoreCacheCapacity), TargetNeighborhood: c.config.GetString(optionNameTargetNeighborhood), + NeighborhoodSuggester: c.config.GetString(optionNameNeighborhoodSuggester), }) return b, err diff --git a/pkg/node/node.go b/pkg/node/node.go index 214cad27d4f..15df1ae799b 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -171,6 +171,7 @@ type Options struct { EnableStorageIncentives bool StatestoreCacheCapacity uint64 TargetNeighborhood string + NeighborhoodSuggester string } const ( @@ -282,9 +283,18 @@ func NewBee( if !nonceExists { // mine the overlay - if o.TargetNeighborhood != "" { - logger.Info("mining an overlay address for the fresh node to target the selected neighborhood", "target", o.TargetNeighborhood) - swarmAddress, nonce, err = nbhdutil.MineOverlay(ctx, *pubKey, networkID, o.TargetNeighborhood) + targetNeighborhood := o.TargetNeighborhood + if o.TargetNeighborhood == "" && o.NeighborhoodSuggester != "" { + logger.Info("fetching target neighborhood from suggester", "url", o.NeighborhoodSuggester) + targetNeighborhood, err = nbhdutil.FetchNeighborhood(&http.Client{}, o.NeighborhoodSuggester) + if err != nil { + return nil, fmt.Errorf("neighborhood suggestion: %w", err) + } + } + + if targetNeighborhood != "" { + logger.Info("mining an overlay address for the fresh node to target the selected neighborhood", "target", targetNeighborhood) + swarmAddress, nonce, err = nbhdutil.MineOverlay(ctx, *pubKey, networkID, targetNeighborhood) if err != nil { return nil, fmt.Errorf("mine overlay address: %w", err) } diff --git a/pkg/util/nbhdutil/neighborhoodsuggestion.go b/pkg/util/nbhdutil/neighborhoodsuggestion.go new file mode 100644 index 00000000000..713f0345055 --- /dev/null +++ b/pkg/util/nbhdutil/neighborhoodsuggestion.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package nbhdutil + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/ethersphere/bee/pkg/swarm" +) + +type httpClient interface { + Get(url string) (*http.Response, error) +} + +func FetchNeighborhood(client httpClient, suggester string) (string, error) { + if suggester == "" { + return "", nil + } + + _, err := url.ParseRequestURI(suggester) + if err != nil { + return "", err + } + + type suggestionRes struct { + Neighborhood string `json:"neighborhood"` + } + res, err := client.Get(suggester) + if err != nil { + return "", err + } + defer res.Body.Close() + var suggestion suggestionRes + d, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + err = json.Unmarshal(d, &suggestion) + if err != nil { + return "", err + } + _, err = swarm.ParseBitStrAddress(suggestion.Neighborhood) + if err != nil { + return "", fmt.Errorf("invalid neighborhood. %s", suggestion.Neighborhood) + } + return suggestion.Neighborhood, nil +} diff --git a/pkg/util/nbhdutil/neighborhoodsuggestion_test.go b/pkg/util/nbhdutil/neighborhoodsuggestion_test.go new file mode 100644 index 00000000000..27c9b59d800 --- /dev/null +++ b/pkg/util/nbhdutil/neighborhoodsuggestion_test.go @@ -0,0 +1,86 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package nbhdutil_test + +import ( + "io" + "net/http" + "strings" + "testing" + + "github.com/ethersphere/bee/pkg/util/nbhdutil" +) + +type mockHttpClient struct { + res string +} + +func (c *mockHttpClient) Get(_ string) (*http.Response, error) { + return &http.Response{ + Body: io.NopCloser(strings.NewReader(c.res)), + }, nil +} + +func Test_FetchNeighborhood(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + neighborhood string + suggester string + httpRes string + wantNeighborhood string + wantError bool + }{ + { + name: "no suggester", + suggester: "", + wantNeighborhood: "", + wantError: false, + }, + { + name: "inavlid suggester url", + suggester: "abc", + wantNeighborhood: "", + wantError: true, + }, + { + name: "missing neighborhood in res", + suggester: "http://test.com", + wantNeighborhood: "", + wantError: false, + httpRes: `{"abc":"abc"}`, + }, + { + name: "invalid neighborhood", + suggester: "http://test.com", + wantNeighborhood: "", + wantError: true, + httpRes: `{"neighborhood":"abc"}`, + }, + { + name: "valid neighborhood", + suggester: "http://test.com", + wantNeighborhood: "11011101000", + wantError: false, + httpRes: `{"neighborhood":"11011101000"}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + neighborhood, err := nbhdutil.FetchNeighborhood(&mockHttpClient{res: test.httpRes}, test.suggester) + if test.wantNeighborhood != neighborhood { + t.Fatalf("invalid neighborhood. want %s, got %s", test.wantNeighborhood, neighborhood) + } + if test.wantError && err == nil { + t.Fatalf("expected error. got no error") + } + if !test.wantError && err != nil { + t.Fatalf("expected no error. got %v", err) + } + }) + } +}