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 }