github.com/Finschia/finschia-sdk@v0.49.1/x/auth/tx/service.go (about)

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