diff --git a/api/gateway/server.go b/api/gateway/server.go index 266d0e790e..451df3da31 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -15,11 +15,12 @@ type Server struct { srv *http.Server srvMux *mux.Router // http request multiplexer listener net.Listener - started atomic.Bool + + started atomic.Bool } // NewServer returns a new gateway Server. -func NewServer(address string, port string) *Server { +func NewServer(address, port string) *Server { srvMux := mux.NewRouter() srvMux.Use(setContentType) @@ -42,13 +43,12 @@ func (s *Server) Start(context.Context) error { log.Warn("cannot start server: already started") return nil } - listener, err := net.Listen("tcp", s.srv.Addr) if err != nil { return err } s.listener = listener - log.Infow("server started", "listening on", listener.Addr().String()) + log.Infow("server started", "listening on", s.srv.Addr) //nolint:errcheck go s.srv.Serve(listener) return nil @@ -65,6 +65,7 @@ func (s *Server) Stop(ctx context.Context) error { if err != nil { return err } + s.listener = nil log.Info("server stopped") return nil } @@ -88,5 +89,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // ListenAddr returns the listen address of the server. func (s *Server) ListenAddr() string { + if s.listener == nil { + return "" + } return s.listener.Addr().String() } diff --git a/api/rpc/server.go b/api/rpc/server.go index 4488ac53b5..dbb080e41e 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "net" "net/http" "sync/atomic" "time" @@ -13,16 +14,18 @@ import ( var log = logging.Logger("rpc") type Server struct { - http *http.Server - rpc *jsonrpc.RPCServer + srv *http.Server + rpc *jsonrpc.RPCServer + listener net.Listener + started atomic.Bool } -func NewServer(address string, port string) *Server { +func NewServer(address, port string) *Server { rpc := jsonrpc.NewServer() return &Server{ rpc: rpc, - http: &http.Server{ + srv: &http.Server{ Addr: address + ":" + port, Handler: rpc, // the amount of time allowed to read request headers. set to the default 2 seconds @@ -44,9 +47,14 @@ func (s *Server) Start(context.Context) error { log.Warn("cannot start server: already started") return nil } + listener, err := net.Listen("tcp", s.srv.Addr) + if err != nil { + return err + } + s.listener = listener + log.Infow("server started", "listening on", s.srv.Addr) //nolint:errcheck - go s.http.ListenAndServe() - log.Infow("server started", "listening on", s.http.Addr) + go s.srv.Serve(listener) return nil } @@ -57,15 +65,19 @@ func (s *Server) Stop(ctx context.Context) error { log.Warn("cannot stop server: already stopped") return nil } - err := s.http.Shutdown(ctx) + err := s.srv.Shutdown(ctx) if err != nil { return err } + s.listener = nil log.Info("server stopped") return nil } // ListenAddr returns the listen address of the server. func (s *Server) ListenAddr() string { - return s.http.Addr + if s.listener == nil { + return "" + } + return s.listener.Addr().String() } diff --git a/api/rpc_test.go b/api/rpc_test.go index c29b233512..6a09d0b0f0 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "reflect" "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -28,8 +29,20 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { t.Cleanup(cancel) nd, server := setupNodeWithModifiedRPC(t) url := nd.RPCServer.ListenAddr() - client, err := client.NewClient(context.Background(), "http://"+url) - t.Cleanup(client.Close) + // we need to run this a few times to prevent the race where the server is not yet started + var ( + rpcClient *client.Client + err error + ) + for i := 0; i < 3; i++ { + time.Sleep(time.Second * 1) + rpcClient, err = client.NewClient(ctx, "http://"+url) + if err == nil { + t.Cleanup(rpcClient.Close) + break + } + } + require.NotNil(t, rpcClient) require.NoError(t, err) expectedBalance := &state.Balance{ @@ -39,7 +52,7 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { server.State.EXPECT().Balance(gomock.Any()).Return(expectedBalance, nil).Times(1) - balance, err := client.State.Balance(ctx) + balance, err := rpcClient.State.Balance(ctx) require.NoError(t, err) require.Equal(t, expectedBalance, balance) } @@ -118,14 +131,16 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { dasMock.NewMockModule(ctrl), } - overrideRPCHandler := fx.Invoke(func(srv *rpc.Server) { + // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root level module. For + // further information, check the documentation on fx.Invoke. + invokeRPC := fx.Invoke(func(srv *rpc.Server) { srv.RegisterService("state", mockAPI.State) srv.RegisterService("share", mockAPI.Share) srv.RegisterService("fraud", mockAPI.Fraud) srv.RegisterService("header", mockAPI.Header) srv.RegisterService("das", mockAPI.Das) }) - nd := nodebuilder.TestNode(t, node.Full, overrideRPCHandler) + nd := nodebuilder.TestNode(t, node.Full, invokeRPC) // start node err := nd.Start(ctx) require.NoError(t, err) diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 2e55096f91..a7fb1e7bde 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -46,7 +46,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti require.NoError(t, err) cfg.Core.IP = ip cfg.Core.RPCPort = port - cfg.RPC.Port = "26655" + cfg.RPC.Port = "0" opts = append(opts, state.WithKeyringSigner(TestKeyringSigner(t)),