From db667a849d58cd9cd43c41bbb80141dcc54c5c26 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:58:12 +0100 Subject: [PATCH] fix(simapp/v2): register extra gRPC gateway routes (backport #22786) (#22798) Co-authored-by: Julien Robert --- server/v2/cometbft/grpc.go | 195 ++++++++++++++++++++++--------- simapp/v2/simdv2/cmd/commands.go | 31 +++-- 2 files changed, 166 insertions(+), 60 deletions(-) diff --git a/server/v2/cometbft/grpc.go b/server/v2/cometbft/grpc.go index 66bbc86731f1..170475246d35 100644 --- a/server/v2/cometbft/grpc.go +++ b/server/v2/cometbft/grpc.go @@ -3,6 +3,7 @@ package cometbft import ( "context" "fmt" + "strings" abci "github.com/cometbft/cometbft/abci/types" abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1" @@ -24,6 +25,8 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" ) type appSimulator[T transaction.Tx] interface { @@ -50,51 +53,6 @@ func gRPCServiceRegistrar[T transaction.Tx]( } } -// CometBFTAutoCLIDescriptor is the auto-generated CLI descriptor for the CometBFT service -var CometBFTAutoCLIDescriptor = &autocliv1.ServiceCommandDescriptor{ - Service: cmtv1beta1.Service_ServiceDesc.ServiceName, - RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - { - RpcMethod: "GetNodeInfo", - Use: "node-info", - Short: "Query the current node info", - }, - { - RpcMethod: "GetSyncing", - Use: "syncing", - Short: "Query node syncing status", - }, - { - RpcMethod: "GetLatestBlock", - Use: "block-latest", - Short: "Query for the latest committed block", - }, - { - RpcMethod: "GetBlockByHeight", - Use: "block-by-height ", - Short: "Query for a committed block by height", - Long: "Query for a specific committed block using the CometBFT RPC `block_by_height` method", - PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}}, - }, - { - RpcMethod: "GetLatestValidatorSet", - Use: "validator-set", - Alias: []string{"validator-set-latest", "comet-validator-set", "cometbft-validator-set", "tendermint-validator-set"}, - Short: "Query for the latest validator set", - }, - { - RpcMethod: "GetValidatorSetByHeight", - Use: "validator-set-by-height ", - Short: "Query for a validator set by height", - PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}}, - }, - { - RpcMethod: "ABCIQuery", - Skip: true, - }, - }, -} - type txServer[T transaction.Tx] struct { clientCtx client.Context txCodec transaction.Codec[T] @@ -112,8 +70,33 @@ func (t txServer[T]) GetBlockWithTxs(context.Context, *txtypes.GetBlockWithTxsRe } // GetTx implements tx.ServiceServer. -func (t txServer[T]) GetTx(context.Context, *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) { - return nil, status.Error(codes.Unimplemented, "not implemented") +func (t txServer[T]) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "request cannot be nil") + } + + if len(req.Hash) == 0 { + return nil, status.Error(codes.InvalidArgument, "tx hash cannot be empty") + } + + result, err := authtx.QueryTx(t.clientCtx, req.Hash) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return nil, status.Errorf(codes.NotFound, "tx not found: %s", req.Hash) + } + + return nil, err + } + + protoTx, ok := result.Tx.GetCachedValue().(*txtypes.Tx) + if !ok { + return nil, status.Errorf(codes.Internal, "expected %T, got %T", txtypes.Tx{}, result.Tx.GetCachedValue()) + } + + return &txtypes.GetTxResponse{ + Tx: protoTx, + TxResponse: result, + }, nil } // GetTxsEvent implements tx.ServiceServer. @@ -181,18 +164,79 @@ func (t txServer[T]) TxDecode(context.Context, *txtypes.TxDecodeRequest) (*txtyp } // TxDecodeAmino implements tx.ServiceServer. -func (t txServer[T]) TxDecodeAmino(context.Context, *txtypes.TxDecodeAminoRequest) (*txtypes.TxDecodeAminoResponse, error) { - return nil, status.Error(codes.Unimplemented, "not implemented") +func (t txServer[T]) TxDecodeAmino(_ context.Context, req *txtypes.TxDecodeAminoRequest) (*txtypes.TxDecodeAminoResponse, error) { + if req.AminoBinary == nil { + return nil, status.Error(codes.InvalidArgument, "invalid empty tx bytes") + } + + var stdTx legacytx.StdTx + err := t.clientCtx.LegacyAmino.Unmarshal(req.AminoBinary, &stdTx) + if err != nil { + return nil, err + } + + res, err := t.clientCtx.LegacyAmino.MarshalJSON(stdTx) + if err != nil { + return nil, err + } + + return &txtypes.TxDecodeAminoResponse{ + AminoJson: string(res), + }, nil } // TxEncode implements tx.ServiceServer. -func (t txServer[T]) TxEncode(context.Context, *txtypes.TxEncodeRequest) (*txtypes.TxEncodeResponse, error) { - return nil, status.Error(codes.Unimplemented, "not implemented") +func (t txServer[T]) TxEncode(_ context.Context, req *txtypes.TxEncodeRequest) (*txtypes.TxEncodeResponse, error) { + if req.Tx == nil { + return nil, status.Error(codes.InvalidArgument, "invalid empty tx") + } + + bodyBytes, err := t.clientCtx.Codec.Marshal(req.Tx.Body) + if err != nil { + return nil, err + } + + authInfoBytes, err := t.clientCtx.Codec.Marshal(req.Tx.AuthInfo) + if err != nil { + return nil, err + } + + raw := &txtypes.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: req.Tx.Signatures, + } + + encodedBytes, err := t.clientCtx.Codec.Marshal(raw) + if err != nil { + return nil, err + } + + return &txtypes.TxEncodeResponse{ + TxBytes: encodedBytes, + }, nil } // TxEncodeAmino implements tx.ServiceServer. -func (t txServer[T]) TxEncodeAmino(context.Context, *txtypes.TxEncodeAminoRequest) (*txtypes.TxEncodeAminoResponse, error) { - return nil, status.Error(codes.Unimplemented, "not implemented") +func (t txServer[T]) TxEncodeAmino(_ context.Context, req *txtypes.TxEncodeAminoRequest) (*txtypes.TxEncodeAminoResponse, error) { + if req.AminoJson == "" { + return nil, status.Error(codes.InvalidArgument, "invalid empty tx json") + } + + var stdTx legacytx.StdTx + err := t.clientCtx.LegacyAmino.UnmarshalJSON([]byte(req.AminoJson), &stdTx) + if err != nil { + return nil, err + } + + encodedBytes, err := t.clientCtx.LegacyAmino.Marshal(stdTx) + if err != nil { + return nil, err + } + + return &txtypes.TxEncodeAminoResponse{ + AminoBinary: encodedBytes, + }, nil } var _ txtypes.ServiceServer = txServer[transaction.Tx]{} @@ -236,3 +280,48 @@ func (s nodeServer[T]) Status(ctx context.Context, _ *nodeservice.StatusRequest) ValidatorHash: nodeInfo.LastBlockAppHash, }, nil } + +// CometBFTAutoCLIDescriptor is the auto-generated CLI descriptor for the CometBFT service +var CometBFTAutoCLIDescriptor = &autocliv1.ServiceCommandDescriptor{ + Service: cmtv1beta1.Service_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "GetNodeInfo", + Use: "node-info", + Short: "Query the current node info", + }, + { + RpcMethod: "GetSyncing", + Use: "syncing", + Short: "Query node syncing status", + }, + { + RpcMethod: "GetLatestBlock", + Use: "block-latest", + Short: "Query for the latest committed block", + }, + { + RpcMethod: "GetBlockByHeight", + Use: "block-by-height ", + Short: "Query for a committed block by height", + Long: "Query for a specific committed block using the CometBFT RPC `block_by_height` method", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}}, + }, + { + RpcMethod: "GetLatestValidatorSet", + Use: "validator-set", + Alias: []string{"validator-set-latest", "comet-validator-set", "cometbft-validator-set", "tendermint-validator-set"}, + Short: "Query for the latest validator set", + }, + { + RpcMethod: "GetValidatorSetByHeight", + Use: "validator-set-by-height ", + Short: "Query for a validator set by height", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}}, + }, + { + RpcMethod: "ABCIQuery", + Skip: true, + }, + }, +} diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index a2eea8b49fbc..28586e32c2de 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "io" "github.com/spf13/cobra" @@ -23,11 +24,14 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/rpc" sdktelemetry "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/genutil" @@ -153,13 +157,7 @@ func InitRootCmd[T transaction.Tx]( if err != nil { return nil, err } - - for _, mod := range deps.ModuleManager.Modules() { - if gmod, ok := mod.(module.HasGRPCGateway); ok { - // TODO(@julienrbrt) https://github.com/cosmos/cosmos-sdk/pull/22701#pullrequestreview-2470651390 - gmod.RegisterGRPCGatewayRoutes(deps.ClientContext, grpcgatewayServer.GRPCGatewayRouter) - } - } + registerGRPCGatewayRoutes[T](deps, grpcgatewayServer) // wire server commands return serverv2.AddCommands[T]( @@ -264,3 +262,22 @@ func RootCommandPersistentPreRun(clientCtx client.Context) func(*cobra.Command, return nil } } + +// registerGRPCGatewayRoutes registers the gRPC gateway routes for all modules and other components +// TODO(@julienrbrt): Eventually, this should removed and directly done within the grpcgateway.Server +// ref: https://github.com/cosmos/cosmos-sdk/pull/22701#pullrequestreview-2470651390 +func registerGRPCGatewayRoutes[T transaction.Tx]( + deps CommandDependencies[T], + server *grpcgateway.Server[T], +) { + // those are the extra services that the CometBFT server implements (server/v2/cometbft/grpc.go) + cmtservice.RegisterGRPCGatewayRoutes(deps.ClientContext, server.GRPCGatewayRouter) + _ = nodeservice.RegisterServiceHandlerClient(context.Background(), server.GRPCGatewayRouter, nodeservice.NewServiceClient(deps.ClientContext)) + _ = txtypes.RegisterServiceHandlerClient(context.Background(), server.GRPCGatewayRouter, txtypes.NewServiceClient(deps.ClientContext)) + + for _, mod := range deps.ModuleManager.Modules() { + if gmod, ok := mod.(module.HasGRPCGateway); ok { + gmod.RegisterGRPCGatewayRoutes(deps.ClientContext, server.GRPCGatewayRouter) + } + } +}