github.com/cosmos/cosmos-sdk@v0.50.10/x/auth/tx/service.go (about) 1 package tx 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 gogogrpc "github.com/cosmos/gogoproto/grpc" 9 "github.com/golang/protobuf/proto" //nolint:staticcheck // keep legacy for now 10 "github.com/grpc-ecosystem/grpc-gateway/runtime" 11 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/status" 13 14 "github.com/cosmos/cosmos-sdk/client" 15 "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" 16 codectypes "github.com/cosmos/cosmos-sdk/codec/types" 17 sdk "github.com/cosmos/cosmos-sdk/types" 18 sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 19 "github.com/cosmos/cosmos-sdk/types/query" 20 txtypes "github.com/cosmos/cosmos-sdk/types/tx" 21 "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" 22 ) 23 24 // baseAppSimulateFn is the signature of the Baseapp#Simulate function. 25 type baseAppSimulateFn func(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) 26 27 // txServer is the server for the protobuf Tx service. 28 type txServer struct { 29 clientCtx client.Context 30 simulate baseAppSimulateFn 31 interfaceRegistry codectypes.InterfaceRegistry 32 } 33 34 // NewTxServer creates a new Tx service server. 35 func NewTxServer(clientCtx client.Context, simulate baseAppSimulateFn, interfaceRegistry codectypes.InterfaceRegistry) txtypes.ServiceServer { 36 return txServer{ 37 clientCtx: clientCtx, 38 simulate: simulate, 39 interfaceRegistry: interfaceRegistry, 40 } 41 } 42 43 var _ txtypes.ServiceServer = txServer{} 44 45 // GetTxsEvent implements the ServiceServer.TxsByEvents RPC method. 46 func (s txServer) GetTxsEvent(ctx context.Context, req *txtypes.GetTxsEventRequest) (*txtypes.GetTxsEventResponse, error) { 47 if req == nil { 48 return nil, status.Error(codes.InvalidArgument, "request cannot be nil") 49 } 50 51 orderBy := parseOrderBy(req.OrderBy) 52 53 result, err := QueryTxsByEvents(s.clientCtx, int(req.Page), int(req.Limit), req.Query, orderBy) 54 if err != nil { 55 return nil, status.Error(codes.Internal, err.Error()) 56 } 57 58 txsList := make([]*txtypes.Tx, len(result.Txs)) 59 for i, tx := range result.Txs { 60 protoTx, ok := tx.Tx.GetCachedValue().(*txtypes.Tx) 61 if !ok { 62 return nil, status.Errorf(codes.Internal, "expected %T, got %T", txtypes.Tx{}, tx.Tx.GetCachedValue()) 63 } 64 65 txsList[i] = protoTx 66 } 67 68 return &txtypes.GetTxsEventResponse{ 69 Txs: txsList, 70 TxResponses: result.Txs, 71 Total: result.TotalCount, 72 }, nil 73 } 74 75 // Simulate implements the ServiceServer.Simulate RPC method. 76 func (s txServer) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*txtypes.SimulateResponse, error) { 77 if req == nil { 78 return nil, status.Error(codes.InvalidArgument, "invalid empty tx") 79 } 80 81 txBytes := req.TxBytes 82 if txBytes == nil && req.Tx != nil { 83 // This block is for backwards-compatibility. 84 // We used to support passing a `Tx` in req. But if we do that, sig 85 // verification might not pass, because the .Marshal() below might not 86 // be the same marshaling done by the client. 87 var err error 88 txBytes, err = proto.Marshal(req.Tx) 89 if err != nil { 90 return nil, status.Errorf(codes.InvalidArgument, "invalid tx; %v", err) 91 } 92 } 93 94 if txBytes == nil { 95 return nil, status.Errorf(codes.InvalidArgument, "empty txBytes is not allowed") 96 } 97 98 gasInfo, result, err := s.simulate(txBytes) 99 if err != nil { 100 return nil, status.Errorf(codes.Unknown, "%v with gas used: '%d'", err, gasInfo.GasUsed) 101 } 102 103 return &txtypes.SimulateResponse{ 104 GasInfo: &gasInfo, 105 Result: result, 106 }, nil 107 } 108 109 // GetTx implements the ServiceServer.GetTx RPC method. 110 func (s txServer) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) { 111 if req == nil { 112 return nil, status.Error(codes.InvalidArgument, "request cannot be nil") 113 } 114 115 if len(req.Hash) == 0 { 116 return nil, status.Error(codes.InvalidArgument, "tx hash cannot be empty") 117 } 118 119 // TODO We should also check the proof flag in gRPC header. 120 // https://github.com/cosmos/cosmos-sdk/issues/7036. 121 result, err := QueryTx(s.clientCtx, req.Hash) 122 if err != nil { 123 if strings.Contains(err.Error(), "not found") { 124 return nil, status.Errorf(codes.NotFound, "tx not found: %s", req.Hash) 125 } 126 127 return nil, err 128 } 129 130 protoTx, ok := result.Tx.GetCachedValue().(*txtypes.Tx) 131 if !ok { 132 return nil, status.Errorf(codes.Internal, "expected %T, got %T", txtypes.Tx{}, result.Tx.GetCachedValue()) 133 } 134 135 return &txtypes.GetTxResponse{ 136 Tx: protoTx, 137 TxResponse: result, 138 }, nil 139 } 140 141 // protoTxProvider is a type which can provide a proto transaction. It is a 142 // workaround to get access to the wrapper TxBuilder's method GetProtoTx(). 143 // ref: https://github.com/cosmos/cosmos-sdk/issues/10347 144 type protoTxProvider interface { 145 GetProtoTx() *txtypes.Tx 146 } 147 148 // GetBlockWithTxs returns a block with decoded txs. 149 func (s txServer) GetBlockWithTxs(ctx context.Context, req *txtypes.GetBlockWithTxsRequest) (*txtypes.GetBlockWithTxsResponse, error) { 150 if req == nil { 151 return nil, status.Error(codes.InvalidArgument, "request cannot be nil") 152 } 153 154 sdkCtx := sdk.UnwrapSDKContext(ctx) 155 currentHeight := sdkCtx.BlockHeight() 156 157 if req.Height < 1 || req.Height > currentHeight { 158 return nil, sdkerrors.ErrInvalidHeight.Wrapf("requested height %d but height must not be less than 1 "+ 159 "or greater than the current height %d", req.Height, currentHeight) 160 } 161 162 blockID, block, err := cmtservice.GetProtoBlock(ctx, s.clientCtx, &req.Height) 163 if err != nil { 164 return nil, err 165 } 166 167 var offset, limit uint64 168 if req.Pagination != nil { 169 offset = req.Pagination.Offset 170 limit = req.Pagination.Limit 171 } else { 172 offset = 0 173 limit = query.DefaultLimit 174 } 175 176 blockTxs := block.Data.Txs 177 blockTxsLn := uint64(len(blockTxs)) 178 txs := make([]*txtypes.Tx, 0, limit) 179 if offset >= blockTxsLn && blockTxsLn != 0 { 180 return nil, sdkerrors.ErrInvalidRequest.Wrapf("out of range: cannot paginate %d txs with offset %d and limit %d", blockTxsLn, offset, limit) 181 } 182 decodeTxAt := func(i uint64) error { 183 tx := blockTxs[i] 184 txb, err := s.clientCtx.TxConfig.TxDecoder()(tx) 185 if err != nil { 186 return err 187 } 188 p, ok := txb.(protoTxProvider) 189 if !ok { 190 return sdkerrors.ErrTxDecode.Wrapf("could not cast %T to %T", txb, txtypes.Tx{}) 191 } 192 txs = append(txs, p.GetProtoTx()) 193 return nil 194 } 195 if req.Pagination != nil && req.Pagination.Reverse { 196 for i, count := offset, uint64(0); i > 0 && count != limit; i, count = i-1, count+1 { 197 if err = decodeTxAt(i); err != nil { 198 sdkCtx.Logger().Error("failed to decode tx", "error", err) 199 } 200 } 201 } else { 202 for i, count := offset, uint64(0); i < blockTxsLn && count != limit; i, count = i+1, count+1 { 203 if err = decodeTxAt(i); err != nil { 204 sdkCtx.Logger().Error("failed to decode tx", "error", err) 205 } 206 } 207 } 208 209 return &txtypes.GetBlockWithTxsResponse{ 210 Txs: txs, 211 BlockId: &blockID, 212 Block: block, 213 Pagination: &query.PageResponse{ 214 Total: blockTxsLn, 215 }, 216 }, nil 217 } 218 219 // BroadcastTx implements the ServiceServer.BroadcastTx RPC method. 220 func (s txServer) BroadcastTx(ctx context.Context, req *txtypes.BroadcastTxRequest) (*txtypes.BroadcastTxResponse, error) { 221 return client.TxServiceBroadcast(ctx, s.clientCtx, req) 222 } 223 224 // TxEncode implements the ServiceServer.TxEncode RPC method. 225 func (s txServer) TxEncode(ctx context.Context, req *txtypes.TxEncodeRequest) (*txtypes.TxEncodeResponse, error) { 226 if req.Tx == nil { 227 return nil, status.Error(codes.InvalidArgument, "invalid empty tx") 228 } 229 230 txBuilder := &wrapper{tx: req.Tx} 231 232 encodedBytes, err := s.clientCtx.TxConfig.TxEncoder()(txBuilder) 233 if err != nil { 234 return nil, err 235 } 236 237 return &txtypes.TxEncodeResponse{ 238 TxBytes: encodedBytes, 239 }, nil 240 } 241 242 // TxEncodeAmino implements the ServiceServer.TxEncodeAmino RPC method. 243 func (s txServer) TxEncodeAmino(ctx context.Context, req *txtypes.TxEncodeAminoRequest) (*txtypes.TxEncodeAminoResponse, error) { 244 if req.AminoJson == "" { 245 return nil, status.Error(codes.InvalidArgument, "invalid empty tx json") 246 } 247 248 var stdTx legacytx.StdTx 249 err := s.clientCtx.LegacyAmino.UnmarshalJSON([]byte(req.AminoJson), &stdTx) 250 if err != nil { 251 return nil, err 252 } 253 254 encodedBytes, err := s.clientCtx.LegacyAmino.Marshal(stdTx) 255 if err != nil { 256 return nil, err 257 } 258 259 return &txtypes.TxEncodeAminoResponse{ 260 AminoBinary: encodedBytes, 261 }, nil 262 } 263 264 // TxDecode implements the ServiceServer.TxDecode RPC method. 265 func (s txServer) TxDecode(ctx context.Context, req *txtypes.TxDecodeRequest) (*txtypes.TxDecodeResponse, error) { 266 if req.TxBytes == nil { 267 return nil, status.Error(codes.InvalidArgument, "invalid empty tx bytes") 268 } 269 270 txb, err := s.clientCtx.TxConfig.TxDecoder()(req.TxBytes) 271 if err != nil { 272 return nil, err 273 } 274 275 txWrapper, ok := txb.(*wrapper) 276 if ok { 277 return &txtypes.TxDecodeResponse{ 278 Tx: txWrapper.tx, 279 }, nil 280 } 281 282 return nil, fmt.Errorf("expected %T, got %T", &wrapper{}, txb) 283 } 284 285 // TxDecodeAmino implements the ServiceServer.TxDecodeAmino RPC method. 286 func (s txServer) TxDecodeAmino(ctx context.Context, req *txtypes.TxDecodeAminoRequest) (*txtypes.TxDecodeAminoResponse, error) { 287 if req.AminoBinary == nil { 288 return nil, status.Error(codes.InvalidArgument, "invalid empty tx bytes") 289 } 290 291 var stdTx legacytx.StdTx 292 err := s.clientCtx.LegacyAmino.Unmarshal(req.AminoBinary, &stdTx) 293 if err != nil { 294 return nil, err 295 } 296 297 res, err := s.clientCtx.LegacyAmino.MarshalJSON(stdTx) 298 if err != nil { 299 return nil, err 300 } 301 302 return &txtypes.TxDecodeAminoResponse{ 303 AminoJson: string(res), 304 }, nil 305 } 306 307 // RegisterTxService registers the tx service on the gRPC router. 308 func RegisterTxService( 309 qrt gogogrpc.Server, 310 clientCtx client.Context, 311 simulateFn baseAppSimulateFn, 312 interfaceRegistry codectypes.InterfaceRegistry, 313 ) { 314 txtypes.RegisterServiceServer( 315 qrt, 316 NewTxServer(clientCtx, simulateFn, interfaceRegistry), 317 ) 318 } 319 320 // RegisterGRPCGatewayRoutes mounts the tx service's GRPC-gateway routes on the 321 // given Mux. 322 func RegisterGRPCGatewayRoutes(clientConn gogogrpc.ClientConn, mux *runtime.ServeMux) { 323 txtypes.RegisterServiceHandlerClient(context.Background(), mux, txtypes.NewServiceClient(clientConn)) 324 } 325 326 func parseOrderBy(orderBy txtypes.OrderBy) string { 327 switch orderBy { 328 case txtypes.OrderBy_ORDER_BY_ASC: 329 return "asc" 330 case txtypes.OrderBy_ORDER_BY_DESC: 331 return "desc" 332 default: 333 return "" // Defaults to CometBFT's default, which is `asc` now. 334 } 335 }