From 233a249168d7ee8b9198e2460dc4291c0ed84545 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 30 Sep 2024 13:43:14 +0200 Subject: [PATCH 01/22] server v2 rest for new modules --- runtime/v2/go.mod | 3 +- runtime/v2/go.sum | 2 ++ runtime/v2/manager.go | 45 ------------------------- runtime/v2/manager_test.go | 50 ++++++++++++++++++++++++++++ runtime/v2/register_services.go | 56 ++++++++++++++++++++++++++++++++ server/v2/api/rest/server.go | 44 +++++++++++++++++++++++++ simapp/v2/simdv2/cmd/commands.go | 2 ++ 7 files changed, 156 insertions(+), 46 deletions(-) create mode 100644 runtime/v2/manager_test.go create mode 100644 runtime/v2/register_services.go create mode 100644 server/v2/api/rest/server.go diff --git a/runtime/v2/go.mod b/runtime/v2/go.mod index ce6c55b50cc2..b420d94f787c 100644 --- a/runtime/v2/go.mod +++ b/runtime/v2/go.mod @@ -22,6 +22,7 @@ require ( cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 cosmossdk.io/x/tx v0.13.3 github.com/cosmos/gogoproto v1.7.0 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.67.0 google.golang.org/protobuf v1.34.2 ) @@ -71,7 +72,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.7.0 // indirect diff --git a/runtime/v2/go.sum b/runtime/v2/go.sum index bd09e3bf2626..58539c14038d 100644 --- a/runtime/v2/go.sum +++ b/runtime/v2/go.sum @@ -219,6 +219,8 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/runtime/v2/manager.go b/runtime/v2/manager.go index 1f05b1014620..5888d7ce3abe 100644 --- a/runtime/v2/manager.go +++ b/runtime/v2/manager.go @@ -614,51 +614,6 @@ func (m *MM[T]) assertNoForgottenModules( return nil } -func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], registry *protoregistry.Files) error { - c := &configurator{ - grpcQueryDecoders: map[string]func() gogoproto.Message{}, - stfQueryRouter: app.queryRouterBuilder, - stfMsgRouter: app.msgRouterBuilder, - registry: registry, - err: nil, - } - - if services, ok := s.(hasServicesV1); ok { - if err := services.RegisterServices(c); err != nil { - return fmt.Errorf("unable to register services: %w", err) - } - } else { - // If module not implement RegisterServices, register msg & query handler. - if module, ok := s.(appmodulev2.HasMsgHandlers); ok { - module.RegisterMsgHandlers(app.msgRouterBuilder) - } - - if module, ok := s.(appmodulev2.HasQueryHandlers); ok { - module.RegisterQueryHandlers(app.queryRouterBuilder) - // TODO: query regist by RegisterQueryHandlers not in grpcQueryDecoders - if module, ok := s.(interface { - GetQueryDecoders() map[string]func() gogoproto.Message - }); ok { - decoderMap := module.GetQueryDecoders() - for path, decoder := range decoderMap { - app.GRPCMethodsToMessageMap[path] = decoder - } - } - } - } - - if c.err != nil { - app.logger.Warn("error registering services", "error", c.err) - } - - // merge maps - for path, decoder := range c.grpcQueryDecoders { - app.GRPCMethodsToMessageMap[path] = decoder - } - - return nil -} - var _ grpc.ServiceRegistrar = (*configurator)(nil) type configurator struct { diff --git a/runtime/v2/manager_test.go b/runtime/v2/manager_test.go new file mode 100644 index 000000000000..f82ba21d7f12 --- /dev/null +++ b/runtime/v2/manager_test.go @@ -0,0 +1,50 @@ +package runtime + +import ( + "testing" + + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/transaction" + "cosmossdk.io/server/v2/stf" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// MockModule implements both HasMsgHandlers and HasQueryHandlers +type MockModule struct { + mock.Mock + appmodulev2.AppModule +} + +func (m *MockModule) RegisterMsgHandlers(router appmodulev2.MsgRouter) { + m.Called(router) +} + +func (m *MockModule) RegisterQueryHandlers(router appmodulev2.QueryRouter) { + m.Called(router) +} + +func TestRegisterServices(t *testing.T) { + mockModule := new(MockModule) + + app := &App[transaction.Tx]{ + msgRouterBuilder: stf.NewMsgRouterBuilder(), + queryRouterBuilder: stf.NewMsgRouterBuilder(), + } + + mm := &MM[transaction.Tx]{ + modules: map[string]appmodulev2.AppModule{ + "mock": mockModule, + }, + } + + mockModule.On("RegisterMsgHandlers", app.msgRouterBuilder).Once() + mockModule.On("RegisterQueryHandlers", app.queryRouterBuilder).Once() + + err := mm.RegisterServices(app) + + assert.NoError(t, err) + + mockModule.AssertExpectations(t) +} diff --git a/runtime/v2/register_services.go b/runtime/v2/register_services.go new file mode 100644 index 000000000000..f38596d00e0c --- /dev/null +++ b/runtime/v2/register_services.go @@ -0,0 +1,56 @@ +package runtime + +import ( + "fmt" + + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/transaction" + + gogoproto "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/reflect/protoregistry" +) + +func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], registry *protoregistry.Files) error { + c := &configurator{ + grpcQueryDecoders: map[string]func() gogoproto.Message{}, + stfQueryRouter: app.queryRouterBuilder, + stfMsgRouter: app.msgRouterBuilder, + registry: registry, + err: nil, + } + + if services, ok := s.(hasServicesV1); ok { + if err := services.RegisterServices(c); err != nil { + return fmt.Errorf("unable to register services: %w", err) + } + } else { + // If module not implement RegisterServices, register msg & query handler. + if module, ok := s.(appmodulev2.HasMsgHandlers); ok { + module.RegisterMsgHandlers(app.msgRouterBuilder) + } + + if module, ok := s.(appmodulev2.HasQueryHandlers); ok { + module.RegisterQueryHandlers(app.queryRouterBuilder) + // TODO: query regist by RegisterQueryHandlers not in grpcQueryDecoders + if module, ok := s.(interface { + GetQueryDecoders() map[string]func() gogoproto.Message + }); ok { + decoderMap := module.GetQueryDecoders() + for path, decoder := range decoderMap { + app.GRPCMethodsToMessageMap[path] = decoder + } + } + } + } + + if c.err != nil { + app.logger.Warn("error registering services", "error", c.err) + } + + // merge maps + for path, decoder := range c.grpcQueryDecoders { + app.GRPCMethodsToMessageMap[path] = decoder + } + + return nil +} diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go new file mode 100644 index 000000000000..4687812fd664 --- /dev/null +++ b/server/v2/api/rest/server.go @@ -0,0 +1,44 @@ +package rest + +import ( + "context" + + "github.com/gorilla/mux" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/log" + serverv2 "cosmossdk.io/server/v2" +) + +const ( + ServerName = "rest-v2" +) + +type Server[T transaction.Tx] struct { + logger log.Logger + router *mux.Router +} + +func New[T transaction.Tx]() *Server[T] { + return &Server[T]{ + router: mux.NewRouter(), + } +} + +func (s *Server[T]) Name() string { + return ServerName +} + +func (s *Server[T]) Start(ctx context.Context) error { + return nil +} + +func (s *Server[T]) Stop(ctx context.Context) error { + return nil +} + +func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { + s.logger = logger.With(log.ModuleKey, s.Name()) + + return nil +} diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index 916f4b2be9da..c17cb5cb2de1 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -15,6 +15,7 @@ import ( runtimev2 "cosmossdk.io/runtime/v2" serverv2 "cosmossdk.io/server/v2" "cosmossdk.io/server/v2/api/grpc" + "cosmossdk.io/server/v2/api/rest" "cosmossdk.io/server/v2/cometbft" "cosmossdk.io/server/v2/store" "cosmossdk.io/simapp/v2" @@ -81,6 +82,7 @@ func initRootCmd[T transaction.Tx]( ), grpc.New[T](), store.New[T](newApp), + rest.New[T](), ); err != nil { panic(err) } From d7202e4c260173ad7fba7d0b75c5704e9647e171 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 30 Sep 2024 21:02:26 +0200 Subject: [PATCH 02/22] server config with defaults --- server/v2/api/rest/config.go | 18 +++++++++++ server/v2/api/rest/server.go | 50 ++++++++++++++++++++++++++----- server/v2/api/rest/server_test.go | 46 ++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 server/v2/api/rest/config.go create mode 100644 server/v2/api/rest/server_test.go diff --git a/server/v2/api/rest/config.go b/server/v2/api/rest/config.go new file mode 100644 index 000000000000..3adb42fe6536 --- /dev/null +++ b/server/v2/api/rest/config.go @@ -0,0 +1,18 @@ +package rest + +func DefaultConfig() *Config { + return &Config{ + Enable: true, + Address: "localhost:8080", + } +} + +type CfgOption func(*Config) + +// Config defines configuration for the HTTP server. +type Config struct { + // Enable defines if the HTTP server should be enabled. + Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the HTTP server should be enabled."` + // Address defines the API server to listen on + Address string `mapstructure:"address" toml:"address" comment:"Address defines the HTTP server address to bind to."` +} diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 4687812fd664..e74ee1cea010 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -2,8 +2,9 @@ package rest import ( "context" - + "errors" "github.com/gorilla/mux" + "net/http" "cosmossdk.io/core/transaction" "cosmossdk.io/log" @@ -17,11 +18,15 @@ const ( type Server[T transaction.Tx] struct { logger log.Logger router *mux.Router + + httpServer *http.Server + config *Config + cfgOptions []CfgOption } -func New[T transaction.Tx]() *Server[T] { +func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] { return &Server[T]{ - router: mux.NewRouter(), + cfgOptions: cfgOptions, } } @@ -29,16 +34,47 @@ func (s *Server[T]) Name() string { return ServerName } +func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { + s.logger = logger.With(log.ModuleKey, s.Name()) + + return nil +} + func (s *Server[T]) Start(ctx context.Context) error { + s.httpServer = &http.Server{ + Addr: s.config.Address, + Handler: s.router, + } + + go func() { + s.logger.Info("Starting HTTP server", "address", s.config.Address) + if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("Failed to start HTTP server", "error", err) + } + }() + return nil } func (s *Server[T]) Stop(ctx context.Context) error { - return nil + if !s.config.Enable { + return nil + } + s.logger.Info("Stopping HTTP server") + + return s.httpServer.Shutdown(ctx) } -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - s.logger = logger.With(log.ModuleKey, s.Name()) +func (s *Server[T]) Config() any { + if s.config == nil || s.config == (&Config{}) { + cfg := DefaultConfig() - return nil + for _, opt := range s.cfgOptions { + opt(cfg) + } + + return cfg + } + + return s.config } diff --git a/server/v2/api/rest/server_test.go b/server/v2/api/rest/server_test.go new file mode 100644 index 000000000000..cec027f5d24b --- /dev/null +++ b/server/v2/api/rest/server_test.go @@ -0,0 +1,46 @@ +package rest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/transaction" +) + +func TestServerConfig(t *testing.T) { + testCases := []struct { + name string + setupFunc func() *Config + expectedConfig *Config + }{ + { + name: "Default configuration, no custom configuration", + setupFunc: func() *Config { + s := New[transaction.Tx]() + return s.Config().(*Config) + }, + expectedConfig: DefaultConfig(), + }, + { + name: "Custom configuration", + setupFunc: func() *Config { + s := New[transaction.Tx](func(config *Config) { + config.Enable = false + }) + return s.Config().(*Config) + }, + expectedConfig: &Config{ + Enable: false, // Custom configuration + Address: "localhost:8080", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := tc.setupFunc() + require.Equal(t, tc.expectedConfig, config) + }) + } +} From f1d7ebefcd342371ae4b2650ffac113009c22222 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Tue, 1 Oct 2024 00:03:07 +0200 Subject: [PATCH 03/22] pre msgrouter --- runtime/v2/{register_services.go => services.go} | 0 runtime/v2/{manager_test.go => services_test.go} | 0 server/v2/api/rest/server.go | 6 +++++- 3 files changed, 5 insertions(+), 1 deletion(-) rename runtime/v2/{register_services.go => services.go} (100%) rename runtime/v2/{manager_test.go => services_test.go} (100%) diff --git a/runtime/v2/register_services.go b/runtime/v2/services.go similarity index 100% rename from runtime/v2/register_services.go rename to runtime/v2/services.go diff --git a/runtime/v2/manager_test.go b/runtime/v2/services_test.go similarity index 100% rename from runtime/v2/manager_test.go rename to runtime/v2/services_test.go diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index e74ee1cea010..9203a9698b3d 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -3,9 +3,10 @@ package rest import ( "context" "errors" - "github.com/gorilla/mux" "net/http" + "github.com/gorilla/mux" + "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -37,6 +38,8 @@ func (s *Server[T]) Name() string { func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { s.logger = logger.With(log.ModuleKey, s.Name()) + s.config = s.Config().(*Config) + return nil } @@ -60,6 +63,7 @@ func (s *Server[T]) Stop(ctx context.Context) error { if !s.config.Enable { return nil } + s.logger.Info("Stopping HTTP server") return s.httpServer.Shutdown(ctx) From 75e19246315dbad828283c0f97e0b215e978428b Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Tue, 1 Oct 2024 20:17:07 +0200 Subject: [PATCH 04/22] use handlers --- server/v2/api/rest/handler.go | 49 +++++++++++++++++++++++++++++++++++ server/v2/api/rest/server.go | 9 +++++++ 2 files changed, 58 insertions(+) create mode 100644 server/v2/api/rest/handler.go diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go new file mode 100644 index 000000000000..b5b48417ac30 --- /dev/null +++ b/server/v2/api/rest/handler.go @@ -0,0 +1,49 @@ +package rest + +import ( + "cosmossdk.io/core/transaction" + "cosmossdk.io/server/v2/appmanager" + "encoding/json" + "fmt" + gogoproto "github.com/cosmos/gogoproto/proto" + "io" + "net/http" + "strings" +) + +type DefaultHandler[T transaction.Tx] struct { + appManager *appmanager.AppManager[T] +} + +func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading body", http.StatusBadRequest) + return + } + defer r.Body.Close() + + requestType := gogoproto.MessageType(r.URL.Path) + if requestType == nil { + http.Error(w, "Unknown request type", http.StatusNotFound) + return + } + + var data map[string]interface{} + if err := json.Unmarshal(body, &data); err != nil { + http.Error(w, "Error parsing body", http.StatusBadRequest) + return + } + + fmt.Fprintf(w, "Ruta accedida: %s\n", path) + fmt.Fprintf(w, "Datos recibidos:\n") + + json.NewEncoder(w).Encode(data) +} diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 9203a9698b3d..5dcfad9692fe 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -2,6 +2,7 @@ package rest import ( "context" + "cosmossdk.io/server/v2/appmanager" "errors" "net/http" @@ -40,6 +41,14 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L s.config = s.Config().(*Config) + var appManager *appmanager.AppManager[T] + appManager = appI.GetAppManager() + + s.router = mux.NewRouter() + s.router.PathPrefix("/").Handler(&DefaultHandler[T]{ + appManager: appManager, + }) + return nil } From 86d450ff84f39dde28a135128c4f1ab76e60dd02 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Wed, 2 Oct 2024 18:54:57 +0200 Subject: [PATCH 05/22] it queries --- server/v2/api/rest/handler.go | 35 +++++++++++++++++--------- server/v2/api/rest/handler_test.go | 40 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 server/v2/api/rest/handler_test.go diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index b5b48417ac30..e16b84a7b185 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -6,11 +6,18 @@ import ( "encoding/json" "fmt" gogoproto "github.com/cosmos/gogoproto/proto" - "io" + "github.com/gogo/protobuf/jsonpb" "net/http" + "reflect" "strings" ) +const ( + ContentTypeJSON = "application/json" + ContentTypeOctetStream = "application/octet-stream" + ContentTypeProtobuf = "application/x-protobuf" +) + type DefaultHandler[T transaction.Tx] struct { appManager *appmanager.AppManager[T] } @@ -23,27 +30,31 @@ func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error reading body", http.StatusBadRequest) - return + contentType := r.Header.Get("Content-Type") + if contentType != ContentTypeJSON { + contentType = ContentTypeJSON } - defer r.Body.Close() - requestType := gogoproto.MessageType(r.URL.Path) + requestType := gogoproto.MessageType(path) if requestType == nil { http.Error(w, "Unknown request type", http.StatusNotFound) return } - var data map[string]interface{} - if err := json.Unmarshal(body, &data); err != nil { + msg := reflect.New(requestType.Elem()).Interface().(gogoproto.Message) + + err := jsonpb.Unmarshal(r.Body, msg) + if err != nil { http.Error(w, "Error parsing body", http.StatusBadRequest) + fmt.Fprintf(w, "Error parsing body: %v\n", err) return } - fmt.Fprintf(w, "Ruta accedida: %s\n", path) - fmt.Fprintf(w, "Datos recibidos:\n") + query, err := h.appManager.Query(r.Context(), 0, msg) + if err != nil { + http.Error(w, "Error querying", http.StatusInternalServerError) + return + } - json.NewEncoder(w).Encode(data) + json.NewEncoder(w).Encode(query) } diff --git a/server/v2/api/rest/handler_test.go b/server/v2/api/rest/handler_test.go new file mode 100644 index 000000000000..696c068d4ba0 --- /dev/null +++ b/server/v2/api/rest/handler_test.go @@ -0,0 +1,40 @@ +package rest + +import ( + "bytes" + "cosmossdk.io/core/transaction" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestDefaultHandlerServeHTTP(t *testing.T) { + mockAppManager := new(MockAppManager) + handler := &DefaultHandler[transaction.Tx]{ + appManager: mockAppManager, + } + + body := []byte(`{"test": "data"}`) + req, err := http.NewRequest("POST", "/MockMessage", bytes.NewBuffer(body)) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + + expectedResponse := map[string]string{"result": "success"} + mockAppManager.On("Query", mock.Anything, int64(0), mock.AnythingOfType("*rest.MockMessage")).Return(expectedResponse, nil) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + + var response map[string]string + err = json.Unmarshal(rr.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, expectedResponse, response) + + mockAppManager.AssertExpectations(t) +} From f0af908dd5be4a0178a2d0b8589d0eb66dd4b821 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 3 Oct 2024 12:05:11 +0200 Subject: [PATCH 06/22] lint --- x/bank/v2/keeper/keeper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/bank/v2/keeper/keeper.go b/x/bank/v2/keeper/keeper.go index f16982f168c4..dd7aae5a6428 100644 --- a/x/bank/v2/keeper/keeper.go +++ b/x/bank/v2/keeper/keeper.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "cosmossdk.io/collections" "cosmossdk.io/collections/indexes" "cosmossdk.io/core/address" From a67fcc9c234d1bcbfec32cf0d8e3d41f88335eb9 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Fri, 4 Oct 2024 15:14:07 +0200 Subject: [PATCH 07/22] include README --- server/v2/api/rest/README | 73 ++++++++++++++++++++++++++++++ server/v2/api/rest/handler.go | 8 ++-- server/v2/api/rest/handler_test.go | 40 ---------------- server/v2/api/rest/server.go | 2 +- server/v2/go.mod | 4 +- server/v2/go.sum | 2 + 6 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 server/v2/api/rest/README delete mode 100644 server/v2/api/rest/handler_test.go diff --git a/server/v2/api/rest/README b/server/v2/api/rest/README new file mode 100644 index 000000000000..dd53f848e58e --- /dev/null +++ b/server/v2/api/rest/README @@ -0,0 +1,73 @@ +# Cosmos SDK REST API + +This document describes how to use a service that exposes endpoints based on Cosmos SDK Protobuf message types. Each endpoint responds with data in JSON format. + +## General Description + +The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned in JSON format. + +## Example + +### 1. `QueryBalanceRequest` + +This endpoint allows querying the balance of an account given an address and a token denomination. + +- **URL:** `localhost:8080/cosmos.bank.v2.QueryBalanceRequest` + +- **Method:** `POST` + +- **Headers:** + + - `Content-Type: application/json` + +- **Body (JSON):** + + ```json + { + "address": "", + "denom": "" + } + ``` + + - `address`: Account address on the Cosmos network. + - `denom`: Token denomination (e.g., `stake`). + +- **Request Example:** + + ``` + POST localhost:8080/cosmos.bank.v2.QueryBalanceRequest + Content-Type: application/json + + { + "address": "cosmos16tms8tax3ha9exdu7x3maxrvall07yum3rdcu0", + "denom": "stake" + } + ``` + +- **Response Example (JSON):** + + ```json + { + "balance": { + "denom": "stake", + "amount": "1000000" + } + } + ``` + + The response shows the balance of the specified token for the given account. + +## Using Tools + +### 1. Using `curl` + +To make a request using `curl`, you can run the following command: + +```bash +curl -X POST localhost:8080/cosmos.bank.v2.QueryBalanceRequest \ + -H "Content-Type: application/json" \ + -d '{ + "address": "cosmos16tms8tax3ha9exdu7x3maxrvall07yum3rdcu0", + "denom": "stake" + }' +``` \ No newline at end of file diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index e16b84a7b185..808cefa0e87f 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -13,11 +13,13 @@ import ( ) const ( - ContentTypeJSON = "application/json" - ContentTypeOctetStream = "application/octet-stream" - ContentTypeProtobuf = "application/x-protobuf" + ContentTypeJSON = "application/json" ) +func NewDefaultHandler(appManager *appmanager.AppManager[transaction.Tx]) http.Handler { + return &DefaultHandler[transaction.Tx]{appManager: appManager} +} + type DefaultHandler[T transaction.Tx] struct { appManager *appmanager.AppManager[T] } diff --git a/server/v2/api/rest/handler_test.go b/server/v2/api/rest/handler_test.go deleted file mode 100644 index 696c068d4ba0..000000000000 --- a/server/v2/api/rest/handler_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package rest - -import ( - "bytes" - "cosmossdk.io/core/transaction" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestDefaultHandlerServeHTTP(t *testing.T) { - mockAppManager := new(MockAppManager) - handler := &DefaultHandler[transaction.Tx]{ - appManager: mockAppManager, - } - - body := []byte(`{"test": "data"}`) - req, err := http.NewRequest("POST", "/MockMessage", bytes.NewBuffer(body)) - assert.NoError(t, err) - - rr := httptest.NewRecorder() - - expectedResponse := map[string]string{"result": "success"} - mockAppManager.On("Query", mock.Anything, int64(0), mock.AnythingOfType("*rest.MockMessage")).Return(expectedResponse, nil) - - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response map[string]string - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, expectedResponse, response) - - mockAppManager.AssertExpectations(t) -} diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 5dcfad9692fe..02f857a6f4d0 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -2,7 +2,6 @@ package rest import ( "context" - "cosmossdk.io/server/v2/appmanager" "errors" "net/http" @@ -11,6 +10,7 @@ import ( "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" + "cosmossdk.io/server/v2/appmanager" ) const ( diff --git a/server/v2/go.mod b/server/v2/go.mod index 220989a6186e..e2897452bb7e 100644 --- a/server/v2/go.mod +++ b/server/v2/go.mod @@ -21,7 +21,9 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/gogogateway v1.2.0 github.com/cosmos/gogoproto v1.7.0 + github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 + github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-metrics v0.5.3 @@ -61,7 +63,6 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -93,6 +94,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tidwall/btree v1.7.0 // indirect diff --git a/server/v2/go.sum b/server/v2/go.sum index 842232d93dbb..5bd5884006c8 100644 --- a/server/v2/go.sum +++ b/server/v2/go.sum @@ -150,6 +150,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= From 3007e67fc13d178712de0fa56eee98b9a0ee7380 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Fri, 4 Oct 2024 15:18:39 +0200 Subject: [PATCH 08/22] create constructor --- server/v2/api/rest/handler.go | 4 ++-- server/v2/api/rest/server.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index 808cefa0e87f..7eeec1115f15 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -16,8 +16,8 @@ const ( ContentTypeJSON = "application/json" ) -func NewDefaultHandler(appManager *appmanager.AppManager[transaction.Tx]) http.Handler { - return &DefaultHandler[transaction.Tx]{appManager: appManager} +func NewDefaultHandler[T transaction.Tx](appManager *appmanager.AppManager[T]) http.Handler { + return &DefaultHandler[T]{appManager: appManager} } type DefaultHandler[T transaction.Tx] struct { diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 02f857a6f4d0..0935ceb561a3 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -45,9 +45,7 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L appManager = appI.GetAppManager() s.router = mux.NewRouter() - s.router.PathPrefix("/").Handler(&DefaultHandler[T]{ - appManager: appManager, - }) + s.router.PathPrefix("/").Handler(NewDefaultHandler(appManager)) return nil } From b4027dd1636b4792a8f318ee159821fa93fc4731 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Fri, 4 Oct 2024 15:27:29 +0200 Subject: [PATCH 09/22] add lint --- runtime/v2/services_test.go | 6 +++--- server/v2/api/rest/handler.go | 10 ++++++---- x/bank/v2/keeper/keeper.go | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/runtime/v2/services_test.go b/runtime/v2/services_test.go index f82ba21d7f12..83e16d4e7141 100644 --- a/runtime/v2/services_test.go +++ b/runtime/v2/services_test.go @@ -3,12 +3,12 @@ package runtime import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/transaction" "cosmossdk.io/server/v2/stf" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) // MockModule implements both HasMsgHandlers and HasQueryHandlers diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index 7eeec1115f15..e653ef60c499 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -1,15 +1,17 @@ package rest import ( - "cosmossdk.io/core/transaction" - "cosmossdk.io/server/v2/appmanager" "encoding/json" "fmt" - gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/gogo/protobuf/jsonpb" "net/http" "reflect" "strings" + + gogoproto "github.com/cosmos/gogoproto/proto" + "github.com/gogo/protobuf/jsonpb" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/server/v2/appmanager" ) const ( diff --git a/x/bank/v2/keeper/keeper.go b/x/bank/v2/keeper/keeper.go index 338777f4d27d..833fb298ef6f 100644 --- a/x/bank/v2/keeper/keeper.go +++ b/x/bank/v2/keeper/keeper.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/collections" "cosmossdk.io/collections/indexes" "cosmossdk.io/core/address" From f22a135fd99585012d1d55bfad8c4a8bc1633a16 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 7 Oct 2024 18:34:42 +0200 Subject: [PATCH 10/22] remove mux usage --- server/v2/api/rest/server.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 0935ceb561a3..06ba2f57dba6 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -5,8 +5,6 @@ import ( "errors" "net/http" - "github.com/gorilla/mux" - "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -19,7 +17,7 @@ const ( type Server[T transaction.Tx] struct { logger log.Logger - router *mux.Router + router *http.ServeMux httpServer *http.Server config *Config @@ -44,8 +42,8 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L var appManager *appmanager.AppManager[T] appManager = appI.GetAppManager() - s.router = mux.NewRouter() - s.router.PathPrefix("/").Handler(NewDefaultHandler(appManager)) + s.router = http.NewServeMux() + s.router.Handle("/", NewDefaultHandler(appManager)) return nil } From ee8d0a4bb5743fbac8e596623246b892712a45d7 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 7 Oct 2024 18:36:54 +0200 Subject: [PATCH 11/22] go mod tidy --- server/v2/go.mod | 2 -- server/v2/go.sum | 2 -- 2 files changed, 4 deletions(-) diff --git a/server/v2/go.mod b/server/v2/go.mod index 7c008087006a..ebb71e622281 100644 --- a/server/v2/go.mod +++ b/server/v2/go.mod @@ -23,7 +23,6 @@ require ( github.com/cosmos/gogoproto v1.7.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 - github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-metrics v0.5.3 @@ -94,7 +93,6 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tidwall/btree v1.7.0 // indirect diff --git a/server/v2/go.sum b/server/v2/go.sum index 3e16a5f77871..618c4b86ec11 100644 --- a/server/v2/go.sum +++ b/server/v2/go.sum @@ -150,8 +150,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= From a733c8374ca9b8ca3cd33cdc64427ec42e94c78f Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 14:56:27 +0200 Subject: [PATCH 12/22] changes due to review --- server/v2/api/rest/{README => README.md} | 0 server/v2/api/rest/config.go | 8 ++++---- server/v2/api/rest/server.go | 11 +++++------ 3 files changed, 9 insertions(+), 10 deletions(-) rename server/v2/api/rest/{README => README.md} (100%) diff --git a/server/v2/api/rest/README b/server/v2/api/rest/README.md similarity index 100% rename from server/v2/api/rest/README rename to server/v2/api/rest/README.md diff --git a/server/v2/api/rest/config.go b/server/v2/api/rest/config.go index 3adb42fe6536..c71d75bd0b19 100644 --- a/server/v2/api/rest/config.go +++ b/server/v2/api/rest/config.go @@ -9,10 +9,10 @@ func DefaultConfig() *Config { type CfgOption func(*Config) -// Config defines configuration for the HTTP server. +// Config defines configuration for the REST server. type Config struct { - // Enable defines if the HTTP server should be enabled. - Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the HTTP server should be enabled."` + // Enable defines if the REST server should be enabled. + Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the REST server should be enabled."` // Address defines the API server to listen on - Address string `mapstructure:"address" toml:"address" comment:"Address defines the HTTP server address to bind to."` + Address string `mapstructure:"address" toml:"address" comment:"Address defines the REST server address to bind to."` } diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 06ba2f57dba6..f37765af6712 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -54,12 +54,11 @@ func (s *Server[T]) Start(ctx context.Context) error { Handler: s.router, } - go func() { - s.logger.Info("Starting HTTP server", "address", s.config.Address) - if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("Failed to start HTTP server", "error", err) - } - }() + s.logger.Info("Starting HTTP server", "address", s.config.Address) + if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("Failed to start HTTP server", "error", err) + return err + } return nil } From 8dc09a36338bcd6f3bac2e8247dfc916426bfe15 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 14:58:07 +0200 Subject: [PATCH 13/22] add enable flag --- server/v2/api/rest/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index f37765af6712..bd3df1cb2d61 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -3,6 +3,7 @@ package rest import ( "context" "errors" + "fmt" "net/http" "cosmossdk.io/core/transaction" @@ -49,6 +50,11 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L } func (s *Server[T]) Start(ctx context.Context) error { + if !s.config.Enable { + s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) + return nil + } + s.httpServer = &http.Server{ Addr: s.config.Address, Handler: s.router, From c186af23a9d122c8af67ff9bc4a06099ea3158df Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 14:59:19 +0200 Subject: [PATCH 14/22] fix config check --- server/v2/api/rest/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index bd3df1cb2d61..37b409217a6b 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -80,7 +80,7 @@ func (s *Server[T]) Stop(ctx context.Context) error { } func (s *Server[T]) Config() any { - if s.config == nil || s.config == (&Config{}) { + if s.config == nil || s.config.Address == "" { cfg := DefaultConfig() for _, opt := range s.cfgOptions { From 2cf4c623e858b4de3eda22d3be76a71ffda0d66e Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 15:00:59 +0200 Subject: [PATCH 15/22] add some config options --- server/v2/api/rest/config.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/v2/api/rest/config.go b/server/v2/api/rest/config.go index c71d75bd0b19..c1e9eb260fc6 100644 --- a/server/v2/api/rest/config.go +++ b/server/v2/api/rest/config.go @@ -16,3 +16,17 @@ type Config struct { // Address defines the API server to listen on Address string `mapstructure:"address" toml:"address" comment:"Address defines the REST server address to bind to."` } + +// OverwriteDefaultConfig overwrites the default config with the new config. +func OverwriteDefaultConfig(newCfg *Config) CfgOption { + return func(cfg *Config) { + *cfg = *newCfg + } +} + +// Disable the rest server by default (default enabled). +func Disable() CfgOption { + return func(cfg *Config) { + cfg.Enable = false + } +} From 9e6b8fbb0751cda278d8263ad99ead3c29dc342d Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 15:05:04 +0200 Subject: [PATCH 16/22] remove Fprintf --- server/v2/api/rest/handler.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index e653ef60c499..304bbc8cafb0 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -2,7 +2,6 @@ package rest import ( "encoding/json" - "fmt" "net/http" "reflect" "strings" @@ -50,7 +49,6 @@ func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { err := jsonpb.Unmarshal(r.Body, msg) if err != nil { http.Error(w, "Error parsing body", http.StatusBadRequest) - fmt.Fprintf(w, "Error parsing body: %v\n", err) return } From ac25a492ae92e4c895d1cd8d9e85534557020ab1 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 15:11:19 +0200 Subject: [PATCH 17/22] improve part of the code --- server/v2/api/rest/handler.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index 304bbc8cafb0..b5e7cf58778c 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -2,6 +2,8 @@ package rest import ( "encoding/json" + "fmt" + "io" "net/http" "reflect" "strings" @@ -15,6 +17,7 @@ import ( const ( ContentTypeJSON = "application/json" + MaxBodySize = 1 << 20 // 1 MB ) func NewDefaultHandler[T transaction.Tx](appManager *appmanager.AppManager[T]) http.Handler { @@ -44,11 +47,17 @@ func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - msg := reflect.New(requestType.Elem()).Interface().(gogoproto.Message) + msg, ok := reflect.New(requestType.Elem()).Interface().(gogoproto.Message) + if !ok { + http.Error(w, "Failed to create message instance", http.StatusInternalServerError) + return + } - err := jsonpb.Unmarshal(r.Body, msg) + defer r.Body.Close() + limitedReader := io.LimitReader(r.Body, MaxBodySize) + err := jsonpb.Unmarshal(limitedReader, msg) if err != nil { - http.Error(w, "Error parsing body", http.StatusBadRequest) + http.Error(w, fmt.Sprintf("Error parsing body: %v", err), http.StatusBadRequest) return } @@ -58,5 +67,8 @@ func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - json.NewEncoder(w).Encode(query) + w.Header().Set("Content-Type", ContentTypeJSON) + if err := json.NewEncoder(w).Encode(query); err != nil { + http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) + } } From 9dd5c9f6eaeb368874afb3bfe0dd222073335be7 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Thu, 10 Oct 2024 15:18:54 +0200 Subject: [PATCH 18/22] refactor a little --- server/v2/api/rest/handler.go | 65 ++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index b5e7cf58778c..affc0af539fb 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -29,46 +29,71 @@ type DefaultHandler[T transaction.Tx] struct { } func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { - path := strings.TrimPrefix(r.URL.Path, "/") + if err := h.validateMethodIsPOST(r); err != nil { + http.Error(w, err.Error(), http.StatusMethodNotAllowed) + return + } - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + if err := h.validateContentTypeIsJSON(r); err != nil { + http.Error(w, err.Error(), http.StatusUnsupportedMediaType) + return + } + + msg, err := h.createMessage(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + query, err := h.appManager.Query(r.Context(), 0, msg) + if err != nil { + http.Error(w, "Error querying", http.StatusInternalServerError) return } + w.Header().Set("Content-Type", ContentTypeJSON) + if err := json.NewEncoder(w).Encode(query); err != nil { + http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) + } +} + +// validateMethodIsPOST validates that the request method is POST. +func (h *DefaultHandler[T]) validateMethodIsPOST(r *http.Request) error { + if r.Method != http.MethodPost { + return fmt.Errorf("method not allowed") + } + return nil +} + +// validateContentTypeIsJSON validates that the request content type is JSON. +func (h *DefaultHandler[T]) validateContentTypeIsJSON(r *http.Request) error { contentType := r.Header.Get("Content-Type") if contentType != ContentTypeJSON { - contentType = ContentTypeJSON + return fmt.Errorf("unsupported content type, expected %s", ContentTypeJSON) } + return nil +} + +// createMessage creates the message by unmarshalling the request body. +func (h *DefaultHandler[T]) createMessage(r *http.Request) (gogoproto.Message, error) { + path := strings.TrimPrefix(r.URL.Path, "/") requestType := gogoproto.MessageType(path) if requestType == nil { - http.Error(w, "Unknown request type", http.StatusNotFound) - return + return nil, fmt.Errorf("unknown request type") } msg, ok := reflect.New(requestType.Elem()).Interface().(gogoproto.Message) if !ok { - http.Error(w, "Failed to create message instance", http.StatusInternalServerError) - return + return nil, fmt.Errorf("failed to create message instance") } defer r.Body.Close() limitedReader := io.LimitReader(r.Body, MaxBodySize) err := jsonpb.Unmarshal(limitedReader, msg) if err != nil { - http.Error(w, fmt.Sprintf("Error parsing body: %v", err), http.StatusBadRequest) - return - } - - query, err := h.appManager.Query(r.Context(), 0, msg) - if err != nil { - http.Error(w, "Error querying", http.StatusInternalServerError) - return + return nil, fmt.Errorf("error parsing body: %v", err) } - w.Header().Set("Content-Type", ContentTypeJSON) - if err := json.NewEncoder(w).Encode(query); err != nil { - http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) - } + return msg, nil } From 1c7d32cacd737e4db2a0a92900bba19dac0cc1bb Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 14 Oct 2024 12:51:12 +0200 Subject: [PATCH 19/22] use cosmos jsonpb --- server/v2/api/rest/handler.go | 5 ++--- server/v2/go.mod | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index affc0af539fb..9d4819e36ab3 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -8,11 +8,10 @@ import ( "reflect" "strings" - gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/gogo/protobuf/jsonpb" - "cosmossdk.io/core/transaction" "cosmossdk.io/server/v2/appmanager" + "github.com/cosmos/gogoproto/jsonpb" + gogoproto "github.com/cosmos/gogoproto/proto" ) const ( diff --git a/server/v2/go.mod b/server/v2/go.mod index 3044de92a55e..11389921eb76 100644 --- a/server/v2/go.mod +++ b/server/v2/go.mod @@ -22,7 +22,6 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/gogogateway v1.2.0 github.com/cosmos/gogoproto v1.7.0 - github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-hclog v1.6.3 @@ -62,6 +61,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect From 506267d2efaf2417103a4b59c98f76246e01ce2e Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Mon, 14 Oct 2024 12:54:24 +0200 Subject: [PATCH 20/22] unmarshal server config --- server/v2/api/rest/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 37b409217a6b..6cab8ba291d0 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -38,13 +38,19 @@ func (s *Server[T]) Name() string { func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { s.logger = logger.With(log.ModuleKey, s.Name()) - s.config = s.Config().(*Config) + serverCfg := s.Config().(*Config) + if len(cfg) > 0 { + if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { + return fmt.Errorf("failed to unmarshal config: %w", err) + } + } var appManager *appmanager.AppManager[T] appManager = appI.GetAppManager() s.router = http.NewServeMux() s.router.Handle("/", NewDefaultHandler(appManager)) + s.config = serverCfg return nil } From 3608feb8051e0ee544c80b384cec5164d4399804 Mon Sep 17 00:00:00 2001 From: Randy Grok <@faulttolerance.net> Date: Wed, 16 Oct 2024 09:13:54 +0200 Subject: [PATCH 21/22] make lint and comments from review --- collections/quad.go | 4 ---- server/v2/api/rest/handler.go | 5 +++-- server/v2/api/rest/server.go | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/collections/quad.go b/collections/quad.go index 2e0678e69ea2..9d8d42a60644 100644 --- a/collections/quad.go +++ b/collections/quad.go @@ -106,7 +106,6 @@ type quadKeyCodec[K1, K2, K3, K4 any] struct { type jsonQuadKey [4]json.RawMessage - // EncodeJSON encodes Quads to json func (t quadKeyCodec[K1, K2, K3, K4]) EncodeJSON(value Quad[K1, K2, K3, K4]) ([]byte, error) { json1, err := t.keyCodec1.EncodeJSON(*value.k1) @@ -210,7 +209,6 @@ func (t quadKeyCodec[K1, K2, K3, K4]) KeyType() string { return fmt.Sprintf("Quad[%s,%s,%s,%s]", t.keyCodec1.KeyType(), t.keyCodec2.KeyType(), t.keyCodec3.KeyType(), t.keyCodec4.KeyType()) } - func (t quadKeyCodec[K1, K2, K3, K4]) Encode(buffer []byte, key Quad[K1, K2, K3, K4]) (int, error) { writtenTotal := 0 if key.k1 != nil { @@ -243,8 +241,6 @@ func (t quadKeyCodec[K1, K2, K3, K4]) Encode(buffer []byte, key Quad[K1, K2, K3, } return writtenTotal, nil } - - func (t quadKeyCodec[K1, K2, K3, K4]) Decode(buffer []byte) (int, Quad[K1, K2, K3, K4], error) { readTotal := 0 read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index 9d4819e36ab3..665bfcd5757c 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -8,10 +8,11 @@ import ( "reflect" "strings" - "cosmossdk.io/core/transaction" - "cosmossdk.io/server/v2/appmanager" "github.com/cosmos/gogoproto/jsonpb" gogoproto "github.com/cosmos/gogoproto/proto" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/server/v2/appmanager" ) const ( diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 6cab8ba291d0..aa511d2fe968 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -66,9 +66,9 @@ func (s *Server[T]) Start(ctx context.Context) error { Handler: s.router, } - s.logger.Info("Starting HTTP server", "address", s.config.Address) + s.logger.Info("starting HTTP server", "address", s.config.Address) if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("Failed to start HTTP server", "error", err) + s.logger.Error("failed to start HTTP server", "error", err) return err } @@ -80,7 +80,7 @@ func (s *Server[T]) Stop(ctx context.Context) error { return nil } - s.logger.Info("Stopping HTTP server") + s.logger.Info("stopping HTTP server") return s.httpServer.Shutdown(ctx) } From db3ac74ffcbdbaad6fdaeb33620fafd408b665b2 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Wed, 16 Oct 2024 16:11:45 +0200 Subject: [PATCH 22/22] lint fix --- server/v2/api/rest/handler.go | 2 +- server/v2/api/rest/server.go | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go index 665bfcd5757c..a5338f35cf31 100644 --- a/server/v2/api/rest/handler.go +++ b/server/v2/api/rest/handler.go @@ -92,7 +92,7 @@ func (h *DefaultHandler[T]) createMessage(r *http.Request) (gogoproto.Message, e limitedReader := io.LimitReader(r.Body, MaxBodySize) err := jsonpb.Unmarshal(limitedReader, msg) if err != nil { - return nil, fmt.Errorf("error parsing body: %v", err) + return nil, fmt.Errorf("error parsing body: %w", err) } return msg, nil diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index aa511d2fe968..eaa0fe8b6b94 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -9,7 +9,6 @@ import ( "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" - "cosmossdk.io/server/v2/appmanager" ) const ( @@ -45,11 +44,8 @@ func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.L } } - var appManager *appmanager.AppManager[T] - appManager = appI.GetAppManager() - s.router = http.NewServeMux() - s.router.Handle("/", NewDefaultHandler(appManager)) + s.router.Handle("/", NewDefaultHandler(appI.GetAppManager())) s.config = serverCfg return nil