diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d5c0ad90f..b41f0bf5947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ minor release, the component will be purged, so be prepared (see `Updating` sect - `neofs-cli object nodes` command to get SNs for an object (#2512) - Fetching container estimations via iterators to prevent NeoVM stack overflow (#2173) - `neofs-adm morph netmap-candidates` CLI command (#1889) +- SN network validation (is available by its announced addresses) on bootstrap by the IR (#2475) ### Fixed - `neo-go` RPC connection loss handling (#1337) diff --git a/pkg/innerring/processors/netmap/process_peers.go b/pkg/innerring/processors/netmap/process_peers.go index f8013058f2e..c50fe327a0e 100644 --- a/pkg/innerring/processors/netmap/process_peers.go +++ b/pkg/innerring/processors/netmap/process_peers.go @@ -1,10 +1,15 @@ package netmap import ( + "bytes" + "context" "encoding/hex" + "fmt" + "time" netmapclient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" + "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/netmap" "go.uber.org/zap" ) @@ -37,6 +42,43 @@ func (np *Processor) processAddPeer(ev netmapEvent.AddPeer) { return } + var results []*client.ResEndpointInfo + nodeInfo.IterateNetworkEndpoints(func(s string) bool { + var res *client.ResEndpointInfo + var c *client.Client + + c, err = createSDKClient(s) + if err != nil { + err = fmt.Errorf("'%s': client creation: %w", s, err) + return true + } + defer func() { + _ = c.Close() + }() + + timeoutContext, cancel := context.WithTimeout(context.Background(), pingTimeout) + defer cancel() + + res, err = c.EndpointInfo(timeoutContext, client.PrmEndpointInfo{}) + if err != nil { + err = fmt.Errorf("'%s': could not ping node with `EndpointInfo`: %w", s, err) + return true + } + + results = append(results, res) + return false + }) + + for _, res := range results { + err = compareNodeInfos(nodeInfo, res.NodeInfo()) + if err != nil { + np.log.Warn("correct node info, but `EndpointInfo` result differs", + zap.Error(err), + zap.String("node", hex.EncodeToString(nodeInfo.PublicKey()))) + return + } + } + // validate and update node info err = np.nodeValidator.VerifyAndUpdate(&nodeInfo) if err != nil { @@ -116,3 +158,82 @@ func (np *Processor) processUpdatePeer(ev netmapEvent.UpdatePeer) { np.log.Error("can't invoke netmap.UpdatePeer", zap.Error(err)) } } + +func compareNodeInfos(niExp, niGot netmap.NodeInfo) error { + // a node can be in a STATE_1 (and respond with it) + // but the request can mean a state transfer to a + // STATE_2, so make both node infos in the same state, + // e.g. ONLINE + niGot.SetOnline() + niExp.SetOnline() + if exp, got := niExp.Marshal(), niGot.Marshal(); bytes.Equal(exp, got) { + return nil + } + + var err error + + if exp, got := niExp.Hash(), niGot.Hash(); exp != got { + return fmt.Errorf("hash: got %d, expect %d", got, exp) + } + + if exp, got := niExp.NumberOfAttributes(), niGot.NumberOfAttributes(); exp != got { + return fmt.Errorf("attr number: got %d, expect %d", got, exp) + } + + niExp.IterateAttributes(func(key, value string) { + vGot := niGot.Attribute(key) + if vGot != value { + err = fmt.Errorf("non-equal %s attribute: got %s, expect %s", key, vGot, value) + } + }) + if err != nil { + return err + } + + if exp, got := niExp.NumberOfNetworkEndpoints(), niGot.NumberOfNetworkEndpoints(); exp != got { + return fmt.Errorf("address number: got %d, expect %d", got, exp) + } + + expAddrM := make(map[string]struct{}, niExp.NumberOfAttributes()) + niExp.IterateNetworkEndpoints(func(s string) bool { + expAddrM[s] = struct{}{} + return false + }) + + niGot.IterateNetworkEndpoints(func(s string) bool { + if _, ok := expAddrM[s]; !ok { + err = fmt.Errorf("got unexpected address: %s", s) + return true + } + + return false + }) + if err != nil { + return err + } + + return nil +} + +const pingTimeout = 15 * time.Second + +func createSDKClient(e string) (*client.Client, error) { + var prmInit client.PrmInit + var prmDial client.PrmDial + + prmDial.SetTimeout(pingTimeout) + prmDial.SetStreamTimeout(pingTimeout) + prmDial.SetServerURI(e) + + c, err := client.New(prmInit) + if err != nil { + return nil, fmt.Errorf("can't create SDK client: %w", err) + } + + err = c.Dial(prmDial) + if err != nil { + return nil, fmt.Errorf("can't init SDK client: %w", err) + } + + return c, nil +}