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  }