From 78194e1cdd8baf65891bcf3a0480eb309af96c63 Mon Sep 17 00:00:00 2001 From: atheeshp <59333759+atheeshp@users.noreply.github.com> Date: Tue, 25 Aug 2020 21:14:13 +0530 Subject: [PATCH] gRPC gateway init (#7019) * WIP: grpc server setup * add register grpc routes * updated go mod * updated grpc for all modules * added pb file for grpc gateway * udpated gw file * added a test for grpc route * fixed conflicts * added grpc server * grpc tests added * cleanup * Fix gateway forward issue * Add godoc * updated tests * fix lint * fix tests * fix tests * fix tests * fixed test * Add grpc headers * Fix error handling * Fix tests - hacky * Fix lint * remove debug logs * Fix review comments * move grpc tests into a separate file * Fix protobuf version * Update x/capability/module.go * Fix godoc * Fix review suggestions * Fix codec * Add query params test for gateway request * Fix gofmt Co-authored-by: anilCSE Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- client/grpc_query.go | 14 +++- go.mod | 1 + go.sum | 11 ++- server/api/server.go | 49 ++++++++++- server/start.go | 1 - simapp/app.go | 1 + tests/mocks/types_module_module.go | 37 +++++++++ testutil/rest.go | 38 +++++++++ types/module/module.go | 9 ++ x/auth/module.go | 5 ++ x/bank/client/rest/grpc_query_test.go | 93 +++++++++++++++++++++ x/bank/module.go | 7 ++ x/capability/module.go | 9 +- x/crisis/module.go | 4 + x/distribution/module.go | 4 + x/evidence/module.go | 5 ++ x/genutil/module.go | 5 ++ x/gov/module.go | 5 ++ x/ibc-transfer/module.go | 5 ++ x/ibc/module.go | 5 ++ x/mint/module.go | 5 ++ x/params/module.go | 4 + x/slashing/module.go | 5 ++ x/staking/client/rest/grpc_query_test.go | 101 +++++++++++++++++++++++ x/staking/module.go | 7 ++ x/upgrade/module.go | 4 + 26 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 testutil/rest.go create mode 100644 x/bank/client/rest/grpc_query_test.go create mode 100644 x/staking/client/rest/grpc_query_test.go diff --git a/client/grpc_query.go b/client/grpc_query.go index ace9e37a52..7f1fe6a394 100644 --- a/client/grpc_query.go +++ b/client/grpc_query.go @@ -27,6 +27,18 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, args, reply if err != nil { return err } + + // parse height header + md, _ := metadata.FromOutgoingContext(grpcCtx) + if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 { + height, err := strconv.ParseInt(heights[0], 10, 64) + if err != nil { + return err + } + + ctx = ctx.WithHeight(height) + } + req := abci.RequestQuery{ Path: method, Data: reqBz, @@ -47,7 +59,7 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, args, reply // We then parse all the call options, if the call option is a // HeaderCallOption, then we manually set the value of that header to the // metadata. - md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10)) + md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10)) for _, callOpt := range opts { header, ok := callOpt.(grpc.HeaderCallOption) if !ok { diff --git a/go.mod b/go.mod index dc1a2a6ccd..68dee21c79 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/cosmos/iavl v0.15.0-rc2 github.com/cosmos/ledger-cosmos-go v0.11.1 github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25 + github.com/gogo/gateway v1.1.0 github.com/gogo/protobuf v1.3.1 github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.2 diff --git a/go.sum b/go.sum index 705cb11807..8d0bcd1945 100644 --- a/go.sum +++ b/go.sum @@ -178,6 +178,8 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= +github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -213,6 +215,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -231,14 +234,10 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw= github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -248,7 +247,9 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.7 h1:Nk5kuHrnWUTf/0GL1a/vchH/om9Ap2/HnVna+jYZgTY= github.com/grpc-ecosystem/grpc-gateway v1.14.7/go.mod h1:oYZKL012gGh6LMyg/xA7Q2yq6j8bu0wa+9w14EEthWU= @@ -740,6 +741,7 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaR google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -761,6 +763,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= diff --git a/server/api/server.go b/server/api/server.go index 9d475bf6fc..2ca63509bb 100644 --- a/server/api/server.go +++ b/server/api/server.go @@ -7,8 +7,10 @@ import ( "strings" "time" + "github.com/gogo/gateway" "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/rakyll/statik/fs" "github.com/tendermint/tendermint/libs/log" tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" @@ -16,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/telemetry" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/cosmos/cosmos-sdk/types/rest" // unnamed import of statik for swagger UI support @@ -24,19 +27,54 @@ import ( // Server defines the server's API interface. type Server struct { - Router *mux.Router - ClientCtx client.Context + Router *mux.Router + GRPCRouter *runtime.ServeMux + ClientCtx client.Context logger log.Logger metrics *telemetry.Metrics listener net.Listener } +// CustomGRPCHeaderMatcher for mapping request headers to +// GRPC metadata. +// HTTP headers that start with 'Grpc-Metadata-' are automatically mapped to +// gRPC metadata after removing prefix 'Grpc-Metadata-'. We can use this +// CustomGRPCHeaderMatcher if headers don't start with `Grpc-Metadata-` +func CustomGRPCHeaderMatcher(key string) (string, bool) { + switch strings.ToLower(key) { + case grpctypes.GRPCBlockHeightHeader: + return grpctypes.GRPCBlockHeightHeader, true + default: + return runtime.DefaultHeaderMatcher(key) + } +} + func New(clientCtx client.Context, logger log.Logger) *Server { + // The default JSON marshaller used by the gRPC-Gateway is unable to marshal non-nullable non-scalar fields. + // Using the gogo/gateway package with the gRPC-Gateway WithMarshaler option fixes the scalar field marshalling issue. + marshalerOption := &gateway.JSONPb{ + EmitDefaults: true, + Indent: " ", + OrigName: true, + } + return &Server{ Router: mux.NewRouter(), ClientCtx: clientCtx, logger: logger, + GRPCRouter: runtime.NewServeMux( + // Custom marshaler option is required for gogo proto + runtime.WithMarshalerOption(runtime.MIMEWildcard, marshalerOption), + + // This is necessary to get error details properly + // marshalled in unary requests. + runtime.WithProtoErrorHandler(runtime.DefaultHTTPProtoErrorHandler), + + // Custom header matcher for mapping request headers to + // GRPC metadata + runtime.WithIncomingHeaderMatcher(CustomGRPCHeaderMatcher), + ), } } @@ -70,6 +108,7 @@ func (s *Server) Start(cfg config.Config) error { return err } + s.registerGRPCRoutes() s.listener = listener var h http.Handler = s.Router @@ -93,7 +132,11 @@ func (s *Server) registerSwaggerUI() { } staticServer := http.FileServer(statikFS) - s.Router.PathPrefix("/").Handler(staticServer) + s.Router.PathPrefix("/legacy").Handler(staticServer) +} + +func (s *Server) registerGRPCRoutes() { + s.Router.PathPrefix("/").Handler(s.GRPCRouter) } func (s *Server) registerMetrics() { diff --git a/server/start.go b/server/start.go index b554d57149..f4571e06e6 100644 --- a/server/start.go +++ b/server/start.go @@ -240,7 +240,6 @@ func startInProcess(ctx *Context, legacyAminoCdc *codec.LegacyAmino, appCreator apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server")) app.RegisterAPIRoutes(apiSrv) - errCh := make(chan error) go func() { diff --git a/simapp/app.go b/simapp/app.go index aed2ffa455..6b21ef8cfa 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -512,6 +512,7 @@ func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server) { rpc.RegisterRoutes(clientCtx, apiSvr.Router) authrest.RegisterTxRoutes(clientCtx, apiSvr.Router) ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router) + ModuleBasics.RegisterGRPCRoutes(apiSvr.ClientCtx, apiSvr.GRPCRouter) } // GetMaccPerms returns a copy of the module account permissions diff --git a/tests/mocks/types_module_module.go b/tests/mocks/types_module_module.go index cf32937f80..2d18a9b9c0 100644 --- a/tests/mocks/types_module_module.go +++ b/tests/mocks/types_module_module.go @@ -15,6 +15,7 @@ import ( grpc "github.com/gogo/protobuf/grpc" gomock "github.com/golang/mock/gomock" mux "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" cobra "github.com/spf13/cobra" types1 "github.com/tendermint/tendermint/abci/types" ) @@ -120,6 +121,18 @@ func (mr *MockAppModuleBasicMockRecorder) RegisterRESTRoutes(arg0, arg1 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRESTRoutes", reflect.TypeOf((*MockAppModuleBasic)(nil).RegisterRESTRoutes), arg0, arg1) } +// RegisterGRPCRoutes mocks base method +func (m *MockAppModuleBasic) RegisterGRPCRoutes(arg0 client.Context, arg1 *runtime.ServeMux) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RegisterGRPCRoutes", arg0, arg1) +} + +// RegisterGRPCRoutes indicates an expected call of RegisterGRPCRoutes +func (mr *MockAppModuleBasicMockRecorder) RegisterGRPCRoutes(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterGRPCRoutes", reflect.TypeOf((*MockAppModuleBasic)(nil).RegisterGRPCRoutes), arg0, arg1) +} + // GetTxCmd mocks base method func (m *MockAppModuleBasic) GetTxCmd() *cobra.Command { m.ctrl.T.Helper() @@ -249,6 +262,18 @@ func (mr *MockAppModuleGenesisMockRecorder) RegisterRESTRoutes(arg0, arg1 interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRESTRoutes", reflect.TypeOf((*MockAppModuleGenesis)(nil).RegisterRESTRoutes), arg0, arg1) } +// RegisterGRPCRoutes mocks base method +func (m *MockAppModuleGenesis) RegisterGRPCRoutes(arg0 client.Context, arg1 *runtime.ServeMux) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RegisterRESTRoutes", arg0, arg1) +} + +// RegisterGRPCRoutes indicates an expected call of RegisterGRPCRoutes +func (mr *MockAppModuleGenesisMockRecorder) RegisterGRPCRoutes(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterGRPCRoutes", reflect.TypeOf((*MockAppModuleGenesis)(nil).RegisterGRPCRoutes), arg0, arg1) +} + // GetTxCmd mocks base method func (m *MockAppModuleGenesis) GetTxCmd() *cobra.Command { m.ctrl.T.Helper() @@ -406,6 +431,18 @@ func (mr *MockAppModuleMockRecorder) RegisterRESTRoutes(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRESTRoutes", reflect.TypeOf((*MockAppModule)(nil).RegisterRESTRoutes), arg0, arg1) } +// RegisterGRPCRoutes mocks base method +func (m *MockAppModule) RegisterGRPCRoutes(arg0 client.Context, arg1 *runtime.ServeMux) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RegisterGRPCRoutes", arg0, arg1) +} + +// RegisterGRPCRoutes indicates an expected call of RegisterGRPCRoutes +func (mr *MockAppModuleMockRecorder) RegisterGRPCRoutes(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterGRPCRoutes", reflect.TypeOf((*MockAppModule)(nil).RegisterGRPCRoutes), arg0, arg1) +} + // GetTxCmd mocks base method func (m *MockAppModule) GetTxCmd() *cobra.Command { m.ctrl.T.Helper() diff --git a/testutil/rest.go b/testutil/rest.go new file mode 100644 index 0000000000..b468b16bed --- /dev/null +++ b/testutil/rest.go @@ -0,0 +1,38 @@ +package testutil + +import ( + "io/ioutil" + "net/http" +) + +// GetRequestWithHeaders defines a wrapper around an HTTP GET request with a provided URL +// and custom headers +// An error is returned if the request or reading the body fails. +func GetRequestWithHeaders(url string, headers map[string]string) ([]byte, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + + for key, value := range headers { + req.Header.Set(key, value) + } + + res, err := client.Do(req) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if err = res.Body.Close(); err != nil { + return nil, err + } + + return body, nil +} diff --git a/types/module/module.go b/types/module/module.go index 27401c64a5..aa88a80bcb 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -32,6 +32,7 @@ import ( "encoding/json" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -56,6 +57,7 @@ type AppModuleBasic interface { // client functionality RegisterRESTRoutes(client.Context, *mux.Router) + RegisterGRPCRoutes(client.Context, *runtime.ServeMux) GetTxCmd() *cobra.Command GetQueryCmd() *cobra.Command } @@ -114,6 +116,13 @@ func (bm BasicManager) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rou } } +// RegisterGRPCRoutes registers all module rest routes +func (bm BasicManager) RegisterGRPCRoutes(clientCtx client.Context, rtr *runtime.ServeMux) { + for _, b := range bm { + b.RegisterGRPCRoutes(clientCtx, rtr) + } +} + // AddTxCommands adds all tx commands to the rootTxCmd. // // TODO: Remove clientCtx argument. diff --git a/x/auth/module.go b/x/auth/module.go index c831693807..78b84bb228 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -6,6 +6,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -64,6 +65,10 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout rest.RegisterRoutes(clientCtx, rtr, types.StoreKey) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the auth module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the root tx command for the auth module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.GetTxCmd() diff --git a/x/bank/client/rest/grpc_query_test.go b/x/bank/client/rest/grpc_query_test.go new file mode 100644 index 0000000000..1ca5946b1d --- /dev/null +++ b/x/bank/client/rest/grpc_query_test.go @@ -0,0 +1,93 @@ +package rest_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func (s *IntegrationTestSuite) TestTotalSupplyGRPCHandler() { + val := s.network.Validators[0] + baseURL := val.APIAddress + + testCases := []struct { + name string + url string + headers map[string]string + respType fmt.Stringer + expected fmt.Stringer + }{ + { + "test GRPC total supply", + fmt.Sprintf("%s/cosmos/bank/v1beta1/supply", baseURL), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + &types.QueryTotalSupplyResponse{}, + &types.QueryTotalSupplyResponse{ + Supply: sdk.NewCoins( + sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens), + sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))), + ), + }, + }, + { + "GRPC total supply of a specific denom", + fmt.Sprintf("%s/cosmos/bank/v1beta1/supply/%s", baseURL, s.cfg.BondDenom), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + &types.QuerySupplyOfResponse{}, + &types.QuerySupplyOfResponse{ + Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))), + }, + }, + { + "Query for `height` > 1", + fmt.Sprintf("%s/cosmos/bank/v1beta1/supply/%s", baseURL, s.cfg.BondDenom), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "2", + }, + &types.QuerySupplyOfResponse{}, + &types.QuerySupplyOfResponse{ + Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(20))), + }, + }, + { + "Query params shouldn't be considered as height", + fmt.Sprintf("%s/cosmos/bank/v1beta1/supply/%s?height=2", baseURL, s.cfg.BondDenom), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + &types.QuerySupplyOfResponse{}, + &types.QuerySupplyOfResponse{ + Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))), + }, + }, + { + "GRPC total supply of a bogus denom", + fmt.Sprintf("%s/cosmos/bank/v1beta1/supply/foobar", baseURL), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + &types.QuerySupplyOfResponse{}, + &types.QuerySupplyOfResponse{ + Amount: sdk.NewCoin("foobar", sdk.ZeroInt()), + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers) + s.Require().NoError(err) + + s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, tc.respType)) + s.Require().Equal(tc.expected.String(), tc.respType.String()) + }) + } +} diff --git a/x/bank/module.go b/x/bank/module.go index 21726cf86a..0d7e33b1c8 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -1,12 +1,14 @@ package bank import ( + "context" "encoding/json" "fmt" "math/rand" "github.com/gogo/protobuf/grpc" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -61,6 +63,11 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout rest.RegisterHandlers(clientCtx, rtr) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the bank module. +func (a AppModuleBasic) RegisterGRPCRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) +} + // GetTxCmd returns the root tx command for the bank module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/capability/module.go b/x/capability/module.go index b4aa43f021..ed081a6495 100644 --- a/x/capability/module.go +++ b/x/capability/module.go @@ -5,15 +5,16 @@ import ( "fmt" "math/rand" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/gogo/protobuf/grpc" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -71,6 +72,10 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxE // RegisterRESTRoutes registers the capability module's REST service handlers. func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} +// RegisterGRPCRoutes registers the gRPC Gateway routes for the capability module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the capability module's root tx command. func (a AppModuleBasic) GetTxCmd() *cobra.Command { return nil } diff --git a/x/crisis/module.go b/x/crisis/module.go index 97f6a10d67..2e0cf6c652 100644 --- a/x/crisis/module.go +++ b/x/crisis/module.go @@ -6,6 +6,7 @@ import ( "github.com/gogo/protobuf/grpc" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -56,6 +57,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxE // RegisterRESTRoutes registers no REST routes for the crisis module. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} +// RegisterGRPCRoutes registers the gRPC Gateway routes for the capability module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) {} + // GetTxCmd returns the root tx command for the crisis module. func (b AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/distribution/module.go b/x/distribution/module.go index ca20c9e0ba..d33fc0ef38 100644 --- a/x/distribution/module.go +++ b/x/distribution/module.go @@ -6,6 +6,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -67,6 +68,9 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, rtr *mux.R rest.RegisterHandlers(clientCtx, rtr) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the distribution module. +func (AppModuleBasic) RegisterGRPCRoutes(_ sdkclient.Context, _ *runtime.ServeMux) {} + // GetTxCmd returns the root tx command for the distribution module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/evidence/module.go b/x/evidence/module.go index 9867b692bc..e9997a5e25 100644 --- a/x/evidence/module.go +++ b/x/evidence/module.go @@ -6,6 +6,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -84,6 +85,10 @@ func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Ro rest.RegisterRoutes(clientCtx, rtr, evidenceRESTHandlers) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the evidence module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the evidence module's root tx command. func (a AppModuleBasic) GetTxCmd() *cobra.Command { evidenceCLIHandlers := make([]*cobra.Command, len(a.evidenceHandlers)) diff --git a/x/genutil/module.go b/x/genutil/module.go index 4be57bf7ff..b1dd164759 100644 --- a/x/genutil/module.go +++ b/x/genutil/module.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -55,6 +56,10 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, txEncodingConfi // RegisterRESTRoutes registers the REST routes for the genutil module. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} +// RegisterGRPCRoutes registers the gRPC Gateway routes for the genutil module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns no root tx command for the genutil module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } diff --git a/x/gov/module.go b/x/gov/module.go index 25bbf60482..7616eaee60 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -8,6 +8,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -83,6 +84,10 @@ func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Ro rest.RegisterHandlers(clientCtx, rtr, proposalRESTHandlers) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the gov module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the root tx command for the gov module. func (a AppModuleBasic) GetTxCmd() *cobra.Command { proposalCLIHandlers := make([]*cobra.Command, 0, len(a.proposalHandlers)) diff --git a/x/ibc-transfer/module.go b/x/ibc-transfer/module.go index 799568de7b..6ec3d81f41 100644 --- a/x/ibc-transfer/module.go +++ b/x/ibc-transfer/module.go @@ -6,6 +6,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -66,6 +67,10 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxE func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the ibc-transfer module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd implements AppModuleBasic interface func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/ibc/module.go b/x/ibc/module.go index e015e773fc..fc15814e45 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -7,6 +7,7 @@ import ( "github.com/gogo/protobuf/grpc" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -63,6 +64,10 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxE // RegisterRESTRoutes does nothing. IBC does not support legacy REST routes. func (AppModuleBasic) RegisterRESTRoutes(client.Context, *mux.Router) {} +// RegisterGRPCRoutes registers the gRPC Gateway routes for the ibc module. +func (a AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the root tx command for the ibc module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.GetTxCmd() diff --git a/x/mint/module.go b/x/mint/module.go index 5cdb995e0a..e7b18307dd 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -7,6 +7,7 @@ import ( "github.com/gogo/protobuf/grpc" "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -68,6 +69,10 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout rest.RegisterRoutes(clientCtx, rtr) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the mint module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns no root tx command for the mint module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } diff --git a/x/params/module.go b/x/params/module.go index bc12ca60a9..b056fc3be7 100644 --- a/x/params/module.go +++ b/x/params/module.go @@ -5,6 +5,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -54,6 +55,9 @@ func (AppModuleBasic) ValidateGenesis(_ codec.JSONMarshaler, config client.TxEnc // RegisterRESTRoutes registers the REST routes for the params module. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} +// RegisterGRPCRoutes registers the gRPC Gateway routes for the params module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) {} + // GetTxCmd returns no root tx command for the params module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } diff --git a/x/slashing/module.go b/x/slashing/module.go index 01efbd78e6..0adc2ee92f 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -6,6 +6,7 @@ import ( "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -75,6 +76,10 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout rest.RegisterHandlers(clientCtx, rtr) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the slashig module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) { +} + // GetTxCmd returns the root tx command for the slashing module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/staking/client/rest/grpc_query_test.go b/x/staking/client/rest/grpc_query_test.go new file mode 100644 index 0000000000..ce9aeca034 --- /dev/null +++ b/x/staking/client/rest/grpc_query_test.go @@ -0,0 +1,101 @@ +package rest_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + + s.cfg = cfg + s.network = network.New(s.T(), cfg) + + _, err := s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestQueryValidatorsGRPCHandler() { + val := s.network.Validators[0] + baseURL := val.APIAddress + + testCases := []struct { + name string + url string + headers map[string]string + error bool + }{ + { + "test query validators gRPC route with invalid status", + fmt.Sprintf("%s/cosmos/staking/v1beta1/validators?status=active", baseURL), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + true, + }, + { + "test query validators gRPC route without status query param", + fmt.Sprintf("%s/cosmos/staking/v1beta1/validators", baseURL), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + true, + }, + { + "test query validators gRPC route with valid status", + fmt.Sprintf("%s/cosmos/staking/v1beta1/validators?status=%s", baseURL, sdk.Bonded.String()), + map[string]string{ + grpctypes.GRPCBlockHeightHeader: "1", + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers) + s.Require().NoError(err) + + var valRes types.QueryValidatorsResponse + err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, &valRes) + + if tc.error { + s.Require().Error(err) + s.Require().Nil(valRes.Validators) + s.Require().Equal(0, len(valRes.Validators)) + } else { + s.Require().NoError(err) + s.Require().NotNil(valRes.Validators) + s.Require().Equal(len(s.network.Validators), len(valRes.Validators)) + } + }) + } +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} diff --git a/x/staking/module.go b/x/staking/module.go index 03a5f0bbca..24d3b05304 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -1,11 +1,13 @@ package staking import ( + "context" "encoding/json" "fmt" "math/rand" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -73,6 +75,11 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout rest.RegisterHandlers(clientCtx, rtr) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the staking module. +func (AppModuleBasic) RegisterGRPCRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) +} + // GetTxCmd returns the root tx command for the staking module. func (AppModuleBasic) GetTxCmd() *cobra.Command { return cli.NewTxCmd() diff --git a/x/upgrade/module.go b/x/upgrade/module.go index e13cdadbd6..650715ae46 100644 --- a/x/upgrade/module.go +++ b/x/upgrade/module.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/gogo/protobuf/grpc" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -51,6 +52,9 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, r *mux.Router rest.RegisterRoutes(clientCtx, r) } +// RegisterGRPCRoutes registers the gRPC Gateway routes for the upgrade module. +func (AppModuleBasic) RegisterGRPCRoutes(_ client.Context, _ *runtime.ServeMux) {} + // GetQueryCmd returns the cli query commands for this module func (AppModuleBasic) GetQueryCmd() *cobra.Command { return cli.GetQueryCmd()