From c2407b1214bf8ee29c32605857de71ed31836b16 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Tue, 13 Feb 2024 14:08:15 +0100 Subject: [PATCH 1/2] feat: neighborhood suggester --- cmd/bee/cmd/cmd.go | 2 ++ cmd/bee/cmd/start.go | 1 + pkg/node/node.go | 40 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) 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..8debd025c2c 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -10,6 +10,7 @@ package node import ( "context" "crypto/ecdsa" + "encoding/json" "errors" "fmt" "io" @@ -17,6 +18,7 @@ import ( "math/big" "net" "net/http" + "net/url" "path/filepath" "runtime" "sync" @@ -171,6 +173,7 @@ type Options struct { EnableStorageIncentives bool StatestoreCacheCapacity uint64 TargetNeighborhood string + NeighborhoodSuggester string } const ( @@ -282,9 +285,40 @@ 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 != "" { + _, err = url.Parse(o.NeighborhoodSuggester) + if err != nil { + return nil, fmt.Errorf("invalid neighborhood suggester: %w", err) + } + logger.Info("fetching target neighborhood from suggester", "url", o.NeighborhoodSuggester) + type suggestionRes struct { + Neighborhood string `json:"neighborhood"` + } + res, err := http.Get(o.NeighborhoodSuggester) + if err != nil { + return nil, fmt.Errorf("get neighborhood suggestion: %w", err) + } + defer res.Body.Close() + var suggestion suggestionRes + d, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("read neighborhood suggestion: %w", err) + } + err = json.Unmarshal(d, &suggestion) + if err != nil { + return nil, fmt.Errorf("unmarshal neighborhood suggestion: %w", err) + } + _, err = swarm.ParseBitStrAddress(suggestion.Neighborhood) + if err != nil { + return nil, fmt.Errorf("invalid neighborhood suggestion. %s", suggestion.Neighborhood) + } + targetNeighborhood = suggestion.Neighborhood + } + + 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) } From a9203df631b59e00c31099e84da2aa4c19665b62 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Tue, 13 Feb 2024 16:26:44 +0100 Subject: [PATCH 2/2] fix: refactor and added tests --- pkg/node/node.go | 28 +----- pkg/util/nbhdutil/neighborhoodsuggestion.go | 53 ++++++++++++ .../nbhdutil/neighborhoodsuggestion_test.go | 86 +++++++++++++++++++ 3 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 pkg/util/nbhdutil/neighborhoodsuggestion.go create mode 100644 pkg/util/nbhdutil/neighborhoodsuggestion_test.go diff --git a/pkg/node/node.go b/pkg/node/node.go index 8debd025c2c..15df1ae799b 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -10,7 +10,6 @@ package node import ( "context" "crypto/ecdsa" - "encoding/json" "errors" "fmt" "io" @@ -18,7 +17,6 @@ import ( "math/big" "net" "net/http" - "net/url" "path/filepath" "runtime" "sync" @@ -287,33 +285,11 @@ func NewBee( // mine the overlay targetNeighborhood := o.TargetNeighborhood if o.TargetNeighborhood == "" && o.NeighborhoodSuggester != "" { - _, err = url.Parse(o.NeighborhoodSuggester) - if err != nil { - return nil, fmt.Errorf("invalid neighborhood suggester: %w", err) - } logger.Info("fetching target neighborhood from suggester", "url", o.NeighborhoodSuggester) - type suggestionRes struct { - Neighborhood string `json:"neighborhood"` - } - res, err := http.Get(o.NeighborhoodSuggester) - if err != nil { - return nil, fmt.Errorf("get neighborhood suggestion: %w", err) - } - defer res.Body.Close() - var suggestion suggestionRes - d, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("read neighborhood suggestion: %w", err) - } - err = json.Unmarshal(d, &suggestion) - if err != nil { - return nil, fmt.Errorf("unmarshal neighborhood suggestion: %w", err) - } - _, err = swarm.ParseBitStrAddress(suggestion.Neighborhood) + targetNeighborhood, err = nbhdutil.FetchNeighborhood(&http.Client{}, o.NeighborhoodSuggester) if err != nil { - return nil, fmt.Errorf("invalid neighborhood suggestion. %s", suggestion.Neighborhood) + return nil, fmt.Errorf("neighborhood suggestion: %w", err) } - targetNeighborhood = suggestion.Neighborhood } if targetNeighborhood != "" { 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) + } + }) + } +}