diff --git a/core/testing/context.go b/core/testing/context.go index ec3248e6405e..bd036705bcef 100644 --- a/core/testing/context.go +++ b/core/testing/context.go @@ -2,6 +2,7 @@ package coretesting import ( "context" + "cosmossdk.io/core/event" "cosmossdk.io/core/gas" "cosmossdk.io/core/header" diff --git a/schema/appdata/mux.go b/schema/appdata/mux.go index 1eab8b69a6fe..9be2942549d9 100644 --- a/schema/appdata/mux.go +++ b/schema/appdata/mux.go @@ -139,7 +139,7 @@ func ListenerMux(listeners ...Listener) Listener { mux.onBatch = func(batch PacketBatch) error { for _, listener := range listeners { - err := batch.apply(&listener) //nolint:gosec // aliasing is safe here + err := batch.apply(&listener) if err != nil { return err } diff --git a/testutil/queryclient/queryclient.go b/testutil/queryclient/queryclient.go new file mode 100644 index 000000000000..57c2a329e2e1 --- /dev/null +++ b/testutil/queryclient/queryclient.go @@ -0,0 +1,131 @@ +package queryclient + +import ( + "context" + "fmt" + + abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/encoding" + + "github.com/cosmos/cosmos-sdk/client/grpc/reflection" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" +) + +var ( + _ gogogrpc.ClientConn = &QueryHelper{} + _ gogogrpc.ClientConn = &QueryHelper{} +) + +// QueryHelper is a test utility for building a query client from a proto interface registry. +type QueryHelper struct { + cdc encoding.Codec + routes map[string]GRPCQueryHandler +} + +func NewQueryHelper(interfaceRegistry codectypes.InterfaceRegistry) *QueryHelper { + // instantiate the codec + cdc := codec.NewProtoCodec(interfaceRegistry).GRPCCodec() + // Once we have an interface registry, we can register the interface + // registry reflection gRPC service. + + qH := &QueryHelper{ + cdc: cdc, + routes: map[string]GRPCQueryHandler{}, + } + + reflection.RegisterReflectionServiceServer(qH, reflection.NewReflectionServiceServer(interfaceRegistry)) + + return qH +} + +// Invoke implements the grpc ClientConn.Invoke method +func (q *QueryHelper) Invoke(ctx context.Context, method string, args, reply interface{}, _ ...grpc.CallOption) error { + querier := q.Route(method) + if querier == nil { + return fmt.Errorf("handler not found for %s", method) + } + reqBz, err := q.cdc.Marshal(args) + if err != nil { + return err + } + + res, err := querier(ctx, &abci.QueryRequest{Data: reqBz}) + if err != nil { + return err + } + + err = q.cdc.Unmarshal(res.Value, reply) + if err != nil { + return err + } + + return nil +} + +// NewStream implements the grpc ClientConn.NewStream method +func (q *QueryHelper) NewStream(context.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) { + panic("not implemented") +} + +// GRPCQueryHandler defines a function type which handles ABCI Query requests +// using gRPC +type GRPCQueryHandler = func(ctx context.Context, req *abci.QueryRequest) (*abci.QueryResponse, error) + +// Route returns the GRPCQueryHandler for a given query route path or nil +// if not found +func (qrt *QueryHelper) Route(path string) GRPCQueryHandler { + handler, found := qrt.routes[path] + if !found { + return nil + } + return handler +} + +// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC +// service description, handler is an object which implements that gRPC service/ +// +// This functions PANICS: +// - if a protobuf service is registered twice. +func (qrt *QueryHelper) RegisterService(sd *grpc.ServiceDesc, handler interface{}) { + // adds a top-level query handler based on the gRPC service name + for _, method := range sd.Methods { + qrt.registerABCIQueryHandler(sd, method, handler) + } +} + +func (qrt *QueryHelper) registerABCIQueryHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) { + fqName := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName) + methodHandler := method.Handler + + _, found := qrt.routes[fqName] + if found { + panic(fmt.Sprintf("handler for %s already registered", fqName)) + } + + qrt.routes[fqName] = func(ctx context.Context, req *abci.QueryRequest) (*abci.QueryResponse, error) { + // call the method handler from the service description with the handler object, + // a wrapped sdk.Context with proto-unmarshaled data from the ABCI request data + res, err := methodHandler(handler, ctx, func(i interface{}) error { + return qrt.cdc.Unmarshal(req.Data, i) + }, nil) + if err != nil { + return nil, err + } + + // proto marshal the result bytes + var resBytes []byte + resBytes, err = qrt.cdc.Marshal(res) + if err != nil { + return nil, err + } + + // return the result bytes as the response value + return &abci.QueryResponse{ + Height: req.Height, + Value: resBytes, + }, nil + } +} diff --git a/x/bank/keeper/grpc_query_test.go b/x/bank/keeper/grpc_query_test.go index c8bd5096d50c..f1e13dc4d93f 100644 --- a/x/bank/keeper/grpc_query_test.go +++ b/x/bank/keeper/grpc_query_test.go @@ -347,7 +347,7 @@ func (suite *KeeperTestSuite) TestQueryParams() { func (suite *KeeperTestSuite) TestQueryDenomsMetadata() { var ( req *types.QueryDenomsMetadataRequest - expMetadata = []types.Metadata{} + expMetadata []types.Metadata ) testCases := []struct { diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 82bebe171a87..31ec3e9974d4 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -27,6 +27,7 @@ import ( codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil/queryclient" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" @@ -117,7 +118,7 @@ type KeeperTestSuite struct { addrCdc address.Codec authKeeper *banktestutil.MockAccountKeeper - queryClient banktypes.QueryServer + queryClient banktypes.QueryClient msgServer banktypes.MsgServer encCfg moduletestutil.TestEncodingConfig @@ -164,11 +165,11 @@ func (suite *KeeperTestSuite) SetupTest() { DefaultSendEnabled: banktypes.DefaultDefaultSendEnabled, })) + queryHelper := queryclient.NewQueryHelper(encCfg.InterfaceRegistry) + banktypes.RegisterQueryServer(queryHelper, suite.bankKeeper) banktypes.RegisterInterfaces(encCfg.InterfaceRegistry) - queryServer := keeper.NewQuerier(&suite.bankKeeper) - - suite.queryClient = queryServer + suite.queryClient = banktypes.NewQueryClient(queryHelper) suite.msgServer = keeper.NewMsgServerImpl(suite.bankKeeper) suite.encCfg = encCfg suite.env = env