From 82263cd248df4a82c1984522f57f075783bfb033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sun, 31 May 2020 18:33:55 +1200 Subject: [PATCH 1/4] Improve reliability and logging of setup node. * Split setup node into multiple functions for easier testability. * Check inputs for each setup request. * Use context.Context in all parts of the setup process to ensure we cancel on hard timeout. * Introduced routerclient.Map to clean up various parts of the logic, as well as allow the setup process to be speedier. * Got rid of the routing.Path data type as it is confusing. I has resorted to use []routing.Hop directly. * Unit tests. --- .travis.yml | 3 +- cmd/setup-node/commands/root.go | 7 +- go.mod | 3 +- go.sum | 3 +- pkg/routefinder/rfclient/client.go | 6 +- pkg/routefinder/rfclient/mock.go | 4 +- pkg/router/router.go | 18 +- pkg/router/routerclient/client.go | 62 ++--- pkg/router/routerclient/client_test.go | 6 +- pkg/router/routerclient/dmsg_wrapper.go | 28 ++ pkg/router/routerclient/map.go | 79 ++++++ pkg/router/routerclient/map_test.go | 164 ++++++++++++ pkg/router/routerclient/wrappers.go | 115 -------- pkg/routing/route.go | 60 ++++- pkg/routing/route_descriptor.go | 2 +- pkg/routing/rule.go | 50 ++-- pkg/routing/rule_test.go | 4 +- pkg/routing/table.go | 2 +- pkg/setup/config.go | 13 +- pkg/setup/id_reserver.go | 158 +++++++++++ pkg/setup/id_reserver_test.go | 237 +++++++++++++++++ pkg/setup/idreservoir.go | 200 -------------- pkg/setup/mock_id_reserver.go | 113 ++++++++ pkg/setup/node.go | 281 ++++++++++++-------- pkg/setup/node_old_test.go | 248 ++++++++++++++++++ pkg/setup/node_test.go | 333 ++++++++---------------- pkg/setup/rpc_gateway.go | 44 ++-- pkg/setup/rules_map.go | 33 +++ pkg/setup/testing_test.go | 108 ++++++++ pkg/snet/mock_dialer.go | 55 ++++ pkg/snet/network.go | 2 + pkg/visor/rpc.go | 2 +- pkg/visor/rpc_client.go | 2 +- pkg/visor/visorconfig/README.md | 60 ++--- pkg/visor/visorconfig/types.go | 5 + vendor/modules.txt | 23 ++ 36 files changed, 1725 insertions(+), 808 deletions(-) create mode 100644 pkg/router/routerclient/dmsg_wrapper.go create mode 100644 pkg/router/routerclient/map.go create mode 100644 pkg/router/routerclient/map_test.go delete mode 100644 pkg/router/routerclient/wrappers.go create mode 100644 pkg/setup/id_reserver.go create mode 100644 pkg/setup/id_reserver_test.go delete mode 100644 pkg/setup/idreservoir.go create mode 100644 pkg/setup/mock_id_reserver.go create mode 100644 pkg/setup/node_old_test.go create mode 100644 pkg/setup/rules_map.go create mode 100644 pkg/setup/testing_test.go create mode 100644 pkg/snet/mock_dialer.go diff --git a/.travis.yml b/.travis.yml index 047629ba81..74393d90a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: # - "1.13.x" At minimum the code should run make check on the latest two go versions in the default linux environment provided by Travis. - - "1.13.x" + - "1.14.x" dist: xenial @@ -19,6 +19,7 @@ before_install: install: - go get -u github.com/FiloSottile/vendorcheck - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.27.0 + - make tidy before_script: - ci_scripts/create-ip-aliases.sh diff --git a/cmd/setup-node/commands/root.go b/cmd/setup-node/commands/root.go index 4698c107ce..f7b6063071 100644 --- a/cmd/setup-node/commands/root.go +++ b/cmd/setup-node/commands/root.go @@ -2,6 +2,7 @@ package commands import ( "bufio" + "context" "encoding/json" "io" "io/ioutil" @@ -10,6 +11,7 @@ import ( "os" "github.com/SkycoinProject/dmsg/buildinfo" + "github.com/SkycoinProject/dmsg/cmdutil" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" @@ -87,7 +89,10 @@ var rootCmd = &cobra.Command{ } }() - logger.Fatal(sn.Serve()) + ctx, cancel := cmdutil.SignalContext(context.Background(), logger) + defer cancel() + + logger.Fatal(sn.Serve(ctx)) }, } diff --git a/go.mod b/go.mod index 87cb24b121..e7b3ed033b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/SkycoinProject/skywire-mainnet -go 1.13 +go 1.14 require ( github.com/SkycoinProject/dmsg v0.2.2 @@ -11,7 +11,6 @@ require ( github.com/google/uuid v1.1.1 github.com/gorilla/securecookie v1.1.1 github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mholt/archiver/v3 v3.3.0 github.com/pkg/profile v1.3.0 github.com/prometheus/client_golang v1.3.0 diff --git a/go.sum b/go.sum index 148dd6eed0..d25f98993f 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/SkycoinProject/dmsg v0.0.0-20200306152741-acee74fa4514/go.mod h1:DzykXMLlx6Fx0fGjZsCIRas/MIvxW8DZpmDA6f2nCRk= -github.com/SkycoinProject/dmsg v0.1.1-0.20200523194607-be73f083a729 h1:Edgnt4ido4MGfNTEJUYqNeXt0AlJ4EHlFCWBrKYPvT4= -github.com/SkycoinProject/dmsg v0.1.1-0.20200523194607-be73f083a729/go.mod h1:MiX+UG/6fl3g+9rS13/fq7BwUQ2eOlg1yOBOnNf6J6A= github.com/SkycoinProject/dmsg v0.2.2 h1:dVsenV5YvvP1Ptm0keproi/c6nA07FU6UntsFBga/74= github.com/SkycoinProject/dmsg v0.2.2/go.mod h1:ze0XfdlLo3wtaK7caRqFipkRxEOZDqfgdPDARqG/jZs= github.com/SkycoinProject/skycoin v0.26.0/go.mod h1:xqPLOKh5B6GBZlGA7B5IJfQmCy7mwimD9NlqxR3gMXo= @@ -164,6 +162,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.3.0 h1:OQIvuDgm00gWVWGTf4m4mCt6W1/0YqU7Ntg0mySWgaI= github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= diff --git a/pkg/routefinder/rfclient/client.go b/pkg/routefinder/rfclient/client.go index e1fb0f52a6..5efea38ee7 100644 --- a/pkg/routefinder/rfclient/client.go +++ b/pkg/routefinder/rfclient/client.go @@ -46,7 +46,7 @@ type HTTPError struct { // Client implements route finding operations. type Client interface { - FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) + FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][][]routing.Hop, error) } // APIClient implements Client interface @@ -71,7 +71,7 @@ func NewHTTP(addr string, apiTimeout time.Duration) Client { // FindRoutes returns routes from source skywire visor to destiny, that has at least the given minHops and as much // the given maxHops as well as the reverse routes from destiny to source. -func (c *apiClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) { +func (c *apiClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][][]routing.Hop, error) { requestBody := &FindRoutesRequest{ Edges: rts, Opts: opts, @@ -113,7 +113,7 @@ func (c *apiClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opt return nil, errors.New(apiErr.Error.Message) } - var paths map[routing.PathEdges][]routing.Path + var paths map[routing.PathEdges][][]routing.Hop err = json.NewDecoder(res.Body).Decode(&paths) if err != nil { return nil, err diff --git a/pkg/routefinder/rfclient/mock.go b/pkg/routefinder/rfclient/mock.go index d5cc193750..05572334d2 100644 --- a/pkg/routefinder/rfclient/mock.go +++ b/pkg/routefinder/rfclient/mock.go @@ -27,7 +27,7 @@ func (r *mockClient) SetError(err error) { } // FindRoutes implements Client for MockClient -func (r *mockClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) { +func (r *mockClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][][]routing.Hop, error) { if r.err != nil { return nil, r.err } @@ -36,7 +36,7 @@ func (r *mockClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, op return nil, fmt.Errorf("no edges provided to returns routes from") } - return map[routing.PathEdges][]routing.Path{ + return map[routing.PathEdges][][]routing.Hop{ [2]cipher.PubKey{rts[0][0], rts[0][1]}: { { routing.Hop{ diff --git a/pkg/router/router.go b/pkg/router/router.go index 6adcb94ff6..24d1986806 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -386,7 +386,7 @@ func (r *router) handleDataPacket(ctx context.Context, packet routing.Packet) er return err } - if rule.Type() == routing.RuleConsume { + if rule.Type() == routing.RuleReverse { r.logger.Debugf("Handling packet of type %s with route ID %d", packet.Type(), packet.RouteID()) } else { r.logger.Debugf("Handling packet of type %s with route ID %d and next ID %d", packet.Type(), @@ -394,7 +394,7 @@ func (r *router) handleDataPacket(ctx context.Context, packet routing.Packet) er } switch rule.Type() { - case routing.RuleForward, routing.RuleIntermediaryForward: + case routing.RuleForward, routing.RuleIntermediary: r.logger.Infoln("Handling intermediary data packet") return r.forwardPacket(ctx, packet, rule) } @@ -429,7 +429,7 @@ func (r *router) handleClosePacket(ctx context.Context, packet routing.Packet) e return err } - if rule.Type() == routing.RuleConsume { + if rule.Type() == routing.RuleReverse { r.logger.Debugf("Handling packet of type %s with route ID %d", packet.Type(), packet.RouteID()) } else { r.logger.Debugf("Handling packet of type %s with route ID %d and next ID %d", packet.Type(), @@ -441,7 +441,7 @@ func (r *router) handleClosePacket(ctx context.Context, packet routing.Packet) e r.rt.DelRules(routeIDs) }() - if t := rule.Type(); t == routing.RuleIntermediaryForward { + if t := rule.Type(); t == routing.RuleIntermediary { r.logger.Infoln("Handling intermediary close packet") return r.forwardPacket(ctx, packet, rule) } @@ -489,7 +489,7 @@ func (r *router) handleKeepAlivePacket(ctx context.Context, packet routing.Packe return err } - if rule.Type() == routing.RuleConsume { + if rule.Type() == routing.RuleReverse { r.logger.Debugf("Handling packet of type %s with route ID %d", packet.Type(), packet.RouteID()) } else { r.logger.Debugf("Handling packet of type %s with route ID %d and next ID %d", packet.Type(), @@ -498,7 +498,7 @@ func (r *router) handleKeepAlivePacket(ctx context.Context, packet routing.Packe // propagate packet only for intermediary rule. forward rule workflow doesn't get here, // consume rules should be omitted, activity is already updated - if t := rule.Type(); t == routing.RuleIntermediaryForward { + if t := rule.Type(); t == routing.RuleIntermediary { r.logger.Infoln("Handling intermediary keep-alive packet") return r.forwardPacket(ctx, packet, rule) } @@ -605,7 +605,7 @@ func (r *router) forwardPacket(ctx context.Context, packet routing.Packet, rule func (r *router) RemoveRouteDescriptor(desc routing.RouteDescriptor) { rules := r.rt.AllRules() for _, rule := range rules { - if rule.Type() != routing.RuleConsume { + if rule.Type() != routing.RuleReverse { continue } @@ -617,7 +617,7 @@ func (r *router) RemoveRouteDescriptor(desc routing.RouteDescriptor) { } } -func (r *router) fetchBestRoutes(src, dst cipher.PubKey, opts *DialOptions) (fwd, rev routing.Path, err error) { +func (r *router) fetchBestRoutes(src, dst cipher.PubKey, opts *DialOptions) (fwd, rev []routing.Hop, err error) { // TODO(nkryuchkov): use opts if opts == nil { opts = DefaultDialOptions() // nolint @@ -802,7 +802,7 @@ func (r *router) removeRouteGroupOfRule(rule routing.Rule) { // we need to process only consume rules, cause we don't // really care about the other ones, other rules removal // doesn't affect our work here - if rule.Type() != routing.RuleConsume { + if rule.Type() != routing.RuleReverse { log. WithField("func", "removeRouteGroupOfRule"). WithField("rule", rule.Type().String()). diff --git a/pkg/router/routerclient/client.go b/pkg/router/routerclient/client.go index d992689b5f..a574f8aef4 100644 --- a/pkg/router/routerclient/client.go +++ b/pkg/router/routerclient/client.go @@ -2,33 +2,44 @@ package routerclient import ( "context" + "fmt" + "io" "net/rpc" "github.com/SkycoinProject/dmsg/cipher" + "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" ) -const rpcName = "RPCGateway" +// RPCName is the RPC gateway object name. +const RPCName = "RPCGateway" -// Client is an RPC client for router. +// Client is used to interact with the router's API remotely. The setup node uses this. type Client struct { rpc *rpc.Client + rPK cipher.PubKey // public key of remote router + log logrus.FieldLogger } // NewClient creates a new Client. -func NewClient(ctx context.Context, dialer snet.Dialer, pk cipher.PubKey) (*Client, error) { - s, err := dialer.Dial(ctx, pk, snet.AwaitSetupPort) +func NewClient(ctx context.Context, dialer snet.Dialer, rPK cipher.PubKey) (*Client, error) { + s, err := dialer.Dial(ctx, rPK, snet.AwaitSetupPort) if err != nil { return nil, err } + return NewClientFromRaw(s, rPK), nil +} - client := &Client{ - rpc: rpc.NewClient(s), +// NewClientFromRaw creates a new client from a raw connection. +func NewClientFromRaw(conn io.ReadWriteCloser, rPK cipher.PubKey) *Client { + return &Client{ + rpc: rpc.NewClient(conn), + rPK: rPK, + log: logging.MustGetLogger(fmt.Sprintf("router_client:%s", rPK.String())), } - - return client, nil } // Close closes a Client. @@ -36,41 +47,32 @@ func (c *Client) Close() error { if c == nil { return nil } - - if err := c.rpc.Close(); err != nil { - return err - } - - return nil + return c.rpc.Close() } // AddEdgeRules adds forward and consume rules to router (forward and reverse). -func (c *Client) AddEdgeRules(ctx context.Context, rules routing.EdgeRules) (bool, error) { - var ok bool - err := c.call(ctx, rpcName+".AddEdgeRules", rules, &ok) - +func (c *Client) AddEdgeRules(ctx context.Context, rules routing.EdgeRules) (ok bool, err error) { + const method = "AddEdgeRules" + err = c.call(ctx, method, rules, &ok) return ok, err } // AddIntermediaryRules adds intermediary rules to router. -func (c *Client) AddIntermediaryRules(ctx context.Context, rules []routing.Rule) (bool, error) { - var ok bool - err := c.call(ctx, rpcName+".AddIntermediaryRules", rules, &ok) - +func (c *Client) AddIntermediaryRules(ctx context.Context, rules []routing.Rule) (ok bool, err error) { + const method = "AddIntermediaryRules" + err = c.call(ctx, method, rules, &ok) return ok, err } // ReserveIDs reserves n IDs and returns them. -func (c *Client) ReserveIDs(ctx context.Context, n uint8) ([]routing.RouteID, error) { - var routeIDs []routing.RouteID - err := c.call(ctx, rpcName+".ReserveIDs", n, &routeIDs) - - return routeIDs, err +func (c *Client) ReserveIDs(ctx context.Context, n uint8) (rtIDs []routing.RouteID, err error) { + const method = "ReserveIDs" + err = c.call(ctx, method, n, &rtIDs) + return rtIDs, err } -func (c *Client) call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error { - call := c.rpc.Go(serviceMethod, args, reply, nil) - +func (c *Client) call(ctx context.Context, method string, args interface{}, reply interface{}) error { + call := c.rpc.Go(RPCName+"."+method, args, reply, nil) select { case <-ctx.Done(): return ctx.Err() diff --git a/pkg/router/routerclient/client_test.go b/pkg/router/routerclient/client_test.go index a2d823e411..4bcc02907b 100644 --- a/pkg/router/routerclient/client_test.go +++ b/pkg/router/routerclient/client_test.go @@ -92,12 +92,8 @@ func prepRPCServerAndClient(t *testing.T, r router.Router) (s *rpc.Server, cl *C l, err := nettest.NewLocalListener("tcp") require.NoError(t, err) - gateway := router.NewRPCGateway(r) - s = rpc.NewServer() - err = s.Register(gateway) - require.NoError(t, err) - + require.NoError(t, s.Register(router.NewRPCGateway(r))) go s.Accept(l) conn, err := net.Dial("tcp", l.Addr().String()) diff --git a/pkg/router/routerclient/dmsg_wrapper.go b/pkg/router/routerclient/dmsg_wrapper.go new file mode 100644 index 0000000000..17dc4abffc --- /dev/null +++ b/pkg/router/routerclient/dmsg_wrapper.go @@ -0,0 +1,28 @@ +package routerclient + +import ( + "context" + "net" + + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" + + "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/dmsg/cipher" +) + +// WrapDmsgClient wraps a dmsg client to implement snet.Dialer +func WrapDmsgClient(dmsgC *dmsg.Client) snet.Dialer { + return &dmsgClientDialer{Client: dmsgC} +} + +type dmsgClientDialer struct { + *dmsg.Client +} + +func (w *dmsgClientDialer) Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) { + return w.Client.Dial(ctx, dmsg.Addr{PK: remote, Port: port}) +} + +func (w *dmsgClientDialer) Type() string { + return snet.DmsgType +} diff --git a/pkg/router/routerclient/map.go b/pkg/router/routerclient/map.go new file mode 100644 index 0000000000..ef5d445354 --- /dev/null +++ b/pkg/router/routerclient/map.go @@ -0,0 +1,79 @@ +package routerclient + +import ( + "context" + + "github.com/SkycoinProject/dmsg/cipher" + + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" +) + +// Map is a map of router RPC clients associated with the router's visor PK. +type Map map[cipher.PubKey]*Client + +type dialResult struct { + client *Client + err error +} + +// MakeMap makes a Map of the router clients, where the key is the router's visor public key. +// It creates these router clients by dialing to them concurrently. +func MakeMap(ctx context.Context, dialer snet.Dialer, pks []cipher.PubKey) (Map, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + results := make(chan dialResult) + defer close(results) + + for _, pk := range pks { + go func(pk cipher.PubKey) { + client, err := NewClient(ctx, dialer, pk) + results <- dialResult{client: client, err: err} + }(pk) + } + + rcM := make(Map, len(pks)) + var err error + for range pks { + res := <-results + if isDone(ctx) { + continue + } + if res.err != nil { + cancel() + err = res.err + continue + } + rcM[res.client.rPK] = res.client + } + + if err != nil { + rcM.CloseAll() // TODO: log this + } + return rcM, err +} + +// Client returns a router client of given public key. +func (cm Map) Client(rPK cipher.PubKey) *Client { + return cm[rPK] +} + +// CloseAll closes all contained router clients. +func (cm Map) CloseAll() (errs []error) { + for k, c := range cm { + if err := c.Close(); err != nil { + errs = append(errs, err) + } + delete(cm, k) + } + return errs +} + +func isDone(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} diff --git a/pkg/router/routerclient/map_test.go b/pkg/router/routerclient/map_test.go new file mode 100644 index 0000000000..70d0712834 --- /dev/null +++ b/pkg/router/routerclient/map_test.go @@ -0,0 +1,164 @@ +package routerclient + +import ( + "context" + "errors" + "fmt" + "net" + "net/rpc" + "testing" + "time" + + "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/dmsg/cipher" + "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "golang.org/x/net/nettest" + + "github.com/SkycoinProject/skywire-mainnet/pkg/router" + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" +) + +func TestMakeMap(t *testing.T) { + logging.SetLevel(logrus.WarnLevel) + + const timeout = time.Second * 5 + + type testCase struct { + okays int // Number of routers to generate, that can be successfully dialed to. + fails []time.Duration // Number of routers to generate, that should fail when dialed to. + } + + cases := []testCase{ + {1, nil}, + {4, nil}, + {7, make([]time.Duration, 5)}, + {8, []time.Duration{0, time.Second, time.Millisecond}}, + {0, make([]time.Duration, 10)}, + {1, []time.Duration{timeout * 2}}, // this should still get canceled from hard deadline + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + var successes = tc.okays + var fails = len(tc.fails) + var total = successes + fails + + // Arrange: dialer that dials to remote routers, used for creating router clients + dialer := newTestDialer(total) + + // Arrange: successful mock router + okayR := new(router.MockRouter) + okayR.On("ReserveKeys", mock.Anything).Return([]routing.RouteID{}, nil) + + // Arrange: serve router gateways (via single mock router) + for i := 0; i < successes; i++ { + addr := serveRouterRPC(t, okayR) + dialer.Add(addr, nil) + } + for i := 0; i < fails; i++ { + addr := serveRouterRPC(t, okayR) + duration := time.Second + dialer.Add(addr, &duration) + } + + // Arrange: Ensure MakeMap call has a hard deadline + ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(timeout)) + defer cancel() + + // Act: MakeMap dials to all routers + rcM, err := MakeMap(ctx, dialer, dialer.PKs()) + t.Cleanup(func() { + for _, err := range rcM.CloseAll() { + assert.NoError(t, err) + } + }) + + if fails == 0 { + // Assert: (all dials successful) we can successfully interact with the remote router via all clients + require.NoError(t, err) + for _, pk := range dialer.PKs() { + routerC := rcM.Client(pk) + assert.NotNil(t, routerC) + _, err := rcM.Client(pk).ReserveIDs(context.Background(), 0) + assert.NoError(t, err) + } + } else { + // Assert: (some dials fail) should return error and internal clients are all nil + require.Error(t, err) + for _, pk := range dialer.PKs() { + routerC := rcM.Client(pk) + assert.Nil(t, routerC) + } + } + }) + } +} + +// serves a router over RPC and returns the bound TCP address +func serveRouterRPC(t *testing.T, r router.Router) (addr string) { + l, err := nettest.NewLocalListener("tcp") + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, l.Close()) }) + + rpcS := rpc.NewServer() + require.NoError(t, rpcS.Register(router.NewRPCGateway(r))) + go rpcS.Accept(l) + + return l.Addr().String() +} + +func pkFromAddr(addr string) (pk cipher.PubKey) { + h := cipher.SumSHA256([]byte(addr)) + copy(pk[1:], h[:]) + return +} + +// testDialer mocks a snet dialer which should dial to a 'pk:port' address +// To achieve this, we use a map that associates pks with a TCP addresses and dial via TCP. +// Ports become irrelevant. +type testDialer struct { + m map[cipher.PubKey]string + failures map[cipher.PubKey]time.Duration +} + +func newTestDialer(routers int) *testDialer { + return &testDialer{ + m: make(map[cipher.PubKey]string, routers), + failures: make(map[cipher.PubKey]time.Duration), + } +} + +func (d *testDialer) Add(addr string, failure *time.Duration) cipher.PubKey { + pk := pkFromAddr(addr) + d.m[pk] = addr + if failure != nil { + d.failures[pk] = *failure + } + return pk +} + +func (d *testDialer) PKs() []cipher.PubKey { + out := make([]cipher.PubKey, 0, len(d.m)) + for pk := range d.m { + out = append(out, pk) + } + return out +} + +func (d *testDialer) Dial(_ context.Context, remote cipher.PubKey, _ uint16) (net.Conn, error) { + if wait, ok := d.failures[remote]; ok { + if wait != 0 { + time.Sleep(wait) + } + return nil, errors.New("test error: failed to dial, as expected") + } + return net.Dial("tcp", d.m[remote]) +} + +func (testDialer) Type() string { + return dmsg.Type +} diff --git a/pkg/router/routerclient/wrappers.go b/pkg/router/routerclient/wrappers.go deleted file mode 100644 index 7619fbdfe4..0000000000 --- a/pkg/router/routerclient/wrappers.go +++ /dev/null @@ -1,115 +0,0 @@ -package routerclient - -import ( - "context" - "fmt" - "net" - - "github.com/SkycoinProject/skywire-mainnet/pkg/snet" - - "github.com/SkycoinProject/dmsg" - "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" - - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" -) - -// TODO: remove this -// dmsgClientWrapper is a temporary workaround to make dmsg client implement `snet.Dialer`. -// The only reason to use this is because client's `Dial` returns `*dmsg.Stream` instead of `net.Conn`, -// so this stuff should be removed as soon as the func's signature changes -type dmsgClientWrapper struct { - *dmsg.Client -} - -func wrapDmsgC(dmsgC *dmsg.Client) *dmsgClientWrapper { - return &dmsgClientWrapper{Client: dmsgC} -} - -func (w *dmsgClientWrapper) Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) { - addr := dmsg.Addr{ - PK: remote, - Port: port, - } - - return w.Client.Dial(ctx, addr) -} - -func (w *dmsgClientWrapper) Type() string { - return snet.DmsgType -} - -// AddEdgeRules is a wrapper for (*Client).AddEdgeRules. -func AddEdgeRules( - ctx context.Context, - log *logging.Logger, - dmsgC *dmsg.Client, - pk cipher.PubKey, - rules routing.EdgeRules, -) (bool, error) { - client, err := NewClient(ctx, wrapDmsgC(dmsgC), pk) - if err != nil { - return false, fmt.Errorf("failed to dial remote: %v", err) - } - - defer closeClient(log, client) - - ok, err := client.AddEdgeRules(ctx, rules) - if err != nil { - return false, fmt.Errorf("failed to add rules: %v", err) - } - - return ok, nil -} - -// AddIntermediaryRules is a wrapper for (*Client).AddIntermediaryRules. -func AddIntermediaryRules( - ctx context.Context, - log *logging.Logger, - dmsgC *dmsg.Client, - pk cipher.PubKey, - rules []routing.Rule, -) (bool, error) { - client, err := NewClient(ctx, wrapDmsgC(dmsgC), pk) - if err != nil { - return false, fmt.Errorf("failed to dial remote: %v", err) - } - - defer closeClient(log, client) - - routeIDs, err := client.AddIntermediaryRules(ctx, rules) - if err != nil { - return false, fmt.Errorf("failed to add rules: %v", err) - } - - return routeIDs, nil -} - -// ReserveIDs is a wrapper for (*Client).ReserveIDs. -func ReserveIDs( - ctx context.Context, - log *logging.Logger, - dmsgC *dmsg.Client, - pk cipher.PubKey, - n uint8, -) ([]routing.RouteID, error) { - client, err := NewClient(ctx, wrapDmsgC(dmsgC), pk) - if err != nil { - return nil, fmt.Errorf("failed to dial remote: %v", err) - } - - defer closeClient(log, client) - - routeIDs, err := client.ReserveIDs(ctx, n) - if err != nil { - return nil, fmt.Errorf("failed to add rules: %v", err) - } - - return routeIDs, nil -} - -func closeClient(log *logging.Logger, client *Client) { - if err := client.Close(); err != nil { - log.Warn(err) - } -} diff --git a/pkg/routing/route.go b/pkg/routing/route.go index 2e7c0607d3..9f91a91134 100644 --- a/pkg/routing/route.go +++ b/pkg/routing/route.go @@ -4,6 +4,8 @@ package routing import ( "bytes" + "encoding/json" + "errors" "fmt" "time" @@ -14,44 +16,66 @@ import ( // Route is a succession of transport entries that denotes a path from source visor to destination visor type Route struct { Desc RouteDescriptor `json:"desc"` - Path Path `json:"path"` + Hops []Hop `json:"path"` KeepAlive time.Duration `json:"keep_alive"` } func (r Route) String() string { res := fmt.Sprintf("[KeepAlive: %s] %s\n", r.KeepAlive, r.Desc.String()) - for _, hop := range r.Path { + for _, hop := range r.Hops { res += fmt.Sprintf("\t%s\n", hop) } return res } +// Errors associated with BidirectionalRoute +var ( + ErrBiRouteHasNoForwardHops = errors.New("bidirectional route does not have forward hops") + ErrBiRouteHasNoReverseHops = errors.New("bidirectional route does not have reverse hops") + ErrBiRouteHasInvalidDesc = errors.New("bidirectional route has an invalid route description") +) + // BidirectionalRoute is a Route with both forward and reverse Paths. type BidirectionalRoute struct { Desc RouteDescriptor KeepAlive time.Duration - Forward Path - Reverse Path + Forward []Hop + Reverse []Hop } // ForwardAndReverse generate forward and reverse routes for bidirectional route. func (br *BidirectionalRoute) ForwardAndReverse() (forward, reverse Route) { forwardRoute := Route{ Desc: br.Desc, - Path: br.Forward, + Hops: br.Forward, KeepAlive: br.KeepAlive, } - reverseRoute := Route{ Desc: br.Desc.Invert(), - Path: br.Reverse, + Hops: br.Reverse, KeepAlive: br.KeepAlive, } - return forwardRoute, reverseRoute } +// Check checks whether the bidirectional route is valid. +func (br *BidirectionalRoute) Check() error { + if len(br.Forward) == 0 { + return ErrBiRouteHasNoForwardHops + } + if len(br.Reverse) == 0 { + return ErrBiRouteHasNoReverseHops + } + if srcPK := br.Desc.SrcPK(); br.Forward[0].From != srcPK || br.Reverse[len(br.Reverse)-1].To != srcPK { + return ErrBiRouteHasInvalidDesc + } + if dstPK := br.Desc.DstPK(); br.Reverse[0].From != dstPK || br.Forward[len(br.Forward)-1].To != dstPK { + return ErrBiRouteHasInvalidDesc + } + return nil +} + // EdgeRules represents edge forward and reverse rules. Edge rules are forward and consume rules. type EdgeRules struct { Desc RouteDescriptor @@ -59,6 +83,22 @@ type EdgeRules struct { Reverse Rule } +// String implements fmt.Stringer +func (er EdgeRules) String() string { + m := map[string]interface{}{ + "descriptor": er.Desc.String(), + "routing_rules": []string{ + er.Forward.String(), + er.Reverse.String(), + }, + } + j, err := json.MarshalIndent(m, "", "\t") + if err != nil { + panic(err) + } + return string(j) +} + // Hop defines a route hop between 2 nodes. type Hop struct { TpID uuid.UUID @@ -66,9 +106,7 @@ type Hop struct { To cipher.PubKey } -// Path is a list of hops between nodes (transports), and indicates a route between the edges -type Path []Hop - +// String implements fmt.Stringer func (h Hop) String() string { return fmt.Sprintf("%s -> %s @ %s", h.From, h.To, h.TpID) } diff --git a/pkg/routing/route_descriptor.go b/pkg/routing/route_descriptor.go index 047b86276f..5ccf7633d1 100644 --- a/pkg/routing/route_descriptor.go +++ b/pkg/routing/route_descriptor.go @@ -92,5 +92,5 @@ func (rd *RouteDescriptor) Invert() RouteDescriptor { } func (rd *RouteDescriptor) String() string { - return fmt.Sprintf("rPK:%s, lPK:%s, rPort:%d, lPort:%d", rd.DstPK(), rd.SrcPK(), rd.DstPort(), rd.SrcPort()) + return fmt.Sprintf("rAddr:%s, lAddr:%s", rd.Dst().String(), rd.Src().String()) } diff --git a/pkg/routing/rule.go b/pkg/routing/rule.go index e5d1f04f18..a6f4767153 100644 --- a/pkg/routing/rule.go +++ b/pkg/routing/rule.go @@ -25,11 +25,11 @@ type RuleType byte func (rt RuleType) String() string { switch rt { - case RuleConsume: + case RuleReverse: return "Consume" case RuleForward: return "Forward" - case RuleIntermediaryForward: + case RuleIntermediary: return "IntermediaryForward" } @@ -37,17 +37,17 @@ func (rt RuleType) String() string { } const ( - // RuleConsume represents a hop to the route's destination visor. + // RuleReverse represents a hop to the route's destination visor. // A packet referencing this rule is to be consumed locally. - RuleConsume = RuleType(0) + RuleReverse = RuleType(0) // RuleForward represents a hop from the route's source visor. // A packet referencing this rule is to be sent to a remote visor. RuleForward = RuleType(1) - // RuleIntermediaryForward represents a hop which is not from the route's source, + // RuleIntermediary represents a hop which is not from the route's source, // nor to the route's destination. - RuleIntermediaryForward = RuleType(2) + RuleIntermediary = RuleType(2) ) // Rule represents a routing rule. @@ -111,7 +111,7 @@ func (r Rule) Body() []byte { // RouteDescriptor returns RouteDescriptor from the rule. func (r Rule) RouteDescriptor() RouteDescriptor { switch t := r.Type(); t { - case RuleConsume, RuleForward: + case RuleReverse, RuleForward: r.assertLen(RuleHeaderSize + routeDescriptorSize) var desc RouteDescriptor @@ -132,7 +132,7 @@ func (r Rule) NextRouteID() RouteID { case RuleForward: offset += routeDescriptorSize fallthrough - case RuleIntermediaryForward: + case RuleIntermediary: r.assertLen(offset + 4) return RouteID(binary.BigEndian.Uint32(r[offset : offset+4])) default: @@ -148,7 +148,7 @@ func (r Rule) setNextRouteID(id RouteID) { case RuleForward: offset += routeDescriptorSize fallthrough - case RuleIntermediaryForward: + case RuleIntermediary: r.assertLen(offset + 4) binary.BigEndian.PutUint32(r[offset:offset+4], uint32(id)) default: @@ -164,7 +164,7 @@ func (r Rule) NextTransportID() uuid.UUID { case RuleForward: offset += routeDescriptorSize fallthrough - case RuleIntermediaryForward: + case RuleIntermediary: r.assertLen(offset + 4) return uuid.Must(uuid.FromBytes(r[offset : offset+uuidSize])) @@ -181,7 +181,7 @@ func (r Rule) setNextTransportID(id uuid.UUID) { case RuleForward: offset += routeDescriptorSize fallthrough - case RuleIntermediaryForward: + case RuleIntermediary: r.assertLen(offset + 4) copy(r[offset:offset+uuidSize], id[:]) default: @@ -192,7 +192,7 @@ func (r Rule) setNextTransportID(id uuid.UUID) { // setSrcPK sets source public key of a rule. func (r Rule) setSrcPK(pk cipher.PubKey) { switch t := r.Type(); t { - case RuleConsume, RuleForward: + case RuleReverse, RuleForward: r.assertLen(RuleHeaderSize + pkSize) copy(r[RuleHeaderSize:RuleHeaderSize+pkSize], pk[:]) default: @@ -203,7 +203,7 @@ func (r Rule) setSrcPK(pk cipher.PubKey) { // setDstPK sets destination public key of a rule. func (r Rule) setDstPK(pk cipher.PubKey) { switch t := r.Type(); t { - case RuleConsume, RuleForward: + case RuleReverse, RuleForward: r.assertLen(RuleHeaderSize + pkSize*2) copy(r[RuleHeaderSize+pkSize:RuleHeaderSize+pkSize*2], pk[:]) default: @@ -214,7 +214,7 @@ func (r Rule) setDstPK(pk cipher.PubKey) { // setSrcPort sets source port of a rule. func (r Rule) setSrcPort(port Port) { switch t := r.Type(); t { - case RuleConsume, RuleForward: + case RuleReverse, RuleForward: r.assertLen(RuleHeaderSize + pkSize*2 + 2) binary.BigEndian.PutUint16(r[RuleHeaderSize+pkSize*2:RuleHeaderSize+pkSize*2+2], uint16(port)) default: @@ -225,7 +225,7 @@ func (r Rule) setSrcPort(port Port) { // setDstPort sets destination port of a rule. func (r Rule) setDstPort(port Port) { switch t := r.Type(); t { - case RuleConsume, RuleForward: + case RuleReverse, RuleForward: r.assertLen(RuleHeaderSize + pkSize*2 + 2*2) binary.BigEndian.PutUint16(r[RuleHeaderSize+pkSize*2+2:RuleHeaderSize+pkSize*2+2*2], uint16(port)) default: @@ -236,16 +236,16 @@ func (r Rule) setDstPort(port Port) { // String returns rule's string representation. func (r Rule) String() string { switch t := r.Type(); t { - case RuleConsume: + case RuleReverse: rd := r.RouteDescriptor() - return fmt.Sprintf("APP(keyRtID:%d, %s)", + return fmt.Sprintf("REV(keyRtID:%d, %s)", r.KeyRouteID(), rd.String()) case RuleForward: rd := r.RouteDescriptor() return fmt.Sprintf("FWD(keyRtID:%d, nxtRtID:%d, nxtTpID:%s, %s)", r.KeyRouteID(), r.NextRouteID(), r.NextTransportID(), rd.String()) - case RuleIntermediaryForward: - return fmt.Sprintf("IFWD(keyRtID:%d, nxtRtID:%d, nxtTpID:%s)", + case RuleIntermediary: + return fmt.Sprintf("INTER(keyRtID:%d, nxtRtID:%d, nxtTpID:%s)", r.KeyRouteID(), r.NextRouteID(), r.NextTransportID()) default: panic(fmt.Sprintf("invalid rule: %v", t.String())) @@ -291,7 +291,7 @@ type RuleSummary struct { // ToRule converts RoutingRuleSummary to RoutingRule. func (rs *RuleSummary) ToRule() (Rule, error) { switch { - case rs.Type == RuleConsume: + case rs.Type == RuleReverse: if rs.ConsumeFields == nil || rs.ForwardFields != nil || rs.IntermediaryForwardFields != nil { return nil, errors.New("invalid routing rule summary") } @@ -309,7 +309,7 @@ func (rs *RuleSummary) ToRule() (Rule, error) { d := f.RouteDescriptor return ForwardRule(rs.KeepAlive, rs.KeyRouteID, f.NextRID, f.NextTID, d.SrcPK, d.DstPK, d.SrcPort, d.DstPort), nil - case rs.Type == RuleIntermediaryForward: + case rs.Type == RuleIntermediary: if rs.ConsumeFields != nil || rs.ForwardFields != nil || rs.IntermediaryForwardFields == nil { return nil, errors.New("invalid routing rule summary") } @@ -331,7 +331,7 @@ func (r Rule) Summary() *RuleSummary { } switch t := summary.Type; t { - case RuleConsume: + case RuleReverse: rd := r.RouteDescriptor() summary.ConsumeFields = &RuleConsumeFields{ @@ -355,7 +355,7 @@ func (r Rule) Summary() *RuleSummary { NextRID: r.NextRouteID(), NextTID: r.NextTransportID(), } - case RuleIntermediaryForward: + case RuleIntermediary: summary.IntermediaryForwardFields = &RuleIntermediaryForwardFields{ NextRID: r.NextRouteID(), NextTID: r.NextTransportID(), @@ -372,7 +372,7 @@ func ConsumeRule(keepAlive time.Duration, key RouteID, lPK, rPK cipher.PubKey, l rule := Rule(make([]byte, RuleHeaderSize+routeDescriptorSize)) rule.setKeepAlive(keepAlive) - rule.setType(RuleConsume) + rule.setType(RuleReverse) rule.SetKeyRouteID(key) rule.setSrcPK(lPK) @@ -412,7 +412,7 @@ func IntermediaryForwardRule(keepAlive time.Duration, key, nextRoute RouteID, ne rule := Rule(make([]byte, RuleHeaderSize+4+pkSize)) rule.setKeepAlive(keepAlive) - rule.setType(RuleIntermediaryForward) + rule.setType(RuleIntermediary) rule.SetKeyRouteID(key) rule.setNextRouteID(nextRoute) rule.setNextTransportID(nextTransport) diff --git a/pkg/routing/rule_test.go b/pkg/routing/rule_test.go index c60d0e5c2a..11833fd7c9 100644 --- a/pkg/routing/rule_test.go +++ b/pkg/routing/rule_test.go @@ -17,7 +17,7 @@ func TestConsumeRule(t *testing.T) { rule := ConsumeRule(keepAlive, 1, localPK, remotePK, 2, 3) assert.Equal(t, keepAlive, rule.KeepAlive()) - assert.Equal(t, RuleConsume, rule.Type()) + assert.Equal(t, RuleReverse, rule.Type()) assert.Equal(t, RouteID(1), rule.KeyRouteID()) rd := rule.RouteDescriptor() @@ -59,7 +59,7 @@ func TestIntermediaryForwardRule(t *testing.T) { rule := IntermediaryForwardRule(keepAlive, 1, 2, trID) assert.Equal(t, keepAlive, rule.KeepAlive()) - assert.Equal(t, RuleIntermediaryForward, rule.Type()) + assert.Equal(t, RuleIntermediary, rule.Type()) assert.Equal(t, RouteID(1), rule.KeyRouteID()) assert.Equal(t, RouteID(2), rule.NextRouteID()) assert.Equal(t, trID, rule.NextTransportID()) diff --git a/pkg/routing/table.go b/pkg/routing/table.go index 0b11d5af68..5f8b173f5b 100644 --- a/pkg/routing/table.go +++ b/pkg/routing/table.go @@ -123,7 +123,7 @@ func (mt *memTable) Rule(key RouteID) (Rule, error) { // crucial, we do this when we have nowhere in the network to forward packet to. // In this case we update activity immediately not to acquire the lock for the second time ruleType := rule.Type() - if ruleType == RuleConsume { + if ruleType == RuleReverse { mt.activity[key] = time.Now() } diff --git a/pkg/setup/config.go b/pkg/setup/config.go index 17add59953..a125f84b87 100644 --- a/pkg/setup/config.go +++ b/pkg/setup/config.go @@ -18,12 +18,9 @@ const ( // Config defines configuration parameters for setup Node. type Config struct { - PubKey cipher.PubKey `json:"public_key"` - SecKey cipher.SecKey `json:"secret_key"` - - Dmsg snet.DmsgConfig `json:"dmsg"` - - TransportDiscovery string `json:"transport_discovery"` - - LogLevel string `json:"log_level"` + PK cipher.PubKey `json:"public_key"` + SK cipher.SecKey `json:"secret_key"` + Dmsg snet.DmsgConfig `json:"dmsg"` + TransportDiscovery string `json:"transport_discovery"` + LogLevel string `json:"log_level"` } diff --git a/pkg/setup/id_reserver.go b/pkg/setup/id_reserver.go new file mode 100644 index 0000000000..b866674227 --- /dev/null +++ b/pkg/setup/id_reserver.go @@ -0,0 +1,158 @@ +package setup + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "sync" + + "github.com/SkycoinProject/dmsg/cipher" + + "github.com/SkycoinProject/skywire-mainnet/pkg/router/routerclient" + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" +) + +// ErrNoKey is returned when key is not found. +var ErrNoKey = errors.New("id reservoir has no key") + +//go:generate mockery -name IDReserver -case underscore -inpkg + +// IDReserver reserves route IDs from remote routers. +// It takes in a slice of paths where each path is a slice of hops and each hop has src and dst public keys and also a +// transport ID. +type IDReserver interface { + io.Closer + fmt.Stringer + + // ReserveIDs reserves route IDs from the router clients. + // It uses an internal map to know how many ids to reserve from each router. + ReserveIDs(ctx context.Context) error + + // PopID pops a reserved route ID from the ID stack of the given public key. + PopID(pk cipher.PubKey) (routing.RouteID, bool) + + // TotalIDs returns the total number of route IDs we have reserved from the routers. + TotalIDs() int + + // Client returns a router client of given public key. + Client(pk cipher.PubKey) *routerclient.Client +} + +type idReserver struct { + total int // the total number of route IDs we reserve from the routers + rcM routerclient.Map // map of router clients + rec map[cipher.PubKey]uint8 // this records the number of expected rules per visor PK + ids map[cipher.PubKey][]routing.RouteID // this records the obtained rules per visor PK + mx sync.Mutex +} + +// NewIDReserver creates a new route ID reserver from a dialer and a slice of paths. +// The exact number of route IDs to reserve from each router is determined from the slice of paths. +func NewIDReserver(ctx context.Context, dialer snet.Dialer, paths [][]routing.Hop) (IDReserver, error) { + var total int // the total number of route IDs we reserve from the routers + + // Prepare 'rec': A map representing the number of expected rules per visor PK. + rec := make(map[cipher.PubKey]uint8) + for _, hops := range paths { + if len(hops) == 0 { + continue + } + rec[hops[0].From]++ + for _, hop := range hops { + rec[hop.To]++ + } + total += len(hops) + 1 + } + + // Prepare 'clients': A map of router clients. + pks := make([]cipher.PubKey, 0, len(rec)) + for pk := range rec { + pks = append(pks, pk) + } + clients, err := routerclient.MakeMap(ctx, dialer, pks) + if err != nil { + return nil, fmt.Errorf("a dial attempt failed with: %v", err) + } + + // Return result. + return &idReserver{ + total: total, + rcM: clients, + rec: rec, + ids: make(map[cipher.PubKey][]routing.RouteID, total), + }, nil +} + +func (idr *idReserver) ReserveIDs(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + errCh := make(chan error, len(idr.rec)) + defer close(errCh) + + for pk, n := range idr.rec { + go func(pk cipher.PubKey, n uint8) { + rtIDs, err := idr.rcM.Client(pk).ReserveIDs(ctx, n) + if err != nil { + cancel() + errCh <- fmt.Errorf("reserve routeID from %s failed: %w", pk, err) + return + } + idr.mx.Lock() + idr.ids[pk] = rtIDs + idr.mx.Unlock() + errCh <- nil + }(pk, n) + } + + return firstError(len(idr.rec), errCh) +} + +func (idr *idReserver) PopID(pk cipher.PubKey) (routing.RouteID, bool) { + idr.mx.Lock() + defer idr.mx.Unlock() + + ids, ok := idr.ids[pk] + if !ok || len(ids) == 0 { + return 0, false + } + + idr.ids[pk] = ids[1:] + + return ids[0], true +} + +func (idr *idReserver) TotalIDs() int { + return idr.total +} + +func (idr *idReserver) Client(pk cipher.PubKey) *routerclient.Client { + return idr.rcM[pk] +} + +func (idr *idReserver) String() string { + idr.mx.Lock() + defer idr.mx.Unlock() + b, _ := json.MarshalIndent(idr.ids, "", "\t") //nolint:errcheck + return string(b) +} + +func (idr *idReserver) Close() error { + if errs := idr.rcM.CloseAll(); errs != nil { + return fmt.Errorf("router client map closed with errors: %v", errs) + } + return nil +} + +func firstError(n int, errCh <-chan error) error { + var firstErr error + for i := 0; i < n; i++ { + if err := <-errCh; firstErr == nil && err != nil { + firstErr = err + } + } + return firstErr +} diff --git a/pkg/setup/id_reserver_test.go b/pkg/setup/id_reserver_test.go new file mode 100644 index 0000000000..4ccfad23e2 --- /dev/null +++ b/pkg/setup/id_reserver_test.go @@ -0,0 +1,237 @@ +package setup + +import ( + "context" + "errors" + "strconv" + "testing" + "time" + + "github.com/SkycoinProject/dmsg/cipher" + "github.com/google/uuid" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" +) + +// We check the contents of 'idReserver.rec' is as expected after calling 'NewIDReserver'. +// We assume that all dials by the dialer are successful. +func TestNewIDReserver(t *testing.T) { + pkA, _ := cipher.GenerateKeyPair() + pkB, _ := cipher.GenerateKeyPair() + pkC, _ := cipher.GenerateKeyPair() + + type testCase struct { + paths [][]routing.Hop // test input + expRec map[cipher.PubKey]uint8 // expected 'idReserver.rec' result + expTotal int // expected 'idReserver.total' result + } + + testCases := []testCase{ + { + paths: nil, + expRec: map[cipher.PubKey]uint8{}, + expTotal: 0, + }, + { + paths: [][]routing.Hop{makeHops(pkA, pkB), makeHops(pkB, pkA)}, + expRec: map[cipher.PubKey]uint8{pkA: 2, pkB: 2}, + expTotal: 4, + }, + { + paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkA)}, + expRec: map[cipher.PubKey]uint8{pkA: 2, pkB: 1, pkC: 2}, + expTotal: 5, + }, + { + paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, + expRec: map[cipher.PubKey]uint8{pkA: 2, pkB: 2, pkC: 2}, + expTotal: 6, + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + // arrange + dialer := newMockDialer(t, nil) + + // act + rtIDR, err := NewIDReserver(context.TODO(), dialer, tc.paths) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, rtIDR.Close()) }) + + // assert + v := rtIDR.(*idReserver) + assert.Equal(t, tc.expTotal, v.total) + assert.Equal(t, tc.expRec, v.rec) + }) + } +} + +func TestIdReserver_ReserveIDs(t *testing.T) { + pkA, _ := cipher.GenerateKeyPair() + pkB, _ := cipher.GenerateKeyPair() + pkC, _ := cipher.GenerateKeyPair() + + // timeout given for all calls to .ReserveIDs + // this is passed with a context with deadline + timeout := time.Second + + type testCase struct { + testName string // test name + routers map[cipher.PubKey]*mockGatewayForDialer // arrange: map of mock router gateways + paths [][]routing.Hop // arrange: idReserver input + expErr error // assert: expected error + } + + makeRun := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + // arrange + dialer := newMockDialer(t, tc.routers) + + rtIDR, err := NewIDReserver(context.TODO(), dialer, tc.paths) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, rtIDR.Close()) }) + + ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(timeout)) + defer cancel() + + // act + err = rtIDR.ReserveIDs(ctx) + + if tc.expErr != nil { + // assert (expected error) + assert.EqualError(t, errors.Unwrap(err), context.DeadlineExceeded.Error()) + } else { + // assert (no expected error) + checkIDReserver(t, rtIDR.(*idReserver)) + } + } + } + + // .ReserveIDs should correctly reserve IDs if all remote routers are functional. + t.Run("correctly_reserve_rtIDs", func(t *testing.T) { + testCases := []testCase{ + { + testName: "fwd1_rev1", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {}, + pkC: {}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkA)}, + expErr: nil, + }, + { + testName: "fwd2_rev2", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {}, + pkB: {}, + pkC: {}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, + expErr: nil, + }, + { + testName: "fwd1_rev2", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {}, + pkB: {}, + pkC: {}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkB, pkA)}, + expErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, makeRun(tc)) + } + }) + + // Calling .ReserveIDs should never hang indefinitely if we set context with timeout. + // We set this by providing a context.Context with a timeout. + // Hence, .ReserveIDs should return context.DeadlineExceeded when the timeout triggers. + // Any other errors, or further delays is considered a failure. + t.Run("no_hangs_with_ctx_timeout", func(t *testing.T) { + testCases := []testCase{ + { + testName: "all_routers_hang", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {hangDuration: time.Second * 5}, + pkC: {hangDuration: time.Second * 5}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkA)}, + expErr: context.DeadlineExceeded, + }, + { + testName: "intermediary_router_hangs", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {}, + pkB: {hangDuration: time.Second * 5}, + pkC: {}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, + expErr: context.DeadlineExceeded, + }, + { + testName: "initiating_router_hangs", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {hangDuration: time.Second * 5}, + pkB: {}, + pkC: {}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkB, pkA)}, + expErr: context.DeadlineExceeded, + }, + { + testName: "responding_router_hangs", + routers: map[cipher.PubKey]*mockGatewayForDialer{ + pkA: {}, + pkB: {}, + pkC: {hangDuration: time.Second * 5}, + }, + paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, + expErr: context.DeadlineExceeded, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, makeRun(tc)) + } + }) +} + +// makes a slice of hops from pks +func makeHops(pks ...cipher.PubKey) []routing.Hop { + hops := make([]routing.Hop, len(pks)-1) + for i, pk := range pks[:len(pks)-1] { + hops[i] = routing.Hop{ + TpID: uuid.New(), + From: pk, + To: pks[i+1], + } + } + return hops +} + +// ensures that the internal idReserver.rec and idReserver.ids match up +func checkIDReserver(t *testing.T, rtIDR *idReserver) { + assert.Equal(t, len(rtIDR.rec), len(rtIDR.ids)) + + // ensure values of .ids are okay + for pk, rec := range rtIDR.rec { + ids, ok := rtIDR.ids[pk] + + assert.True(t, ok) + assert.Len(t, ids, int(rec)) + + // ensure there are no duplicates in 'ids' + idMap := make(map[routing.RouteID]struct{}, len(ids)) + for _, id := range ids { + idMap[id] = struct{}{} + } + assert.Len(t, idMap, len(ids)) + } +} diff --git a/pkg/setup/idreservoir.go b/pkg/setup/idreservoir.go deleted file mode 100644 index 872195f764..0000000000 --- a/pkg/setup/idreservoir.go +++ /dev/null @@ -1,200 +0,0 @@ -package setup - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "sync" - - "github.com/SkycoinProject/dmsg" - "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" - - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" -) - -// ErrNoKey is returned when key is not found. -var ErrNoKey = errors.New("id reservoir has no key") - -type idReservoir struct { - rec map[cipher.PubKey]uint8 - ids map[cipher.PubKey][]routing.RouteID - mx sync.Mutex -} - -func newIDReservoir(paths ...routing.Path) (*idReservoir, int) { - rec := make(map[cipher.PubKey]uint8) - - var total int - - for _, path := range paths { - if len(path) == 0 { - continue - } - - rec[path[0].From]++ - - for _, hop := range path { - rec[hop.To]++ - } - - total += len(path) + 1 - } - - return &idReservoir{ - rec: rec, - ids: make(map[cipher.PubKey][]routing.RouteID), - }, total -} - -type reserveFunc func( - ctx context.Context, - log *logging.Logger, - dmsgC *dmsg.Client, - pk cipher.PubKey, - n uint8, -) ([]routing.RouteID, error) - -func (idr *idReservoir) ReserveIDs( - ctx context.Context, - log *logging.Logger, - dmsgC *dmsg.Client, - reserve reserveFunc, -) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - errCh := make(chan error, len(idr.rec)) - defer close(errCh) - - for pk, n := range idr.rec { - go func(pk cipher.PubKey, n uint8) { - ids, err := reserve(ctx, log, dmsgC, pk, n) - if err != nil { - errCh <- fmt.Errorf("reserve routeID from %s failed: %v", pk, err) - return - } - idr.mx.Lock() - idr.ids[pk] = ids - idr.mx.Unlock() - errCh <- nil - }(pk, n) - } - - return finalError(len(idr.rec), errCh) -} - -func (idr *idReservoir) PopID(pk cipher.PubKey) (routing.RouteID, bool) { - idr.mx.Lock() - defer idr.mx.Unlock() - - ids, ok := idr.ids[pk] - if !ok || len(ids) == 0 { - return 0, false - } - - idr.ids[pk] = ids[1:] - - return ids[0], true -} - -func (idr *idReservoir) String() string { - idr.mx.Lock() - defer idr.mx.Unlock() - - b, _ := json.MarshalIndent(idr.ids, "", "\t") //nolint:errcheck - - return string(b) -} - -// RuleMap associates a rule to a visor's public key. -type RuleMap map[cipher.PubKey]routing.Rule - -// RulesMap associates a slice of rules to a visor's public key. -type RulesMap map[cipher.PubKey][]routing.Rule - -func (rm RulesMap) String() string { - out := make(map[cipher.PubKey][]string, len(rm)) - - for pk, rules := range rm { - str := make([]string, len(rules)) - for i, rule := range rules { - str[i] = rule.String() - } - - out[pk] = str - } - - jb, err := json.MarshalIndent(out, "", "\t") - if err != nil { - panic(err) - } - - return string(jb) -} - -// GenerateRules generates rules for given forward and reverse routes. -// The outputs are as follows: -// - maps that relate slices of forward, consume and intermediary routing rules to a given visor's public key. -// - an error (if any). -func (idr *idReservoir) GenerateRules(fwd, rev routing.Route) ( - forwardRules, consumeRules RuleMap, - intermediaryRules RulesMap, - err error, -) { - forwardRules = make(RuleMap) - consumeRules = make(RuleMap) - intermediaryRules = make(RulesMap) - - for _, route := range []routing.Route{fwd, rev} { - // 'firstRID' is the first visor's key routeID - firstRID, ok := idr.PopID(route.Path[0].From) - if !ok { - return nil, nil, nil, ErrNoKey - } - - desc := route.Desc - srcPK := desc.SrcPK() - dstPK := desc.DstPK() - srcPort := desc.SrcPort() - dstPort := desc.DstPort() - - var rID = firstRID - - for i, hop := range route.Path { - nxtRID, ok := idr.PopID(hop.To) - if !ok { - return nil, nil, nil, ErrNoKey - } - - if i == 0 { - rule := routing.ForwardRule(route.KeepAlive, rID, nxtRID, hop.TpID, srcPK, dstPK, srcPort, dstPort) - forwardRules[hop.From] = rule - } else { - rule := routing.IntermediaryForwardRule(route.KeepAlive, rID, nxtRID, hop.TpID) - intermediaryRules[hop.From] = append(intermediaryRules[hop.From], rule) - } - - rID = nxtRID - } - - fmt.Printf("GENERATING CONSUME RULE WITH SRC %s\n", srcPK) - rule := routing.ConsumeRule(route.KeepAlive, rID, srcPK, dstPK, srcPort, dstPort) - consumeRules[dstPK] = rule - } - - return forwardRules, consumeRules, intermediaryRules, nil -} - -func finalError(n int, errCh <-chan error) error { - var finalErr error - - for i := 0; i < n; i++ { - if err := <-errCh; err != nil { - finalErr = err - } - } - - return finalErr -} diff --git a/pkg/setup/mock_id_reserver.go b/pkg/setup/mock_id_reserver.go new file mode 100644 index 0000000000..15bf8899f4 --- /dev/null +++ b/pkg/setup/mock_id_reserver.go @@ -0,0 +1,113 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package setup + +import ( + context "context" + + cipher "github.com/SkycoinProject/dmsg/cipher" + + mock "github.com/stretchr/testify/mock" + + routerclient "github.com/SkycoinProject/skywire-mainnet/pkg/router/routerclient" + + routing "github.com/SkycoinProject/skywire-mainnet/pkg/routing" +) + +// MockIDReserver is an autogenerated mock type for the IDReserver type +type MockIDReserver struct { + mock.Mock +} + +// Client provides a mock function with given fields: pk +func (_m *MockIDReserver) Client(pk cipher.PubKey) *routerclient.Client { + ret := _m.Called(pk) + + var r0 *routerclient.Client + if rf, ok := ret.Get(0).(func(cipher.PubKey) *routerclient.Client); ok { + r0 = rf(pk) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*routerclient.Client) + } + } + + return r0 +} + +// Close provides a mock function with given fields: +func (_m *MockIDReserver) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// PopID provides a mock function with given fields: pk +func (_m *MockIDReserver) PopID(pk cipher.PubKey) (routing.RouteID, bool) { + ret := _m.Called(pk) + + var r0 routing.RouteID + if rf, ok := ret.Get(0).(func(cipher.PubKey) routing.RouteID); ok { + r0 = rf(pk) + } else { + r0 = ret.Get(0).(routing.RouteID) + } + + var r1 bool + if rf, ok := ret.Get(1).(func(cipher.PubKey) bool); ok { + r1 = rf(pk) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// ReserveIDs provides a mock function with given fields: ctx +func (_m *MockIDReserver) ReserveIDs(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// String provides a mock function with given fields: +func (_m *MockIDReserver) String() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// TotalIDs provides a mock function with given fields: +func (_m *MockIDReserver) TotalIDs() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} diff --git a/pkg/setup/node.go b/pkg/setup/node.go index a7ba8f67a5..4171820fc6 100644 --- a/pkg/setup/node.go +++ b/pkg/setup/node.go @@ -4,66 +4,50 @@ import ( "context" "fmt" "net/rpc" - "sync" "time" "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/disc" "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/metrics" "github.com/SkycoinProject/skywire-mainnet/pkg/router/routerclient" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" ) +var log = logging.MustGetLogger("setup_node") + // Node performs routes setup operations over messaging channel. type Node struct { - logger *logging.Logger - dmsgC *dmsg.Client - dmsgL *dmsg.Listener - sessionsCount int - metrics metrics.Recorder + dmsgC *dmsg.Client + metrics metrics.Recorder } // NewNode constructs a new SetupNode. func NewNode(conf *Config, metrics metrics.Recorder) (*Node, error) { - logger := logging.NewMasterLogger() - if lvl, err := logging.LevelFromString(conf.LogLevel); err == nil { - logger.SetLevel(lvl) + logging.SetLevel(lvl) } - log := logger.PackageLogger("setup_node") - - // Prepare dmsg. - dmsgC := dmsg.NewClient( - conf.PubKey, - conf.SecKey, - disc.NewHTTP(conf.Dmsg.Discovery), - &dmsg.Config{MinSessions: conf.Dmsg.SessionsCount}, - ) - dmsgC.SetLogger(logger.PackageLogger(dmsg.Type)) - + // Connect to dmsg network. + dmsgDisc := disc.NewHTTP(conf.Dmsg.Discovery) + dmsgConf := &dmsg.Config{MinSessions: conf.Dmsg.SessionsCount} + dmsgC := dmsg.NewClient(conf.PK, conf.SK, dmsgDisc, dmsgConf) go dmsgC.Serve() - log.Info("connected to dmsg servers") - - dmsgL, err := dmsgC.Listen(skyenv.DmsgSetupPort) - if err != nil { - return nil, fmt.Errorf("failed to listen on dmsg port %d: %v", skyenv.DmsgSetupPort, dmsgL) - } - - log.Info("started listening for dmsg connections") + log.WithField("local_pk", conf.PK).WithField("dmsg_conf", conf.Dmsg). + Info("Connecting to the dmsg network.") + <-dmsgC.Ready() + log.Info("Connected!") node := &Node{ - logger: log, - dmsgC: dmsgC, - dmsgL: dmsgL, - sessionsCount: conf.Dmsg.SessionsCount, - metrics: metrics, + dmsgC: dmsgC, + metrics: metrics, } - return node, nil } @@ -72,125 +56,214 @@ func (sn *Node) Close() error { if sn == nil { return nil } - return sn.dmsgC.Close() } // Serve starts transport listening loop. -func (sn *Node) Serve() error { - sn.logger.Info("Serving setup node") +func (sn *Node) Serve(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + const dmsgPort = skyenv.DmsgSetupPort + const timeout = 30 * time.Second + + log.WithField("dmsg_port", dmsgPort).Info("Starting listener.") + lis, err := sn.dmsgC.Listen(skyenv.DmsgSetupPort) + if err != nil { + return fmt.Errorf("failed to listen on dmsg port %d: %v", skyenv.DmsgSetupPort, lis) + } + defer func() { + if err := lis.Close(); err != nil { + log.WithError(err).Warn("Dmsg listener closed with non-nil error.") + } + }() + + log.WithField("dmsg_port", dmsgPort).Info("Accepting dmsg streams.") for { - conn, err := sn.dmsgL.AcceptStream() + conn, err := lis.AcceptStream() if err != nil { return err } - - remote := conn.RemoteAddr().(dmsg.Addr) - sn.logger.WithField("requester", remote.PK).Infof("Received request.") - - const timeout = 30 * time.Second - + gw := &RPCGateway{ + Metrics: sn.metrics, + Ctx: ctx, + Conn: conn, + ReqPK: conn.RemoteAddr().(dmsg.Addr).PK, + Dialer: routerclient.WrapDmsgClient(sn.dmsgC), + Timeout: timeout, + } rpcS := rpc.NewServer() - if err := rpcS.Register(NewRPCGateway(remote.PK, sn, timeout)); err != nil { + if err := rpcS.Register(gw); err != nil { return err } - go rpcS.ServeConn(conn) } } -func (sn *Node) handleDialRouteGroup(ctx context.Context, route routing.BidirectionalRoute) (routing.EdgeRules, error) { - sn.logger.Infof("Setup route from %s to %s", route.Desc.SrcPK(), route.Desc.DstPK()) +// CreateRouteGroup creates a route group by communicating with routers used within the bidirectional route. +// The following steps are taken: +// * Check the validity of bi route input. +// * Route IDs are reserved from the routers. +// * Intermediary rules are broadcasted to the intermediary routers. +// * Edge rules are broadcasted to the responding router. +// * Edge rules is returned (to the initiating router). +func CreateRouteGroup(ctx context.Context, dialer snet.Dialer, biRt routing.BidirectionalRoute) (resp routing.EdgeRules, err error) { + start := time.Now() + log := logging.MustGetLogger(fmt.Sprintf("request:%s->%s", biRt.Desc.SrcPK(), biRt.Desc.DstPK())) + log.Info("Processing request.") + defer func() { + elapsed := time.Since(start) + log := log.WithField("elapsed", elapsed) + if err != nil { + log.WithError(err).Warn("Request processed with error.") + } else { + log.Info("Request processed successfully.") + } + }() - idr, err := sn.reserveRouteIDs(ctx, route) - if err != nil { + // Ensure bi routes input is valid. + if err = biRt.Check(); err != nil { return routing.EdgeRules{}, err } - forwardRoute, reverseRoute := route.ForwardAndReverse() + // Reserve route IDs from remote routers. + rtIDR, err := ReserveRouteIDs(ctx, log, dialer, biRt) + if err != nil { + return routing.EdgeRules{}, err + } + defer func() { log.WithError(rtIDR.Close()).Debug("Closing route id reserver.") }() - // Determine the rules to send to visors using route group descriptor and reserved route IDs. - forwardRules, consumeRules, intermediaryRules, err := idr.GenerateRules(forwardRoute, reverseRoute) + // Generate forward and reverse routes. + fwdRt, revRt := biRt.ForwardAndReverse() + srcPK := biRt.Desc.SrcPK() + dstPK := biRt.Desc.DstPK() + // Generate routing rules (for edge and intermediary routers) that are to be sent. + // Rules are grouped by rule type [FWD, REV, INTER]. + fwdRules, revRules, interRules, err := GenerateRules(rtIDR, []routing.Route{fwdRt, revRt}) if err != nil { - sn.logger.WithError(err).Error("ERROR GENERATING RULES") return routing.EdgeRules{}, err } + initEdge := routing.EdgeRules{Desc: revRt.Desc, Forward: fwdRules[srcPK][0], Reverse: revRules[srcPK][0]} + respEdge := routing.EdgeRules{Desc: fwdRt.Desc, Forward: fwdRules[dstPK][0], Reverse: revRules[dstPK][0]} - sn.logger.Infof("generated forward rules: %v", forwardRules) - sn.logger.Infof("generated consume rules: %v", consumeRules) - sn.logger.Infof("generated intermediary rules: %v", intermediaryRules) + log.Infof("Generated routing rules:\nInitiating edge: %v\nResponding edge: %v\nIntermediaries: %v", + initEdge.String(), respEdge.String(), interRules.String()) - if err := sn.addIntermediaryRules(ctx, intermediaryRules); err != nil { + // Broadcast intermediary rules to intermediary routers. + if err := BroadcastIntermediaryRules(ctx, log, rtIDR, interRules); err != nil { return routing.EdgeRules{}, err } - initRouteRules := routing.EdgeRules{ - Desc: reverseRoute.Desc, - Forward: forwardRules[route.Desc.SrcPK()], - Reverse: consumeRules[route.Desc.SrcPK()], + // Broadcast rules to responding router. + log.Debug("Broadcasting responding rules...") + ok, err := rtIDR.Client(biRt.Desc.DstPK()).AddEdgeRules(ctx, respEdge) + if err != nil || !ok { + return routing.EdgeRules{}, fmt.Errorf("failed to broadcast rules to destination router: %v", err) } - respRouteRules := routing.EdgeRules{ - Desc: forwardRoute.Desc, - Forward: forwardRules[route.Desc.DstPK()], - Reverse: consumeRules[route.Desc.DstPK()], - } + // Return rules to initiating router. + return initEdge, nil +} - sn.logger.Infof("initRouteRules: Desc(%s), %s", &initRouteRules.Desc, initRouteRules) - sn.logger.Infof("respRouteRules: Desc(%s), %s", &respRouteRules.Desc, respRouteRules) +// ReserveRouteIDs dials to all routers and reserves required route IDs from them. +// The number of route IDs to be reserved per router, is extrapolated from the 'route' input. +func ReserveRouteIDs(ctx context.Context, log logrus.FieldLogger, dialer snet.Dialer, route routing.BidirectionalRoute) (idR IDReserver, err error) { + log.Debug("Reserving route IDs...") + defer func() { + if err != nil { + log.WithError(err).Warn("Failed to reserve route IDs.") + } + }() - // Confirm routes with responding visor. - ok, err := routerclient.AddEdgeRules(ctx, sn.logger, sn.dmsgC, route.Desc.DstPK(), respRouteRules) - if err != nil || !ok { - return routing.EdgeRules{}, fmt.Errorf("failed to confirm route group with destination visor: %v", err) + idR, err = NewIDReserver(ctx, dialer, [][]routing.Hop{route.Forward, route.Reverse}) + if err != nil { + return nil, fmt.Errorf("failed to instantiate route id reserver: %w", err) } + defer func() { + if err != nil { + log.WithError(idR.Close()).Warn("Closing router clients due to error.") + } + }() - sn.logger.Infof("Returning route rules to initiating visor: %v", initRouteRules) - - return initRouteRules, nil + if err = idR.ReserveIDs(ctx); err != nil { + return nil, fmt.Errorf("failed to reserve route ids: %w", err) + } + return idR, nil } -func (sn *Node) addIntermediaryRules(ctx context.Context, intermediaryRules RulesMap) error { - errCh := make(chan error, len(intermediaryRules)) - - var wg sync.WaitGroup +// GenerateRules generates rules for given forward and reverse routes. +// The outputs are as follows: +// - maps that relate slices of forward, consume and intermediary routing rules to a given visor's public key. +// - an error (if any). +func GenerateRules(idR IDReserver, routes []routing.Route) (fwdRules, revRules, interRules RulesMap, err error) { + fwdRules = make(RulesMap) + revRules = make(RulesMap) + interRules = make(RulesMap) + + for _, route := range routes { + // 'firstRID' is the first visor's key routeID + firstRID, ok := idR.PopID(route.Hops[0].From) + if !ok { + return nil, nil, nil, ErrNoKey + } - for pk, rules := range intermediaryRules { - pk, rules := pk, rules + desc := route.Desc + srcPK := desc.SrcPK() + dstPK := desc.DstPK() + srcPort := desc.SrcPort() + dstPort := desc.DstPort() - sn.logger.WithField("remote", pk).Info("Adding rules to intermediary visor") + var rID = firstRID - wg.Add(1) + for i, hop := range route.Hops { + nxtRID, ok := idR.PopID(hop.To) + if !ok { + return nil, nil, nil, ErrNoKey + } - go func() { - defer wg.Done() - if _, err := routerclient.AddIntermediaryRules(ctx, sn.logger, sn.dmsgC, pk, rules); err != nil { - sn.logger.WithField("remote", pk).WithError(err).Warn("failed to add rules") - errCh <- err + if i == 0 { + rule := routing.ForwardRule(route.KeepAlive, rID, nxtRID, hop.TpID, srcPK, dstPK, srcPort, dstPort) + fwdRules[hop.From] = append(fwdRules[hop.From], rule) + } else { + rule := routing.IntermediaryForwardRule(route.KeepAlive, rID, nxtRID, hop.TpID) + interRules[hop.From] = append(interRules[hop.From], rule) } - }() - } - wg.Wait() - close(errCh) + rID = nxtRID + } + + rule := routing.ConsumeRule(route.KeepAlive, rID, srcPK, dstPK, srcPort, dstPort) + revRules[dstPK] = append(revRules[dstPK], rule) + } - return finalError(len(intermediaryRules), errCh) + return fwdRules, revRules, interRules, nil } -func (sn *Node) reserveRouteIDs(ctx context.Context, route routing.BidirectionalRoute) (*idReservoir, error) { - reservoir, total := newIDReservoir(route.Forward, route.Reverse) - sn.logger.Infof("There are %d route IDs to reserve.", total) +// BroadcastIntermediaryRules broadcasts routing rules to the intermediary routers. +func BroadcastIntermediaryRules(ctx context.Context, log logrus.FieldLogger, rtIDR IDReserver, interRules RulesMap) (err error) { + log.WithField("intermediary_routers", len(interRules)).Debug("Broadcasting intermediary rules...") + defer func() { + if err != nil { + log.WithError(err).Warn("Failed to broadcast intermediary rules.") + } + }() - err := reservoir.ReserveIDs(ctx, sn.logger, sn.dmsgC, routerclient.ReserveIDs) + ctx, cancel := context.WithCancel(ctx) + defer cancel() - if err != nil { - sn.logger.WithError(err).Warnf("Failed to reserve route IDs.") - return nil, err - } + errCh := make(chan error, len(interRules)) + defer close(errCh) - sn.logger.Infof("Successfully reserved route IDs.") + for pk, rules := range interRules { + go func(pk cipher.PubKey, rules []routing.Rule) { + _, err := rtIDR.Client(pk).AddIntermediaryRules(ctx, rules) + if err != nil { + cancel() + } + errCh <- err + }(pk, rules) + } - return reservoir, err + return firstError(len(interRules), errCh) } diff --git a/pkg/setup/node_old_test.go b/pkg/setup/node_old_test.go new file mode 100644 index 0000000000..c576134666 --- /dev/null +++ b/pkg/setup/node_old_test.go @@ -0,0 +1,248 @@ +// +build !no_ci + +package setup + +// TODO(evanlinjin): Either fix or rewrite these tests. + +// func TestMain(m *testing.M) { +// loggingLevel, ok := os.LookupEnv("TEST_LOGGING_LEVEL") +// if ok { +// lvl, err := logging.LevelFromString(loggingLevel) +// if err != nil { +// log.Fatal(err) +// } +// +// logging.SetLevel(lvl) +// } else { +// logging.Disable() +// } +// +// os.Exit(m.Run()) +// } +// +// type clientWithDMSGAddrAndListener struct { +// *dmsg.Client +// Addr dmsg.Addr +// // Listener *dmsg.Listener +// AppliedIntermediaryRules []routing.Rule +// AppliedEdgeRules routing.EdgeRules +// } +// +// func TestNode(t *testing.T) { +// // We are generating five key pairs - one for the `Router` of setup node, +// // the other ones - for the clients along the desired route. +// keys := snettest.GenKeyPairs(5) +// +// // create test env +// nEnv := snettest.NewEnv(t, keys, []string{dmsg.Type}) +// defer nEnv.Teardown() +// +// reservedIDs := []routing.RouteID{1, 2} +// +// // TEST: Emulates the communication between 4 visors and a setup node, +// // where the first client visor initiates a route to the last. +// t.Run("DialRouteGroup", func(t *testing.T) { +// testDialRouteGroup(t, keys, nEnv, reservedIDs) +// }) +// } +// +// func testDialRouteGroup(t *testing.T, keys []snettest.KeyPair, nEnv *snettest.Env, reservedIDs []routing.RouteID) { +// // client index 0 is for setup node. +// // clients index 1 to 4 are for visors. +// clients, closeClients := prepClients(t, keys, nEnv, reservedIDs, 5) +// defer closeClients() +// +// // prepare and serve setup node (using client 0). +// _, closeSetup := prepSetupNode(t, clients[0].Client) +// defer closeSetup() +// +// route := prepBidirectionalRoute(clients) +// +// forwardRules, consumeRules, intermediaryRules := generateRules(t, route, reservedIDs) +// +// forwardRoute, reverseRoute := route.ForwardAndReverse() +// +// wantEdgeRules := routing.EdgeRules{ +// Desc: reverseRoute.Desc, +// Forward: forwardRules[route.Desc.SrcPK()], +// Reverse: consumeRules[route.Desc.SrcPK()], +// } +// +// testLogger := logging.MustGetLogger("setupclient_test") +// pks := []cipher.PubKey{clients[0].Addr.PK} +// gotEdgeRules, err := setupclient.NewSetupNodeDialer().Dial(context.TODO(), testLogger, nEnv.Nets[1], pks, route) +// require.NoError(t, err) +// require.Equal(t, wantEdgeRules, gotEdgeRules) +// +// for pk, rules := range intermediaryRules { +// for _, cl := range clients { +// if cl.Addr.PK == pk { +// require.Equal(t, cl.AppliedIntermediaryRules, rules) +// break +// } +// } +// } +// +// respRouteRules := routing.EdgeRules{ +// Desc: forwardRoute.Desc, +// Forward: forwardRules[route.Desc.DstPK()], +// Reverse: consumeRules[route.Desc.DstPK()], +// } +// +// require.Equal(t, respRouteRules, clients[4].AppliedEdgeRules) +// } +// +// func prepBidirectionalRoute(clients []clientWithDMSGAddrAndListener) routing.BidirectionalRoute { +// // prepare route group creation (client_1 will use this to request a route group creation with setup node). +// desc := routing.NewRouteDescriptor(clients[1].Addr.PK, clients[4].Addr.PK, 1, 1) +// +// forwardHops := []routing.Hop{ +// {From: clients[1].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, +// {From: clients[2].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, +// {From: clients[3].Addr.PK, To: clients[4].Addr.PK, TpID: uuid.New()}, +// } +// +// reverseHops := []routing.Hop{ +// {From: clients[4].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, +// {From: clients[3].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, +// {From: clients[2].Addr.PK, To: clients[1].Addr.PK, TpID: uuid.New()}, +// } +// +// route := routing.BidirectionalRoute{ +// Desc: desc, +// KeepAlive: 1 * time.Hour, +// Forward: forwardHops, +// Reverse: reverseHops, +// } +// +// return route +// } +// +// func generateRules( +// t *testing.T, +// route routing.BidirectionalRoute, +// reservedIDs []routing.RouteID, +// ) ( +// forwardRules map[cipher.PubKey]routing.Rule, +// consumeRules map[cipher.PubKey]routing.Rule, +// intermediaryRules RulesMap, +// ) { +// wantIDR, _ := NewIDReserver(route.Forward, route.Reverse) +// for pk := range wantIDR.rec { +// wantIDR.ids[pk] = reservedIDs +// } +// +// forwardRoute, reverseRoute := route.ForwardAndReverse() +// +// forwardRules, consumeRules, intermediaryRules, err := wantIDR.GenerateRules(forwardRoute, reverseRoute) +// require.NoError(t, err) +// +// return forwardRules, consumeRules, intermediaryRules +// } +// +// func prepClients( +// t *testing.T, +// keys []snettest.KeyPair, +// nEnv *snettest.Env, +// reservedIDs []routing.RouteID, +// n int, +// ) ([]clientWithDMSGAddrAndListener, func()) { +// clients := make([]clientWithDMSGAddrAndListener, n) +// +// for i := 0; i < n; i++ { +// var port uint16 +// // setup node +// if i == 0 { +// port = skyenv.DmsgSetupPort +// } else { +// port = skyenv.DmsgAwaitSetupPort +// } +// +// pk, sk := keys[i].PK, keys[i].SK +// t.Logf("client[%d] PK: %s\n", i, pk) +// +// clientLogger := logging.MustGetLogger(fmt.Sprintf("client_%d:%s:%d", i, pk, port)) +// c := dmsg.NewClient(pk, sk, nEnv.DmsgD, &dmsg.Config{MinSessions: 1}) +// c.SetLogger(clientLogger) +// +// go c.Serve() +// +// listener, err := c.Listen(port) +// require.NoError(t, err) +// +// clients[i] = clientWithDMSGAddrAndListener{ +// Client: c, +// Addr: dmsg.Addr{ +// PK: pk, +// Port: port, +// }, +// // Listener: listener, +// } +// +// fmt.Printf("Client %d PK: %s\n", i, clients[i].Addr.PK) +// +// // exclude setup node +// if i == 0 { +// continue +// } +// +// r := prepRouter(&clients[i], reservedIDs, i == n-1) +// +// startRPC(t, r, listener) +// } +// +// return clients, func() { +// for _, c := range clients { +// require.NoError(t, c.Close()) +// } +// } +// } +// +// func prepRouter(client *clientWithDMSGAddrAndListener, reservedIDs []routing.RouteID, last bool) *router.MockRouter { +// r := &router.MockRouter{} +// // passing two rules to each visor (forward and reverse routes). Simulate +// // applying intermediary rules. +// r.On("SaveRoutingRules", mock.Anything, mock.Anything). +// Return(func(rules ...routing.Rule) error { +// client.AppliedIntermediaryRules = append(client.AppliedIntermediaryRules, rules...) +// return nil +// }) +// +// // simulate reserving IDs. +// r.On("ReserveKeys", 2).Return(reservedIDs, testhelpers.NoErr) +// +// // destination visor. Simulate applying edge rules. +// if last { +// r.On("IntroduceRules", mock.Anything).Return(func(rules routing.EdgeRules) error { +// client.AppliedEdgeRules = rules +// return nil +// }) +// } +// +// return r +// } +// +// func startRPC(t *testing.T, r router.Router, listener net.Listener) { +// rpcServer := rpc.NewServer() +// require.NoError(t, rpcServer.Register(router.NewRPCGateway(r))) +// +// go rpcServer.Accept(listener) +// } +// +// func prepSetupNode(t *testing.T, c *dmsg.Client) (*Node, func()) { +// sn := &Node{ +// log: logging.MustGetLogger("setup_node"), +// dmsgC: c, +// metrics: metrics.NewDummy(), +// } +// +// go func() { +// if err := sn.Serve(); err != nil { +// sn.log.WithError(err).Error("Failed to serve") +// } +// }() +// +// return sn, func() { +// require.NoError(t, sn.Close()) +// } +// } diff --git a/pkg/setup/node_test.go b/pkg/setup/node_test.go index 78a0478106..2a30843645 100644 --- a/pkg/setup/node_test.go +++ b/pkg/setup/node_test.go @@ -1,276 +1,147 @@ -// +build !no_ci - package setup import ( "context" "fmt" - "log" - "net" - "net/rpc" - "os" + "math/rand" + "strconv" "testing" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/setup/setupclient" - - "github.com/SkycoinProject/skywire-mainnet/internal/testhelpers" - - "github.com/SkycoinProject/skywire-mainnet/pkg/snet/snettest" - - "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" - "github.com/stretchr/testify/mock" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/SkycoinProject/skywire-mainnet/pkg/metrics" - "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" - "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" ) -func TestMain(m *testing.M) { - loggingLevel, ok := os.LookupEnv("TEST_LOGGING_LEVEL") - if ok { - lvl, err := logging.LevelFromString(loggingLevel) - if err != nil { - log.Fatal(err) - } - - logging.SetLevel(lvl) - } else { - logging.Disable() - } - - os.Exit(m.Run()) -} - -type clientWithDMSGAddrAndListener struct { - *dmsg.Client - Addr dmsg.Addr - Listener *dmsg.Listener - AppliedIntermediaryRules []routing.Rule - AppliedEdgeRules routing.EdgeRules -} - -func TestNode(t *testing.T) { - // We are generating five key pairs - one for the `Router` of setup node, - // the other ones - for the clients along the desired route. - keys := snettest.GenKeyPairs(5) - - // create test env - nEnv := snettest.NewEnv(t, keys, []string{dmsg.Type}) - defer nEnv.Teardown() - - reservedIDs := []routing.RouteID{1, 2} - - // TEST: Emulates the communication between 4 visors and a setup node, - // where the first client visor initiates a route to the last. - t.Run("DialRouteGroup", func(t *testing.T) { - testDialRouteGroup(t, keys, nEnv, reservedIDs) - }) -} - -func testDialRouteGroup(t *testing.T, keys []snettest.KeyPair, nEnv *snettest.Env, reservedIDs []routing.RouteID) { - // client index 0 is for setup node. - // clients index 1 to 4 are for visors. - clients, closeClients := prepClients(t, keys, nEnv, reservedIDs, 5) - defer closeClients() - - // prepare and serve setup node (using client 0). - _, closeSetup := prepSetupNode(t, clients[0].Client, clients[0].Listener) - defer closeSetup() - - route := prepBidirectionalRoute(clients) +func TestGenerateRules(t *testing.T) { + pkA, _ := cipher.GenerateKeyPair() + pkB, _ := cipher.GenerateKeyPair() + pkC, _ := cipher.GenerateKeyPair() + pkD, _ := cipher.GenerateKeyPair() - forwardRules, consumeRules, intermediaryRules := generateRules(t, route, reservedIDs) - - forwardRoute, reverseRoute := route.ForwardAndReverse() - - wantEdgeRules := routing.EdgeRules{ - Desc: reverseRoute.Desc, - Forward: forwardRules[route.Desc.SrcPK()], - Reverse: consumeRules[route.Desc.SrcPK()], + type testCase struct { + fwd routing.Route + rev routing.Route } - testLogger := logging.MustGetLogger("setupclient_test") - pks := []cipher.PubKey{clients[0].Addr.PK} - gotEdgeRules, err := setupclient.NewSetupNodeDialer().Dial(context.TODO(), testLogger, nEnv.Nets[1], pks, route) - require.NoError(t, err) - require.Equal(t, wantEdgeRules, gotEdgeRules) - - for pk, rules := range intermediaryRules { - for _, cl := range clients { - if cl.Addr.PK == pk { - require.Equal(t, cl.AppliedIntermediaryRules, rules) - break - } - } + testCases := []testCase{ + { + fwd: routing.Route{ + Desc: routing.NewRouteDescriptor(pkA, pkC, 1, 0), + Hops: []routing.Hop{ + {TpID: uuid.New(), From: pkA, To: pkB}, + {TpID: uuid.New(), From: pkB, To: pkD}, + {TpID: uuid.New(), From: pkD, To: pkC}, + }, + }, + rev: routing.Route{ + Desc: routing.NewRouteDescriptor(pkC, pkA, 0, 1), + Hops: []routing.Hop{ + {TpID: uuid.New(), From: pkC, To: pkB}, + {TpID: uuid.New(), From: pkB, To: pkA}, + }, + }, + }, } - respRouteRules := routing.EdgeRules{ - Desc: forwardRoute.Desc, - Forward: forwardRules[route.Desc.DstPK()], - Reverse: consumeRules[route.Desc.DstPK()], + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + // arrange + rtIDR := newMockReserver(t, nil) + + // act + fwd, rev, inter, err1 := GenerateRules(rtIDR, []routing.Route{tc.fwd, tc.rev}) + t.Log("FORWARD:", fwd) + t.Log("REVERSE:", rev) + t.Log("INTERMEDIARY:", inter) + + // assert + // TODO: We need more checks here + require.NoError(t, err1) + require.Len(t, fwd, 2) + require.Len(t, rev, 2) + }) } - - require.Equal(t, respRouteRules, clients[4].AppliedEdgeRules) } -func prepBidirectionalRoute(clients []clientWithDMSGAddrAndListener) routing.BidirectionalRoute { - // prepare route group creation (client_1 will use this to request a route group creation with setup node). - desc := routing.NewRouteDescriptor(clients[1].Addr.PK, clients[4].Addr.PK, 1, 1) - - forwardHops := []routing.Hop{ - {From: clients[1].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, - {From: clients[2].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, - {From: clients[3].Addr.PK, To: clients[4].Addr.PK, TpID: uuid.New()}, - } - - reverseHops := []routing.Hop{ - {From: clients[4].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, - {From: clients[3].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, - {From: clients[2].Addr.PK, To: clients[1].Addr.PK, TpID: uuid.New()}, - } +func TestBroadcastIntermediaryRules(t *testing.T) { + const ctxTimeout = time.Second + const failingTimeout = time.Second * 5 - route := routing.BidirectionalRoute{ - Desc: desc, - KeepAlive: 1 * time.Hour, - Forward: forwardHops, - Reverse: reverseHops, + type testCase struct { + workingRouters int // number of working routers + failingRouters int // number of failing routers } - return route -} - -func generateRules( - t *testing.T, - route routing.BidirectionalRoute, - reservedIDs []routing.RouteID, -) ( - forwardRules map[cipher.PubKey]routing.Rule, - consumeRules map[cipher.PubKey]routing.Rule, - intermediaryRules RulesMap, -) { - wantIDR, _ := newIDReservoir(route.Forward, route.Reverse) - for pk := range wantIDR.rec { - wantIDR.ids[pk] = reservedIDs + testCases := []testCase{ + {workingRouters: 4, failingRouters: 0}, + {workingRouters: 12, failingRouters: 1}, + {workingRouters: 9, failingRouters: 2}, + {workingRouters: 0, failingRouters: 3}, } - forwardRoute, reverseRoute := route.ForwardAndReverse() - - forwardRules, consumeRules, intermediaryRules, err := wantIDR.GenerateRules(forwardRoute, reverseRoute) - require.NoError(t, err) - - return forwardRules, consumeRules, intermediaryRules -} - -func prepClients( - t *testing.T, - keys []snettest.KeyPair, - nEnv *snettest.Env, - reservedIDs []routing.RouteID, - n int, -) ([]clientWithDMSGAddrAndListener, func()) { - clients := make([]clientWithDMSGAddrAndListener, n) - - for i := 0; i < n; i++ { - var port uint16 - // setup node - if i == 0 { - port = skyenv.DmsgSetupPort - } else { - port = skyenv.DmsgAwaitSetupPort - } - - pk, sk := keys[i].PK, keys[i].SK - t.Logf("client[%d] PK: %s\n", i, pk) - - clientLogger := logging.MustGetLogger(fmt.Sprintf("client_%d:%s:%d", i, pk, port)) - c := dmsg.NewClient(pk, sk, nEnv.DmsgD, &dmsg.Config{MinSessions: 1}) - c.SetLogger(clientLogger) - - go c.Serve() - - listener, err := c.Listen(port) - require.NoError(t, err) - - clients[i] = clientWithDMSGAddrAndListener{ - Client: c, - Addr: dmsg.Addr{ - PK: pk, - Port: port, - }, - Listener: listener, - } - - fmt.Printf("Client %d PK: %s\n", i, clients[i].Addr.PK) + for _, tc := range testCases { + name := fmt.Sprintf("%d_normal_%d_failing", tc.workingRouters, tc.failingRouters) - // exclude setup node - if i == 0 { - continue - } + t.Run(name, func(t *testing.T) { + // arrange + workingPKs := randPKs(tc.workingRouters) + failingPKs := randPKs(tc.failingRouters) - r := prepRouter(&clients[i], reservedIDs, i == n-1) - - startRPC(t, r, listener) - } + gateways := make(map[cipher.PubKey]*mockGatewayForReserver, tc.workingRouters+tc.failingRouters) + for _, pk := range workingPKs { + gateways[pk] = &mockGatewayForReserver{} + } + for _, pk := range failingPKs { + gateways[pk] = &mockGatewayForReserver{hangDuration: failingTimeout} + } - return clients, func() { - for _, c := range clients { - require.NoError(t, c.Close()) - } - } -} + rtIDR := newMockReserver(t, gateways) + rules := randRulesMap(append(workingPKs, failingPKs...)) -func prepRouter(client *clientWithDMSGAddrAndListener, reservedIDs []routing.RouteID, last bool) *router.MockRouter { - r := &router.MockRouter{} - // passing two rules to each visor (forward and reverse routes). Simulate - // applying intermediary rules. - r.On("SaveRoutingRules", mock.Anything, mock.Anything). - Return(func(rules ...routing.Rule) error { - client.AppliedIntermediaryRules = append(client.AppliedIntermediaryRules, rules...) - return nil - }) + ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(ctxTimeout)) + defer cancel() - // simulate reserving IDs. - r.On("ReserveKeys", 2).Return(reservedIDs, testhelpers.NoErr) + // act + err := BroadcastIntermediaryRules(ctx, logrus.New(), rtIDR, rules) - // destination visor. Simulate applying edge rules. - if last { - r.On("IntroduceRules", mock.Anything).Return(func(rules routing.EdgeRules) error { - client.AppliedEdgeRules = rules - return nil + // assert + if tc.failingRouters > 0 { + assert.EqualError(t, err, context.DeadlineExceeded.Error()) + } else { + assert.NoError(t, err) + } }) } - - return r } -func startRPC(t *testing.T, r router.Router, listener net.Listener) { - rpcServer := rpc.NewServer() - require.NoError(t, rpcServer.Register(router.NewRPCGateway(r))) - - go rpcServer.Accept(listener) +func randPKs(n int) []cipher.PubKey { + out := make([]cipher.PubKey, n) + for i := range out { + out[i], _ = cipher.GenerateKeyPair() + } + return out } -func prepSetupNode(t *testing.T, c *dmsg.Client, listener *dmsg.Listener) (*Node, func()) { - sn := &Node{ - logger: logging.MustGetLogger("setup_node"), - dmsgC: c, - dmsgL: listener, - metrics: metrics.NewDummy(), +func randRulesMap(pks []cipher.PubKey) RulesMap { + rules := make(RulesMap, len(pks)) + for _, pk := range pks { + rules[pk] = randIntermediaryRules(2) } + return rules +} - go func() { - if err := sn.Serve(); err != nil { - sn.logger.WithError(err).Error("Failed to serve") - } - }() +func randIntermediaryRules(n int) []routing.Rule { + const keepAlive = time.Second + randRtID := func() routing.RouteID { return routing.RouteID(rand.Uint32()) } - return sn, func() { - require.NoError(t, sn.Close()) + out := make([]routing.Rule, n) + for i := range out { + out[i] = routing.IntermediaryForwardRule(keepAlive, randRtID(), randRtID(), uuid.New()) } + return out } diff --git a/pkg/setup/rpc_gateway.go b/pkg/setup/rpc_gateway.go index e5be746c0e..444d1cfce9 100644 --- a/pkg/setup/rpc_gateway.go +++ b/pkg/setup/rpc_gateway.go @@ -2,53 +2,51 @@ package setup import ( "context" - "fmt" + "net" "time" "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/SkycoinProject/skywire-mainnet/pkg/metrics" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" ) // RPCGateway is a RPC interface for setup node. type RPCGateway struct { - logger *logging.Logger - reqPK cipher.PubKey - sn *Node - timeout time.Duration -} + Metrics metrics.Recorder -// NewRPCGateway returns a new RPCGateway. -func NewRPCGateway(reqPK cipher.PubKey, sn *Node, timeout time.Duration) *RPCGateway { - return &RPCGateway{ - logger: logging.MustGetLogger(fmt.Sprintf("setup-gateway (%s)", reqPK)), - reqPK: reqPK, - sn: sn, - timeout: timeout, - } + Ctx context.Context + Conn net.Conn + ReqPK cipher.PubKey + Dialer snet.Dialer + Timeout time.Duration } // DialRouteGroup dials RouteGroups for route and rules. func (g *RPCGateway) DialRouteGroup(route routing.BidirectionalRoute, rules *routing.EdgeRules) (err error) { - startTime := time.Now() - - defer func() { - g.sn.metrics.Record(time.Since(startTime), err != nil) - }() + log := logging.MustGetLogger("request:" + g.ReqPK.String()) - g.logger.Infof("Received RPC DialRouteGroup request") + startTime := time.Now() + defer func() { g.Metrics.Record(time.Since(startTime), err != nil) }() - ctx, cancel := context.WithTimeout(context.Background(), g.timeout) + ctx, cancel := context.WithTimeout(g.Ctx, g.Timeout) defer cancel() + go func() { + if <-ctx.Done(); ctx.Err() == context.DeadlineExceeded { + log.WithError(ctx.Err()). + WithField("close_error", g.Conn.Close()). + Warn("Closed underlying connection because deadline was exceeded.") + } + }() - initRules, err := g.sn.handleDialRouteGroup(ctx, route) + initRules, err := CreateRouteGroup(ctx, g.Dialer, route) if err != nil { return err } // Confirm routes with initiating visor. *rules = initRules - return nil } diff --git a/pkg/setup/rules_map.go b/pkg/setup/rules_map.go new file mode 100644 index 0000000000..24cee045cd --- /dev/null +++ b/pkg/setup/rules_map.go @@ -0,0 +1,33 @@ +package setup + +import ( + "encoding/json" + + "github.com/SkycoinProject/dmsg/cipher" + + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" +) + +// RulesMap associates a slice of rules to a visor's public key. +type RulesMap map[cipher.PubKey][]routing.Rule + +// String implements fmt.Stringer +func (rm RulesMap) String() string { + out := make(map[cipher.PubKey][]string, len(rm)) + + for pk, rules := range rm { + str := make([]string, len(rules)) + for i, rule := range rules { + str[i] = rule.String() + } + + out[pk] = str + } + + jb, err := json.MarshalIndent(out, "", "\t") + if err != nil { + panic(err) + } + + return string(jb) +} diff --git a/pkg/setup/testing_test.go b/pkg/setup/testing_test.go new file mode 100644 index 0000000000..5539f3580d --- /dev/null +++ b/pkg/setup/testing_test.go @@ -0,0 +1,108 @@ +package setup + +import ( + "net" + "net/rpc" + "sync/atomic" + "testing" + "time" + + "github.com/SkycoinProject/dmsg/cipher" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/SkycoinProject/skywire-mainnet/pkg/router/routerclient" + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" +) + +// creates a mock dialer +func newMockDialer(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForDialer) snet.Dialer { + dialer := new(snet.MockDialer) + + handlePK := func(pk, gw interface{}) { + connC, connS := net.Pipe() + t.Cleanup(func() { + assert.NoError(t, connC.Close()) + assert.NoError(t, connS.Close()) + }) + + rpcS := rpc.NewServer() + require.NoError(t, rpcS.RegisterName(routerclient.RPCName, gw)) + go rpcS.ServeConn(connS) + + dialer.On("Dial", mock.Anything, pk, mock.Anything).Return(connC, nil) + } + + if gateways == nil { + handlePK(mock.Anything, new(mockGatewayForDialer)) + } else { + for pk, gw := range gateways { + handlePK(pk, gw) + } + } + return dialer +} + +type mockGatewayForDialer struct { + hangDuration time.Duration // if set, calling .ReserveIDs should hang for given duration before returning + nextID uint32 +} + +func (gw *mockGatewayForDialer) ReserveIDs(n uint8, routeIDs *[]routing.RouteID) error { + if gw.hangDuration != 0 { + time.Sleep(gw.hangDuration) + } + + out := make([]routing.RouteID, n) + for i := 0; i < int(n); i++ { + out[i] = routing.RouteID(atomic.AddUint32(&gw.nextID, 1)) + } + *routeIDs = out + return nil +} + +// create a mock id reserver +func newMockReserver(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForReserver) IDReserver { + rtIDR := new(MockIDReserver) + + handlePK := func(pk, gw interface{}) { + connC, connS := net.Pipe() + t.Cleanup(func() { + assert.NoError(t, connC.Close()) + assert.NoError(t, connS.Close()) + }) + + rpcS := rpc.NewServer() + require.NoError(t, rpcS.RegisterName(routerclient.RPCName, gw)) + go rpcS.ServeConn(connS) + + pkRaw, _ := pk.(cipher.PubKey) + rc := routerclient.NewClientFromRaw(connC, pkRaw) + rtIDR.On("Client", pk).Return(rc) + rtIDR.On("PopID", mock.Anything).Return(routing.RouteID(1), true) + } + + if gateways == nil { + handlePK(mock.Anything, new(mockGatewayForReserver)) + } else { + for pk, gw := range gateways { + handlePK(pk, gw) + } + } + + return rtIDR +} + +type mockGatewayForReserver struct { + hangDuration time.Duration // if set, calling .ReserveIDs should hang for given duration before returning +} + +func (gw *mockGatewayForReserver) AddIntermediaryRules(_ []routing.Rule, ok *bool) error { + if gw.hangDuration > 0 { + time.Sleep(gw.hangDuration) + } + *ok = true + return nil +} diff --git a/pkg/snet/mock_dialer.go b/pkg/snet/mock_dialer.go new file mode 100644 index 0000000000..1ac47da8d9 --- /dev/null +++ b/pkg/snet/mock_dialer.go @@ -0,0 +1,55 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package snet + +import ( + context "context" + + cipher "github.com/SkycoinProject/dmsg/cipher" + + mock "github.com/stretchr/testify/mock" + + net "net" +) + +// MockDialer is an autogenerated mock type for the Dialer type +type MockDialer struct { + mock.Mock +} + +// Dial provides a mock function with given fields: ctx, remote, port +func (_m *MockDialer) Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) { + ret := _m.Called(ctx, remote, port) + + var r0 net.Conn + if rf, ok := ret.Get(0).(func(context.Context, cipher.PubKey, uint16) net.Conn); ok { + r0 = rf(ctx, remote, port) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Conn) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, cipher.PubKey, uint16) error); ok { + r1 = rf(ctx, remote, port) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Type provides a mock function with given fields: +func (_m *MockDialer) Type() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} diff --git a/pkg/snet/network.go b/pkg/snet/network.go index 7ec1adbb60..a8b756caec 100644 --- a/pkg/snet/network.go +++ b/pkg/snet/network.go @@ -200,6 +200,8 @@ func (n *Network) Dmsg() *dmsg.Client { return n.dmsgC } // STcp returns the underlying stcp.Client. func (n *Network) STcp() *stcp.Client { return n.stcpC } +//go:generate mockery -name Dialer -case underscore -inpkg + // Dialer is an entity that can be dialed and asked for its type. type Dialer interface { Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index d96db3c075..9d3b49153e 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -454,7 +454,7 @@ func (r *RPC) RouteGroups(_ *struct{}, out *[]RouteGroupInfo) (err error) { rules := r.visor.router.Rules() for _, rule := range rules { - if rule.Type() != routing.RuleConsume { + if rule.Type() != routing.RuleReverse { continue } diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index c612655fd8..e36eb01562 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -648,7 +648,7 @@ func (mc *mockRPCClient) RouteGroups() ([]RouteGroupInfo, error) { rules := mc.rt.AllRules() for _, rule := range rules { - if rule.Type() != routing.RuleConsume { + if rule.Type() != routing.RuleReverse { continue } diff --git a/pkg/visor/visorconfig/README.md b/pkg/visor/visorconfig/README.md index 893b7907fa..5598652bba 100644 --- a/pkg/visor/visorconfig/README.md +++ b/pkg/visor/visorconfig/README.md @@ -14,17 +14,13 @@ - `restart_check_delay` (string) -# V1UptimeTracker - -- `addr` (string) - - -# V1Dmsgpty +# V1Launcher -- `port` (uint16) -- `authorization_file` (string) -- `cli_network` (string) -- `cli_address` (string) +- `discovery` (*[V1AppDisc](#V1AppDisc)) +- `apps` ([][AppConfig](#AppConfig)) +- `server_addr` (string) +- `bin_path` (string) +- `local_path` (string) # V1Transport @@ -34,13 +30,18 @@ - `trusted_visors` () -# V1Launcher +# V1LogStore -- `discovery` (*[V1AppDisc](#V1AppDisc)) -- `apps` ([][AppConfig](#AppConfig)) -- `server_addr` (string) -- `bin_path` (string) -- `local_path` (string) +- `type` (string) - Type defines the log store type. Valid values: file, memory. +- `location` (string) + + +# V1Dmsgpty + +- `port` (uint16) +- `authorization_file` (string) +- `cli_network` (string) +- `cli_address` (string) # V1Routing @@ -50,18 +51,25 @@ - `route_finder_timeout` (Duration) -# V1LogStore - -- `type` (string) - Type defines the log store type. Valid values: file, memory. -- `location` (string) - - # V1AppDisc - `update_interval` (Duration) - `proxy_discovery_addr` (string) +# V1UptimeTracker + +- `addr` (string) + + +# AppConfig + +- `name` (string) +- `args` ([]string) +- `auto_start` (bool) +- `port` (Port) + + # STCPConfig - `pk_table` () @@ -72,11 +80,3 @@ - `discovery` (string) - `sessions_count` (int) - - -# AppConfig - -- `name` (string) -- `args` ([]string) -- `auto_start` (bool) -- `port` (Port) diff --git a/pkg/visor/visorconfig/types.go b/pkg/visor/visorconfig/types.go index d938d92501..755c1d4806 100644 --- a/pkg/visor/visorconfig/types.go +++ b/pkg/visor/visorconfig/types.go @@ -27,6 +27,11 @@ func (d Duration) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements unmarshal from json func (d *Duration) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + *d = 0 + return nil + } + var v interface{} if err := json.Unmarshal(b, &v); err != nil { return err diff --git a/vendor/modules.txt b/vendor/modules.txt index c692ba2ff3..5c88632aa8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,5 @@ # github.com/SkycoinProject/dmsg v0.2.2 +## explicit github.com/SkycoinProject/dmsg github.com/SkycoinProject/dmsg/buildinfo github.com/SkycoinProject/dmsg/cipher @@ -10,6 +11,7 @@ github.com/SkycoinProject/dmsg/ioutil github.com/SkycoinProject/dmsg/netutil github.com/SkycoinProject/dmsg/noise # github.com/SkycoinProject/skycoin v0.27.0 +## explicit github.com/SkycoinProject/skycoin/src/cipher github.com/SkycoinProject/skycoin/src/cipher/base58 github.com/SkycoinProject/skycoin/src/cipher/ripemd160 @@ -17,6 +19,7 @@ github.com/SkycoinProject/skycoin/src/cipher/secp256k1-go github.com/SkycoinProject/skycoin/src/cipher/secp256k1-go/secp256k1-go2 github.com/SkycoinProject/skycoin/src/util/logging # github.com/SkycoinProject/yamux v0.0.0-20191213015001-a36efeefbf6a +## explicit github.com/SkycoinProject/yamux # github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template @@ -26,6 +29,7 @@ github.com/alecthomas/units # github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 github.com/andybalholm/brotli # github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 +## explicit github.com/armon/go-socks5 # github.com/beorn7/perks v1.0.1 github.com/beorn7/perks/quantile @@ -45,6 +49,7 @@ github.com/dsnet/compress/internal/prefix # github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 github.com/flynn/noise # github.com/go-chi/chi v4.0.2+incompatible +## explicit github.com/go-chi/chi github.com/go-chi/chi/middleware # github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 @@ -55,10 +60,12 @@ github.com/golang/protobuf/proto # github.com/golang/snappy v0.0.1 github.com/golang/snappy # github.com/google/uuid v1.1.1 +## explicit github.com/google/uuid # github.com/gorilla/handlers v1.4.2 github.com/gorilla/handlers # github.com/gorilla/securecookie v1.1.1 +## explicit github.com/gorilla/securecookie # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap @@ -72,6 +79,7 @@ github.com/klauspost/compress/zstd/internal/xxhash # github.com/klauspost/pgzip v1.2.1 github.com/klauspost/pgzip # github.com/konsorten/go-windows-terminal-sequences v1.0.3 +## explicit github.com/konsorten/go-windows-terminal-sequences # github.com/mattn/go-colorable v0.1.6 github.com/mattn/go-colorable @@ -82,6 +90,7 @@ github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/mgutz/ansi # github.com/mholt/archiver/v3 v3.3.0 +## explicit github.com/mholt/archiver/v3 # github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/mitchellh/colorstring @@ -91,10 +100,12 @@ github.com/nwaples/rardecode github.com/pierrec/lz4 github.com/pierrec/lz4/internal/xxh32 # github.com/pkg/profile v1.3.0 +## explicit github.com/pkg/profile # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib # github.com/prometheus/client_golang v1.3.0 +## explicit github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promauto @@ -102,6 +113,7 @@ github.com/prometheus/client_golang/prometheus/promhttp # github.com/prometheus/client_model v0.1.0 github.com/prometheus/client_model/go # github.com/prometheus/common v0.7.0 +## explicit github.com/prometheus/common/expfmt github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/log @@ -111,21 +123,27 @@ github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/rakyll/statik v0.1.7 +## explicit github.com/rakyll/statik/fs # github.com/schollz/progressbar/v2 v2.15.0 +## explicit github.com/schollz/progressbar/v2 # github.com/sirupsen/logrus v1.5.0 +## explicit github.com/sirupsen/logrus github.com/sirupsen/logrus/hooks/syslog # github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 +## explicit github.com/songgao/water # github.com/spf13/cobra v0.0.5 +## explicit github.com/spf13/cobra # github.com/spf13/pflag v1.0.3 github.com/spf13/pflag # github.com/stretchr/objx v0.1.1 github.com/stretchr/objx # github.com/stretchr/testify v1.4.0 +## explicit github.com/stretchr/testify/assert github.com/stretchr/testify/mock github.com/stretchr/testify/require @@ -137,8 +155,10 @@ github.com/ulikunitz/xz/lzma # github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 github.com/xi2/xz # go.etcd.io/bbolt v1.3.4 +## explicit go.etcd.io/bbolt # golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc +## explicit golang.org/x/crypto/blake2b golang.org/x/crypto/blake2s golang.org/x/crypto/chacha20 @@ -148,6 +168,7 @@ golang.org/x/crypto/internal/subtle golang.org/x/crypto/poly1305 golang.org/x/crypto/ssh/terminal # golang.org/x/net v0.0.0-20200226121028-0de0cce0169b +## explicit golang.org/x/net/bpf golang.org/x/net/context golang.org/x/net/internal/iana @@ -157,6 +178,7 @@ golang.org/x/net/ipv6 golang.org/x/net/nettest golang.org/x/net/proxy # golang.org/x/sys v0.0.0-20200428200454-593003d681fa +## explicit golang.org/x/sys/cpu golang.org/x/sys/unix golang.org/x/sys/windows @@ -166,6 +188,7 @@ golang.org/x/sys/windows/svc/eventlog golang.org/x/text/transform golang.org/x/text/unicode/norm # golang.zx2c4.com/wireguard v0.0.20200320 +## explicit golang.zx2c4.com/wireguard/rwcancel golang.zx2c4.com/wireguard/tun golang.zx2c4.com/wireguard/tun/wintun From 7172bcdd1d23969c9d777c2cb5dd4b393819cac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Wed, 3 Jun 2020 07:18:33 +1200 Subject: [PATCH 2/4] Changes as suggested by @nkryuchkov * Rewrote setup.Node tests. * Various tweaks/renamings. --- .travis.yml | 2 +- pkg/routing/route.go | 15 ++ pkg/setup/id_reserver_test.go | 60 ++++---- pkg/setup/node_old_test.go | 248 ---------------------------------- pkg/setup/node_test.go | 218 +++++++++++++++++++++++++++++- pkg/setup/testing_test.go | 49 +++++-- pkg/snet/dialer.go | 16 +++ pkg/snet/network.go | 8 -- 8 files changed, 313 insertions(+), 303 deletions(-) delete mode 100644 pkg/setup/node_old_test.go create mode 100644 pkg/snet/dialer.go diff --git a/.travis.yml b/.travis.yml index 74393d90a9..fd3517abe9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - # - "1.13.x" At minimum the code should run make check on the latest two go versions in the default linux environment provided by Travis. + # - "1.14.x" At minimum the code should run make check on the latest two go versions in the default linux environment provided by Travis. - "1.14.x" dist: xenial diff --git a/pkg/routing/route.go b/pkg/routing/route.go index 9f91a91134..162aff9461 100644 --- a/pkg/routing/route.go +++ b/pkg/routing/route.go @@ -76,6 +76,21 @@ func (br *BidirectionalRoute) Check() error { return nil } +// String implements fmt.Stringer +func (br *BidirectionalRoute) String() string { + m := map[string]interface{}{ + "descriptor": br.Desc.String(), + "keep_alive": br.KeepAlive.String(), + "fwd_hops": br.Forward, + "rev_hops": br.Reverse, + } + j, err := json.MarshalIndent(m, "", "\t") + if err != nil { + panic(err) // should never happen + } + return string(j) +} + // EdgeRules represents edge forward and reverse rules. Edge rules are forward and consume rules. type EdgeRules struct { Desc RouteDescriptor diff --git a/pkg/setup/id_reserver_test.go b/pkg/setup/id_reserver_test.go index 4ccfad23e2..22a795d2ef 100644 --- a/pkg/setup/id_reserver_test.go +++ b/pkg/setup/id_reserver_test.go @@ -80,10 +80,10 @@ func TestIdReserver_ReserveIDs(t *testing.T) { timeout := time.Second type testCase struct { - testName string // test name - routers map[cipher.PubKey]*mockGatewayForDialer // arrange: map of mock router gateways - paths [][]routing.Hop // arrange: idReserver input - expErr error // assert: expected error + testName string // test name + routers map[cipher.PubKey]interface{} // arrange: map of mock router gateways + paths [][]routing.Hop // arrange: idReserver input + expErr error // assert: expected error } makeRun := func(tc testCase) func(t *testing.T) { @@ -116,29 +116,29 @@ func TestIdReserver_ReserveIDs(t *testing.T) { testCases := []testCase{ { testName: "fwd1_rev1", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {}, - pkC: {}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{}, + pkC: &mockGatewayForDialer{}, }, paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkA)}, expErr: nil, }, { testName: "fwd2_rev2", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {}, - pkB: {}, - pkC: {}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{}, + pkB: &mockGatewayForDialer{}, + pkC: &mockGatewayForDialer{}, }, paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, expErr: nil, }, { testName: "fwd1_rev2", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {}, - pkB: {}, - pkC: {}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{}, + pkB: &mockGatewayForDialer{}, + pkC: &mockGatewayForDialer{}, }, paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkB, pkA)}, expErr: nil, @@ -158,39 +158,39 @@ func TestIdReserver_ReserveIDs(t *testing.T) { testCases := []testCase{ { testName: "all_routers_hang", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {hangDuration: time.Second * 5}, - pkC: {hangDuration: time.Second * 5}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{hangDuration: time.Second * 5}, + pkC: &mockGatewayForDialer{hangDuration: time.Second * 5}, }, paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkA)}, expErr: context.DeadlineExceeded, }, { testName: "intermediary_router_hangs", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {}, - pkB: {hangDuration: time.Second * 5}, - pkC: {}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{}, + pkB: &mockGatewayForDialer{hangDuration: time.Second * 5}, + pkC: &mockGatewayForDialer{}, }, paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, expErr: context.DeadlineExceeded, }, { testName: "initiating_router_hangs", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {hangDuration: time.Second * 5}, - pkB: {}, - pkC: {}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{hangDuration: time.Second * 5}, + pkB: &mockGatewayForDialer{}, + pkC: &mockGatewayForDialer{}, }, paths: [][]routing.Hop{makeHops(pkA, pkC), makeHops(pkC, pkB, pkA)}, expErr: context.DeadlineExceeded, }, { testName: "responding_router_hangs", - routers: map[cipher.PubKey]*mockGatewayForDialer{ - pkA: {}, - pkB: {}, - pkC: {hangDuration: time.Second * 5}, + routers: map[cipher.PubKey]interface{}{ + pkA: &mockGatewayForDialer{}, + pkB: &mockGatewayForDialer{}, + pkC: &mockGatewayForDialer{hangDuration: time.Second * 5}, }, paths: [][]routing.Hop{makeHops(pkA, pkB, pkC), makeHops(pkC, pkB, pkA)}, expErr: context.DeadlineExceeded, diff --git a/pkg/setup/node_old_test.go b/pkg/setup/node_old_test.go deleted file mode 100644 index c576134666..0000000000 --- a/pkg/setup/node_old_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// +build !no_ci - -package setup - -// TODO(evanlinjin): Either fix or rewrite these tests. - -// func TestMain(m *testing.M) { -// loggingLevel, ok := os.LookupEnv("TEST_LOGGING_LEVEL") -// if ok { -// lvl, err := logging.LevelFromString(loggingLevel) -// if err != nil { -// log.Fatal(err) -// } -// -// logging.SetLevel(lvl) -// } else { -// logging.Disable() -// } -// -// os.Exit(m.Run()) -// } -// -// type clientWithDMSGAddrAndListener struct { -// *dmsg.Client -// Addr dmsg.Addr -// // Listener *dmsg.Listener -// AppliedIntermediaryRules []routing.Rule -// AppliedEdgeRules routing.EdgeRules -// } -// -// func TestNode(t *testing.T) { -// // We are generating five key pairs - one for the `Router` of setup node, -// // the other ones - for the clients along the desired route. -// keys := snettest.GenKeyPairs(5) -// -// // create test env -// nEnv := snettest.NewEnv(t, keys, []string{dmsg.Type}) -// defer nEnv.Teardown() -// -// reservedIDs := []routing.RouteID{1, 2} -// -// // TEST: Emulates the communication between 4 visors and a setup node, -// // where the first client visor initiates a route to the last. -// t.Run("DialRouteGroup", func(t *testing.T) { -// testDialRouteGroup(t, keys, nEnv, reservedIDs) -// }) -// } -// -// func testDialRouteGroup(t *testing.T, keys []snettest.KeyPair, nEnv *snettest.Env, reservedIDs []routing.RouteID) { -// // client index 0 is for setup node. -// // clients index 1 to 4 are for visors. -// clients, closeClients := prepClients(t, keys, nEnv, reservedIDs, 5) -// defer closeClients() -// -// // prepare and serve setup node (using client 0). -// _, closeSetup := prepSetupNode(t, clients[0].Client) -// defer closeSetup() -// -// route := prepBidirectionalRoute(clients) -// -// forwardRules, consumeRules, intermediaryRules := generateRules(t, route, reservedIDs) -// -// forwardRoute, reverseRoute := route.ForwardAndReverse() -// -// wantEdgeRules := routing.EdgeRules{ -// Desc: reverseRoute.Desc, -// Forward: forwardRules[route.Desc.SrcPK()], -// Reverse: consumeRules[route.Desc.SrcPK()], -// } -// -// testLogger := logging.MustGetLogger("setupclient_test") -// pks := []cipher.PubKey{clients[0].Addr.PK} -// gotEdgeRules, err := setupclient.NewSetupNodeDialer().Dial(context.TODO(), testLogger, nEnv.Nets[1], pks, route) -// require.NoError(t, err) -// require.Equal(t, wantEdgeRules, gotEdgeRules) -// -// for pk, rules := range intermediaryRules { -// for _, cl := range clients { -// if cl.Addr.PK == pk { -// require.Equal(t, cl.AppliedIntermediaryRules, rules) -// break -// } -// } -// } -// -// respRouteRules := routing.EdgeRules{ -// Desc: forwardRoute.Desc, -// Forward: forwardRules[route.Desc.DstPK()], -// Reverse: consumeRules[route.Desc.DstPK()], -// } -// -// require.Equal(t, respRouteRules, clients[4].AppliedEdgeRules) -// } -// -// func prepBidirectionalRoute(clients []clientWithDMSGAddrAndListener) routing.BidirectionalRoute { -// // prepare route group creation (client_1 will use this to request a route group creation with setup node). -// desc := routing.NewRouteDescriptor(clients[1].Addr.PK, clients[4].Addr.PK, 1, 1) -// -// forwardHops := []routing.Hop{ -// {From: clients[1].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, -// {From: clients[2].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, -// {From: clients[3].Addr.PK, To: clients[4].Addr.PK, TpID: uuid.New()}, -// } -// -// reverseHops := []routing.Hop{ -// {From: clients[4].Addr.PK, To: clients[3].Addr.PK, TpID: uuid.New()}, -// {From: clients[3].Addr.PK, To: clients[2].Addr.PK, TpID: uuid.New()}, -// {From: clients[2].Addr.PK, To: clients[1].Addr.PK, TpID: uuid.New()}, -// } -// -// route := routing.BidirectionalRoute{ -// Desc: desc, -// KeepAlive: 1 * time.Hour, -// Forward: forwardHops, -// Reverse: reverseHops, -// } -// -// return route -// } -// -// func generateRules( -// t *testing.T, -// route routing.BidirectionalRoute, -// reservedIDs []routing.RouteID, -// ) ( -// forwardRules map[cipher.PubKey]routing.Rule, -// consumeRules map[cipher.PubKey]routing.Rule, -// intermediaryRules RulesMap, -// ) { -// wantIDR, _ := NewIDReserver(route.Forward, route.Reverse) -// for pk := range wantIDR.rec { -// wantIDR.ids[pk] = reservedIDs -// } -// -// forwardRoute, reverseRoute := route.ForwardAndReverse() -// -// forwardRules, consumeRules, intermediaryRules, err := wantIDR.GenerateRules(forwardRoute, reverseRoute) -// require.NoError(t, err) -// -// return forwardRules, consumeRules, intermediaryRules -// } -// -// func prepClients( -// t *testing.T, -// keys []snettest.KeyPair, -// nEnv *snettest.Env, -// reservedIDs []routing.RouteID, -// n int, -// ) ([]clientWithDMSGAddrAndListener, func()) { -// clients := make([]clientWithDMSGAddrAndListener, n) -// -// for i := 0; i < n; i++ { -// var port uint16 -// // setup node -// if i == 0 { -// port = skyenv.DmsgSetupPort -// } else { -// port = skyenv.DmsgAwaitSetupPort -// } -// -// pk, sk := keys[i].PK, keys[i].SK -// t.Logf("client[%d] PK: %s\n", i, pk) -// -// clientLogger := logging.MustGetLogger(fmt.Sprintf("client_%d:%s:%d", i, pk, port)) -// c := dmsg.NewClient(pk, sk, nEnv.DmsgD, &dmsg.Config{MinSessions: 1}) -// c.SetLogger(clientLogger) -// -// go c.Serve() -// -// listener, err := c.Listen(port) -// require.NoError(t, err) -// -// clients[i] = clientWithDMSGAddrAndListener{ -// Client: c, -// Addr: dmsg.Addr{ -// PK: pk, -// Port: port, -// }, -// // Listener: listener, -// } -// -// fmt.Printf("Client %d PK: %s\n", i, clients[i].Addr.PK) -// -// // exclude setup node -// if i == 0 { -// continue -// } -// -// r := prepRouter(&clients[i], reservedIDs, i == n-1) -// -// startRPC(t, r, listener) -// } -// -// return clients, func() { -// for _, c := range clients { -// require.NoError(t, c.Close()) -// } -// } -// } -// -// func prepRouter(client *clientWithDMSGAddrAndListener, reservedIDs []routing.RouteID, last bool) *router.MockRouter { -// r := &router.MockRouter{} -// // passing two rules to each visor (forward and reverse routes). Simulate -// // applying intermediary rules. -// r.On("SaveRoutingRules", mock.Anything, mock.Anything). -// Return(func(rules ...routing.Rule) error { -// client.AppliedIntermediaryRules = append(client.AppliedIntermediaryRules, rules...) -// return nil -// }) -// -// // simulate reserving IDs. -// r.On("ReserveKeys", 2).Return(reservedIDs, testhelpers.NoErr) -// -// // destination visor. Simulate applying edge rules. -// if last { -// r.On("IntroduceRules", mock.Anything).Return(func(rules routing.EdgeRules) error { -// client.AppliedEdgeRules = rules -// return nil -// }) -// } -// -// return r -// } -// -// func startRPC(t *testing.T, r router.Router, listener net.Listener) { -// rpcServer := rpc.NewServer() -// require.NoError(t, rpcServer.Register(router.NewRPCGateway(r))) -// -// go rpcServer.Accept(listener) -// } -// -// func prepSetupNode(t *testing.T, c *dmsg.Client) (*Node, func()) { -// sn := &Node{ -// log: logging.MustGetLogger("setup_node"), -// dmsgC: c, -// metrics: metrics.NewDummy(), -// } -// -// go func() { -// if err := sn.Serve(); err != nil { -// sn.log.WithError(err).Error("Failed to serve") -// } -// }() -// -// return sn, func() { -// require.NoError(t, sn.Close()) -// } -// } diff --git a/pkg/setup/node_test.go b/pkg/setup/node_test.go index 2a30843645..542e6f8ce2 100644 --- a/pkg/setup/node_test.go +++ b/pkg/setup/node_test.go @@ -4,11 +4,14 @@ import ( "context" "fmt" "math/rand" + "os" "strconv" + "sync" "testing" "time" "github.com/SkycoinProject/dmsg/cipher" + "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -17,6 +20,215 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) +func TestMain(m *testing.M) { + loggingLevel, ok := os.LookupEnv("TEST_LOGGING_LEVEL") + if ok { + lvl, err := logging.LevelFromString(loggingLevel) + if err != nil { + log.Fatal(err) + } + + logging.SetLevel(lvl) + } else { + logging.Disable() + } + + os.Exit(m.Run()) +} + +func TestCreateRouteGroup(t *testing.T) { + pkA, _ := cipher.GenerateKeyPair() + pkB, _ := cipher.GenerateKeyPair() + pkC, _ := cipher.GenerateKeyPair() + pkD, _ := cipher.GenerateKeyPair() + + type testCase struct { + fwdPKs []cipher.PubKey + revPKs []cipher.PubKey + SrcPort routing.Port + DstPort routing.Port + } + + testCases := []testCase{ + { + fwdPKs: []cipher.PubKey{pkA, pkB, pkC, pkD}, + revPKs: []cipher.PubKey{pkD, pkC, pkB, pkA}, + SrcPort: 1, + DstPort: 5, + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + // arrange: router keys + routerPKs := append(tc.fwdPKs, tc.revPKs...) + routerCount := countUniquePKs(append(tc.fwdPKs, tc.revPKs...)) + initPK := routerPKs[0] + // respPK := routerPKs[routerCount-1] + + // arrange: routers + routers := make(map[cipher.PubKey]interface{}, routerCount) + for _, pk := range routerPKs { + routers[pk] = newMockRouterGateway(pk) + } + + // arrange: mock dialer + dialer := newMockDialer(t, routers) + + // arrange: bidirectional route input + biRt := biRouteFromKeys(tc.fwdPKs, tc.revPKs, tc.SrcPort, tc.DstPort) + // fmt.Println(biRt.String()) + + // act + resp, err := CreateRouteGroup(context.TODO(), dialer, biRt) + if err == nil { + // if successful, inject response (response edge rules) to responding router + var ok bool + _ = routers[initPK].(*mockRouterGateway).AddEdgeRules(resp, &ok) // nolint:errcheck + } + + // assert: no error + assert.NoError(t, err) + + // assert: valid route ID keys + for pk, r := range routers { + mr := r.(*mockRouterGateway) + t.Logf("Checking router %s: lastRtID=%d edgeRules=%d interRules=%d", + pk, mr.lastRtID, len(mr.edgeRules), len(mr.interRules)) + checkRtIDKeysOfRouterRules(t, mr) + } + + // TODO: assert: edge routers + // * Ensure edge routers have 1 edge rule each, and no inter rules. + // * Edge rule's descriptor should be of provided src/dst pk/port. + + // TODO: assert: inter routers + // * Ensure inter routers have 2 or more inter rules (depending on routes). + // * Ensure inter routers have no edge rules. + }) + } +} + +// checkRtIDKeysOfRouterRules ensures that the rules advertised to the router (from the setup logic) has route ID keys +// which are valid. +func checkRtIDKeysOfRouterRules(t *testing.T, r *mockRouterGateway) { + r.mx.Lock() + defer r.mx.Unlock() + + var rtIDKeys []routing.RouteID + + for _, edge := range r.edgeRules { + rtIDKeys = append(rtIDKeys, edge.Forward.KeyRouteID(), edge.Reverse.KeyRouteID()) + } + for _, rules := range r.interRules { + for _, rule := range rules { + rtIDKeys = append(rtIDKeys, rule.KeyRouteID()) + } + } + + // assert: no duplicate rtIDs + dupM := make(map[routing.RouteID]struct{}) + for _, rtID := range rtIDKeys { + dupM[rtID] = struct{}{} + } + assert.Len(t, dupM, len(rtIDKeys), "rtIDKeys=%v dupM=%v", rtIDKeys, dupM) + + // assert: all routes IDs are explicitly reserved by router + for _, rtID := range rtIDKeys { + assert.LessOrEqual(t, uint32(rtID), r.lastRtID) + } +} + +func countUniquePKs(pks []cipher.PubKey) int { + m := make(map[cipher.PubKey]struct{}) + for _, pk := range pks { + m[pk] = struct{}{} + } + return len(m) +} + +func biRouteFromKeys(fwdPKs, revPKs []cipher.PubKey, srcPort, dstPort routing.Port) routing.BidirectionalRoute { + fwdHops := make([]routing.Hop, len(fwdPKs)-1) + for i, srcPK := range fwdPKs[:len(fwdPKs)-1] { + dstPK := fwdPKs[i+1] + fwdHops[i] = routing.Hop{TpID: determineTpID(srcPK, dstPK), From: srcPK, To: dstPK} + } + revHops := make([]routing.Hop, len(revPKs)-1) + for i, srcPK := range revPKs[:len(revPKs)-1] { + dstPK := revPKs[i+1] + revHops[i] = routing.Hop{TpID: determineTpID(srcPK, dstPK), From: srcPK, To: dstPK} + } + // TODO: This should also return a map of format: map[uuid.UUID][]cipher.PubKey + // This way, we can associate transport IDs to the two transport edges, allowing for more checks. + return routing.BidirectionalRoute{ + Desc: routing.NewRouteDescriptor(fwdPKs[0], revPKs[0], srcPort, dstPort), + KeepAlive: 0, + Forward: fwdHops, + Reverse: revHops, + } +} + +// for tests, we make transport IDs deterministic +// hence, we can derive the tpID from any pk pair +func determineTpID(pk1, pk2 cipher.PubKey) (tpID uuid.UUID) { + v1, v2 := pk1.Big(), pk2.Big() + var hash cipher.SHA256 + if v1.Cmp(v2) > 0 { + hash = cipher.SumSHA256(append(pk1[:], pk2[:]...)) + } else { + hash = cipher.SumSHA256(append(pk2[:], pk1[:]...)) + } + copy(tpID[:], hash[:]) + return tpID +} + +// mockRouterGateway mocks router.RPCGateway and has an internal state machine that records all remote calls. +// mockRouterGateway acts as a well behaved router, and no error will be returned on any of it's endpoints. +type mockRouterGateway struct { + pk cipher.PubKey // router's public key + lastRtID uint32 // last route ID that was reserved (the first returned rtID would be 1 if this starts as 0). + edgeRules []routing.EdgeRules // edge rules added by remote. + interRules [][]routing.Rule // intermediary rules added by remote. + mx sync.Mutex +} + +func newMockRouterGateway(pk cipher.PubKey) *mockRouterGateway { + return &mockRouterGateway{pk: pk} +} + +func (gw *mockRouterGateway) AddEdgeRules(rules routing.EdgeRules, ok *bool) error { + gw.mx.Lock() + defer gw.mx.Unlock() + + gw.edgeRules = append(gw.edgeRules, rules) + *ok = true + return nil +} + +func (gw *mockRouterGateway) AddIntermediaryRules(rules []routing.Rule, ok *bool) error { + gw.mx.Lock() + defer gw.mx.Unlock() + + gw.interRules = append(gw.interRules, rules) + *ok = true + return nil +} + +func (gw *mockRouterGateway) ReserveIDs(n uint8, routeIDs *[]routing.RouteID) error { + gw.mx.Lock() + defer gw.mx.Unlock() + + out := make([]routing.RouteID, n) + for i := range out { + gw.lastRtID++ + out[i] = routing.RouteID(gw.lastRtID) + } + *routeIDs = out + return nil +} + +// There are no distinctive goals for this test yet. +// As of writing, we only check whether GenerateRules() returns any errors. func TestGenerateRules(t *testing.T) { pkA, _ := cipher.GenerateKeyPair() pkB, _ := cipher.GenerateKeyPair() @@ -54,14 +266,14 @@ func TestGenerateRules(t *testing.T) { rtIDR := newMockReserver(t, nil) // act - fwd, rev, inter, err1 := GenerateRules(rtIDR, []routing.Route{tc.fwd, tc.rev}) + fwd, rev, inter, err := GenerateRules(rtIDR, []routing.Route{tc.fwd, tc.rev}) t.Log("FORWARD:", fwd) t.Log("REVERSE:", rev) t.Log("INTERMEDIARY:", inter) // assert // TODO: We need more checks here - require.NoError(t, err1) + require.NoError(t, err) require.Len(t, fwd, 2) require.Len(t, rev, 2) }) @@ -92,7 +304,7 @@ func TestBroadcastIntermediaryRules(t *testing.T) { workingPKs := randPKs(tc.workingRouters) failingPKs := randPKs(tc.failingRouters) - gateways := make(map[cipher.PubKey]*mockGatewayForReserver, tc.workingRouters+tc.failingRouters) + gateways := make(map[cipher.PubKey]interface{}, tc.workingRouters+tc.failingRouters) for _, pk := range workingPKs { gateways[pk] = &mockGatewayForReserver{} } diff --git a/pkg/setup/testing_test.go b/pkg/setup/testing_test.go index 5539f3580d..d3b7a542dd 100644 --- a/pkg/setup/testing_test.go +++ b/pkg/setup/testing_test.go @@ -1,12 +1,15 @@ package setup import ( + "context" + "fmt" "net" "net/rpc" "sync/atomic" "testing" "time" + "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -18,10 +21,8 @@ import ( ) // creates a mock dialer -func newMockDialer(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForDialer) snet.Dialer { - dialer := new(snet.MockDialer) - - handlePK := func(pk, gw interface{}) { +func newMockDialer(t *testing.T, gateways map[cipher.PubKey]interface{}) snet.Dialer { + newRPCConn := func(gw interface{}) net.Conn { connC, connS := net.Pipe() t.Cleanup(func() { assert.NoError(t, connC.Close()) @@ -32,21 +33,40 @@ func newMockDialer(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForDiale require.NoError(t, rpcS.RegisterName(routerclient.RPCName, gw)) go rpcS.ServeConn(connS) - dialer.On("Dial", mock.Anything, pk, mock.Anything).Return(connC, nil) + return connC } if gateways == nil { - handlePK(mock.Anything, new(mockGatewayForDialer)) - } else { - for pk, gw := range gateways { - handlePK(pk, gw) - } + conn := newRPCConn(new(mockGatewayForDialer)) + dialer := new(snet.MockDialer) + dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) + return dialer + } + + dialer := make(mockDialer, len(gateways)) + for pk, gw := range gateways { + dialer[pk] = newRPCConn(gw) } return dialer } +type mockDialer map[cipher.PubKey]net.Conn + +func (d mockDialer) Type() string { return dmsg.Type } + +func (d mockDialer) Dial(_ context.Context, remote cipher.PubKey, _ uint16) (net.Conn, error) { + conn, ok := d[remote] + if !ok { + return nil, fmt.Errorf("cannot dial to given pk %s", remote) + } + return conn, nil +} + +// mockGatewayForDialer is the default mock router.RPCGateway for newMockDialer. +// It reserves route IDs sequentially for each .ReserveIDs call. +// If hangDuration is > 0, calling .ReserveIDS would hang for the given duration before returning. type mockGatewayForDialer struct { - hangDuration time.Duration // if set, calling .ReserveIDs should hang for given duration before returning + hangDuration time.Duration nextID uint32 } @@ -64,7 +84,7 @@ func (gw *mockGatewayForDialer) ReserveIDs(n uint8, routeIDs *[]routing.RouteID) } // create a mock id reserver -func newMockReserver(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForReserver) IDReserver { +func newMockReserver(t *testing.T, gateways map[cipher.PubKey]interface{}) IDReserver { rtIDR := new(MockIDReserver) handlePK := func(pk, gw interface{}) { @@ -95,8 +115,11 @@ func newMockReserver(t *testing.T, gateways map[cipher.PubKey]*mockGatewayForRes return rtIDR } +// mockGatewayForReserver is the default mock router.RPCGateway for newMockReserver. +// It pretends to successfully trigger .AddIntermediaryRules. +// If handDuration is set, calling .ReserveIDs should hang for given duration before returning type mockGatewayForReserver struct { - hangDuration time.Duration // if set, calling .ReserveIDs should hang for given duration before returning + hangDuration time.Duration } func (gw *mockGatewayForReserver) AddIntermediaryRules(_ []routing.Rule, ok *bool) error { diff --git a/pkg/snet/dialer.go b/pkg/snet/dialer.go new file mode 100644 index 0000000000..66d0c6210f --- /dev/null +++ b/pkg/snet/dialer.go @@ -0,0 +1,16 @@ +package snet + +import ( + "context" + "net" + + "github.com/SkycoinProject/dmsg/cipher" +) + +//go:generate mockery -name Dialer -case underscore -inpkg + +// Dialer is an entity that can be dialed and asked for its type. +type Dialer interface { + Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) + Type() string +} diff --git a/pkg/snet/network.go b/pkg/snet/network.go index 7e79c25958..fa16d87016 100644 --- a/pkg/snet/network.go +++ b/pkg/snet/network.go @@ -317,14 +317,6 @@ func (n *Network) STcpr() *stcpr.Client { return n.clients.StcprC } // STcpH returns the underlying stcph.Client. func (n *Network) STcpH() *stcph.Client { return n.clients.StcphC } -//go:generate mockery -name Dialer -case underscore -inpkg - -// Dialer is an entity that can be dialed and asked for its type. -type Dialer interface { - Dial(ctx context.Context, remote cipher.PubKey, port uint16) (net.Conn, error) - Type() string -} - // Dial dials a visor by its public key and returns a connection. func (n *Network) Dial(ctx context.Context, network string, pk cipher.PubKey, port uint16) (*Conn, error) { switch network { From 0fcb37689e599e13d7e2419e6d415743acd30189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Wed, 3 Jun 2020 07:24:36 +1200 Subject: [PATCH 3/4] Format code. --- go.mod | 1 + go.sum | 3 ++ pkg/router/routerclient/dmsg_wrapper.go | 4 +- pkg/setup/id_reserver_test.go | 1 - pkg/setup/mock_id_reserver.go | 2 - pkg/setup/node_test.go | 2 - pkg/snet/mock_dialer.go | 4 +- vendor/github.com/pkg/errors/.travis.yml | 11 ++--- vendor/github.com/pkg/errors/Makefile | 44 ++++++++++++++++++ vendor/github.com/pkg/errors/README.md | 11 ++++- vendor/github.com/pkg/errors/errors.go | 8 +++- vendor/github.com/pkg/errors/go113.go | 38 ++++++++++++++++ vendor/github.com/pkg/errors/stack.go | 58 ++++++++++++++++++------ vendor/modules.txt | 3 +- 14 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 vendor/github.com/pkg/errors/Makefile create mode 100644 vendor/github.com/pkg/errors/go113.go diff --git a/go.mod b/go.mod index 8af1784394..1966c7f77f 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/gorilla/securecookie v1.1.1 github.com/libp2p/go-reuseport v0.0.1 github.com/mholt/archiver/v3 v3.3.0 + github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.3.0 github.com/prometheus/client_golang v1.3.0 github.com/prometheus/common v0.7.0 diff --git a/go.sum b/go.sum index 05a8b9743d..af763a7192 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.3.0 h1:OQIvuDgm00gWVWGTf4m4mCt6W1/0YqU7Ntg0mySWgaI= github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -295,6 +297,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/pkg/router/routerclient/dmsg_wrapper.go b/pkg/router/routerclient/dmsg_wrapper.go index a8d8799242..5b0a851cfa 100644 --- a/pkg/router/routerclient/dmsg_wrapper.go +++ b/pkg/router/routerclient/dmsg_wrapper.go @@ -4,10 +4,10 @@ import ( "context" "net" - "github.com/SkycoinProject/skywire-mainnet/pkg/snet" - "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" + + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" ) // WrapDmsgClient wraps a dmsg client to implement snet.Dialer diff --git a/pkg/setup/id_reserver_test.go b/pkg/setup/id_reserver_test.go index 22a795d2ef..2f52710962 100644 --- a/pkg/setup/id_reserver_test.go +++ b/pkg/setup/id_reserver_test.go @@ -9,7 +9,6 @@ import ( "github.com/SkycoinProject/dmsg/cipher" "github.com/google/uuid" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/setup/mock_id_reserver.go b/pkg/setup/mock_id_reserver.go index 15bf8899f4..ba93431b1c 100644 --- a/pkg/setup/mock_id_reserver.go +++ b/pkg/setup/mock_id_reserver.go @@ -6,11 +6,9 @@ import ( context "context" cipher "github.com/SkycoinProject/dmsg/cipher" - mock "github.com/stretchr/testify/mock" routerclient "github.com/SkycoinProject/skywire-mainnet/pkg/router/routerclient" - routing "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) diff --git a/pkg/setup/node_test.go b/pkg/setup/node_test.go index 542e6f8ce2..ed83fe3f94 100644 --- a/pkg/setup/node_test.go +++ b/pkg/setup/node_test.go @@ -64,7 +64,6 @@ func TestCreateRouteGroup(t *testing.T) { routerPKs := append(tc.fwdPKs, tc.revPKs...) routerCount := countUniquePKs(append(tc.fwdPKs, tc.revPKs...)) initPK := routerPKs[0] - // respPK := routerPKs[routerCount-1] // arrange: routers routers := make(map[cipher.PubKey]interface{}, routerCount) @@ -77,7 +76,6 @@ func TestCreateRouteGroup(t *testing.T) { // arrange: bidirectional route input biRt := biRouteFromKeys(tc.fwdPKs, tc.revPKs, tc.SrcPort, tc.DstPort) - // fmt.Println(biRt.String()) // act resp, err := CreateRouteGroup(context.TODO(), dialer, biRt) diff --git a/pkg/snet/mock_dialer.go b/pkg/snet/mock_dialer.go index 1ac47da8d9..5fde908bf2 100644 --- a/pkg/snet/mock_dialer.go +++ b/pkg/snet/mock_dialer.go @@ -4,12 +4,10 @@ package snet import ( context "context" + net "net" cipher "github.com/SkycoinProject/dmsg/cipher" - mock "github.com/stretchr/testify/mock" - - net "net" ) // MockDialer is an autogenerated mock type for the Dialer type diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml index d4b92663ba..9159de03e0 100644 --- a/vendor/github.com/pkg/errors/.travis.yml +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -1,15 +1,10 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - 1.11.x + - 1.12.x + - 1.13.x - tip script: - - go test -v ./... + - make check diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 0000000000..ce9d7cded6 --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md index 6483ba2afb..54dfdcb12e 100644 --- a/vendor/github.com/pkg/errors/README.md +++ b/vendor/github.com/pkg/errors/README.md @@ -41,11 +41,18 @@ default: [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) +- 1.0. Final release. + ## Contributing -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. -Before proposing a change, please discuss your change by raising an issue. +Before sending a PR, please discuss your change by raising an issue. ## License diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 7421f326ff..161aea2582 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -82,7 +82,7 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // @@ -159,6 +159,9 @@ type withStack struct { func (w *withStack) Cause() error { return w.error } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -241,6 +244,9 @@ type withMessage struct { func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go new file mode 100644 index 0000000000..be0d10d0c7 --- /dev/null +++ b/vendor/github.com/pkg/errors/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 2874a048cf..779a8348fb 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -5,10 +5,13 @@ import ( "io" "path" "runtime" + "strconv" "strings" ) // Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. type Frame uintptr // pc returns the program counter for this frame; @@ -37,6 +40,15 @@ func (f Frame) line() int { return line } +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) - if fn == nil { - io.WriteString(s, "unknown") - } else { - file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) - } + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) default: io.WriteString(s, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(s, strconv.Itoa(f.line())) case 'n': - name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(s, funcname(f.name())) case 'v': f.Format(s, 's') io.WriteString(s, ":") @@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) { } } +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame @@ -94,16 +110,30 @@ func (st StackTrace) Format(s fmt.State, verb rune) { switch { case s.Flag('+'): for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + io.WriteString(s, "\n") + f.Format(s, verb) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) } + io.WriteString(s, "]") } // stack represents a stack of program counters. diff --git a/vendor/modules.txt b/vendor/modules.txt index bf3587106f..2edd9ec9eb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -101,7 +101,8 @@ github.com/nwaples/rardecode # github.com/pierrec/lz4 v2.0.5+incompatible github.com/pierrec/lz4 github.com/pierrec/lz4/internal/xxh32 -# github.com/pkg/errors v0.8.1 +# github.com/pkg/errors v0.9.1 +## explicit github.com/pkg/errors # github.com/pkg/profile v1.3.0 ## explicit From 67d5e6f8dfb850c2ae65a0191bad18586bd6112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Mon, 8 Jun 2020 08:51:56 +1200 Subject: [PATCH 4/4] Tweaks as suggested by @nkryuchkov Actual dialing to the snettest.Env struct has also been temporarily removed. This will be addressed in #395 --- pkg/routing/route.go | 10 +++++++++ pkg/setup/node_test.go | 4 ++++ pkg/snet/snettest/env.go | 46 ++++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/pkg/routing/route.go b/pkg/routing/route.go index 162aff9461..e142d7abe3 100644 --- a/pkg/routing/route.go +++ b/pkg/routing/route.go @@ -51,11 +51,13 @@ func (br *BidirectionalRoute) ForwardAndReverse() (forward, reverse Route) { Hops: br.Forward, KeepAlive: br.KeepAlive, } + reverseRoute := Route{ Desc: br.Desc.Invert(), Hops: br.Reverse, KeepAlive: br.KeepAlive, } + return forwardRoute, reverseRoute } @@ -64,15 +66,19 @@ func (br *BidirectionalRoute) Check() error { if len(br.Forward) == 0 { return ErrBiRouteHasNoForwardHops } + if len(br.Reverse) == 0 { return ErrBiRouteHasNoReverseHops } + if srcPK := br.Desc.SrcPK(); br.Forward[0].From != srcPK || br.Reverse[len(br.Reverse)-1].To != srcPK { return ErrBiRouteHasInvalidDesc } + if dstPK := br.Desc.DstPK(); br.Reverse[0].From != dstPK || br.Forward[len(br.Forward)-1].To != dstPK { return ErrBiRouteHasInvalidDesc } + return nil } @@ -84,10 +90,12 @@ func (br *BidirectionalRoute) String() string { "fwd_hops": br.Forward, "rev_hops": br.Reverse, } + j, err := json.MarshalIndent(m, "", "\t") if err != nil { panic(err) // should never happen } + return string(j) } @@ -107,10 +115,12 @@ func (er EdgeRules) String() string { er.Reverse.String(), }, } + j, err := json.MarshalIndent(m, "", "\t") if err != nil { panic(err) } + return string(j) } diff --git a/pkg/setup/node_test.go b/pkg/setup/node_test.go index ed83fe3f94..0367efa936 100644 --- a/pkg/setup/node_test.go +++ b/pkg/setup/node_test.go @@ -151,11 +151,13 @@ func biRouteFromKeys(fwdPKs, revPKs []cipher.PubKey, srcPort, dstPort routing.Po dstPK := fwdPKs[i+1] fwdHops[i] = routing.Hop{TpID: determineTpID(srcPK, dstPK), From: srcPK, To: dstPK} } + revHops := make([]routing.Hop, len(revPKs)-1) for i, srcPK := range revPKs[:len(revPKs)-1] { dstPK := revPKs[i+1] revHops[i] = routing.Hop{TpID: determineTpID(srcPK, dstPK), From: srcPK, To: dstPK} } + // TODO: This should also return a map of format: map[uuid.UUID][]cipher.PubKey // This way, we can associate transport IDs to the two transport edges, allowing for more checks. return routing.BidirectionalRoute{ @@ -170,12 +172,14 @@ func biRouteFromKeys(fwdPKs, revPKs []cipher.PubKey, srcPort, dstPort routing.Po // hence, we can derive the tpID from any pk pair func determineTpID(pk1, pk2 cipher.PubKey) (tpID uuid.UUID) { v1, v2 := pk1.Big(), pk2.Big() + var hash cipher.SHA256 if v1.Cmp(v2) > 0 { hash = cipher.SumSHA256(append(pk1[:], pk2[:]...)) } else { hash = cipher.SumSHA256(append(pk2[:], pk1[:]...)) } + copy(tpID[:], hash[:]) return tpID } diff --git a/pkg/snet/snettest/env.go b/pkg/snet/snettest/env.go index dd80150560..a87d8f4af6 100644 --- a/pkg/snet/snettest/env.go +++ b/pkg/snet/snettest/env.go @@ -13,10 +13,7 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" - "github.com/SkycoinProject/skywire-mainnet/pkg/snet/arclient" "github.com/SkycoinProject/skywire-mainnet/pkg/snet/stcp" - "github.com/SkycoinProject/skywire-mainnet/pkg/snet/stcph" - "github.com/SkycoinProject/skywire-mainnet/pkg/snet/stcpr" ) // KeyPair holds a public/private key pair. @@ -66,7 +63,10 @@ func NewEnv(t *testing.T, keys []KeyPair, networks []string) *Env { table := stcp.NewTable(tableEntries) - var hasDmsg, hasStcp, hasStcpr, hasStcph bool + var hasDmsg, hasStcp bool + + // TODO: https://github.com/SkycoinProject/skywire-mainnet/issues/395 + // var hasStcpr, hasStcph bool for _, network := range networks { switch network { @@ -74,10 +74,12 @@ func NewEnv(t *testing.T, keys []KeyPair, networks []string) *Env { hasDmsg = true case stcp.Type: hasStcp = true - case stcpr.Type: - hasStcpr = true - case stcph.Type: - hasStcph = true + + // TODO: https://github.com/SkycoinProject/skywire-mainnet/issues/395 + // case stcpr.Type: + // hasStcpr = true + // case stcph.Type: + // hasStcph = true } } @@ -94,24 +96,26 @@ func NewEnv(t *testing.T, keys []KeyPair, networks []string) *Env { go clients.DmsgC.Serve() } - addr := "127.0.0.1:" + strconv.Itoa(stcpBasePort+i) - - addressResolver, err := arclient.NewHTTP(skyenv.TestAddressResolverAddr, pairs.PK, pairs.SK) - if err != nil { - panic(err) - } + // TODO: https://github.com/SkycoinProject/skywire-mainnet/issues/395 + // addr := "127.0.0.1:" + strconv.Itoa(stcpBasePort+i) + // + // addressResolver, err := arclient.NewHTTP(skyenv.TestAddressResolverAddr, pairs.PK, pairs.SK) + // if err != nil { + // panic(err) + // } if hasStcp { clients.StcpC = stcp.NewClient(pairs.PK, pairs.SK, table) } - if hasStcpr { - clients.StcprC = stcpr.NewClient(pairs.PK, pairs.SK, addressResolver, addr) - } - - if hasStcph { - clients.StcphC = stcph.NewClient(pairs.PK, pairs.SK, addressResolver) - } + // TODO: https://github.com/SkycoinProject/skywire-mainnet/issues/395 + // if hasStcpr { + // clients.StcprC = stcpr.NewClient(pairs.PK, pairs.SK, addressResolver, addr) + // } + // + // if hasStcph { + // clients.StcphC = stcph.NewClient(pairs.PK, pairs.SK, addressResolver) + // } networkConfigs := snet.NetworkConfigs{ Dmsg: &snet.DmsgConfig{