2020-10-30 05:32:02 -07:00
|
|
|
package tx
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
2020-11-12 06:22:54 -08:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2020-10-30 05:32:02 -07:00
|
|
|
|
|
|
|
gogogrpc "github.com/gogo/protobuf/grpc"
|
|
|
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
2020-11-12 06:22:54 -08:00
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
|
|
tmtypes "github.com/tendermint/tendermint/types"
|
|
|
|
|
2020-10-30 05:32:02 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2020-11-12 06:22:54 -08:00
|
|
|
pagination "github.com/cosmos/cosmos-sdk/types/query"
|
2020-10-30 05:32:02 -07:00
|
|
|
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
|
|
|
)
|
|
|
|
|
|
|
|
// baseAppSimulateFn is the signature of the Baseapp#Simulate function.
|
|
|
|
type baseAppSimulateFn func(txBytes []byte) (sdk.GasInfo, *sdk.Result, error)
|
|
|
|
|
|
|
|
// txServer is the server for the protobuf Tx service.
|
|
|
|
type txServer struct {
|
|
|
|
clientCtx client.Context
|
|
|
|
simulate baseAppSimulateFn
|
|
|
|
interfaceRegistry codectypes.InterfaceRegistry
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTxServer creates a new Tx service server.
|
|
|
|
func NewTxServer(clientCtx client.Context, simulate baseAppSimulateFn, interfaceRegistry codectypes.InterfaceRegistry) txtypes.ServiceServer {
|
|
|
|
return txServer{
|
|
|
|
clientCtx: clientCtx,
|
|
|
|
simulate: simulate,
|
|
|
|
interfaceRegistry: interfaceRegistry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ txtypes.ServiceServer = txServer{}
|
|
|
|
|
2020-11-12 06:22:54 -08:00
|
|
|
const (
|
|
|
|
eventFormat = "{eventType}.{eventAttribute}={value}"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TxsByEvents implements the ServiceServer.TxsByEvents RPC method.
|
|
|
|
func (s txServer) GetTxsEvent(ctx context.Context, req *txtypes.GetTxsEventRequest) (*txtypes.GetTxsEventResponse, error) {
|
2020-12-02 09:50:40 -08:00
|
|
|
if req == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
|
|
|
}
|
|
|
|
|
2020-11-30 20:18:35 -08:00
|
|
|
page, limit, err := pagination.ParsePagination(req.Pagination)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-11-12 06:22:54 -08:00
|
|
|
}
|
|
|
|
|
2020-11-30 20:18:35 -08:00
|
|
|
if len(req.Events) == 0 {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "must declare at least one event to search")
|
2020-11-12 06:22:54 -08:00
|
|
|
}
|
|
|
|
|
2020-11-30 20:18:35 -08:00
|
|
|
tmEvents := make([]string, len(req.Events))
|
|
|
|
for i, event := range req.Events {
|
2020-11-12 06:22:54 -08:00
|
|
|
if !strings.Contains(event, "=") {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid event; event %s should be of the format: %s", event, eventFormat))
|
|
|
|
} else if strings.Count(event, "=") > 1 {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid event; event %s should be of the format: %s", event, eventFormat))
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens := strings.Split(event, "=")
|
|
|
|
if tokens[0] == tmtypes.TxHeightKey {
|
|
|
|
event = fmt.Sprintf("%s=%s", tokens[0], tokens[1])
|
|
|
|
} else {
|
|
|
|
event = fmt.Sprintf("%s='%s'", tokens[0], tokens[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
tmEvents[i] = event
|
|
|
|
}
|
|
|
|
|
|
|
|
query := strings.Join(tmEvents, " AND ")
|
|
|
|
|
|
|
|
result, err := s.clientCtx.Client.TxSearch(ctx, query, false, &page, &limit, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a proto codec, we need it to unmarshal the tx bytes.
|
|
|
|
cdc := codec.NewProtoCodec(s.clientCtx.InterfaceRegistry)
|
|
|
|
txRespList := make([]*sdk.TxResponse, len(result.Txs))
|
|
|
|
txsList := make([]*txtypes.Tx, len(result.Txs))
|
|
|
|
|
|
|
|
for i, tx := range result.Txs {
|
|
|
|
txResp := txResultToTxResponse(&tx.TxResult)
|
|
|
|
txResp.Height = tx.Height
|
|
|
|
txResp.TxHash = tx.Hash.String()
|
|
|
|
txRespList[i] = txResp
|
|
|
|
|
|
|
|
var protoTx txtypes.Tx
|
|
|
|
if err := cdc.UnmarshalBinaryBare(tx.Tx, &protoTx); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txsList[i] = &protoTx
|
|
|
|
}
|
|
|
|
|
|
|
|
return &txtypes.GetTxsEventResponse{
|
|
|
|
Txs: txsList,
|
|
|
|
TxResponses: txRespList,
|
|
|
|
Pagination: &pagination.PageResponse{
|
|
|
|
Total: uint64(result.TotalCount),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-30 05:32:02 -07:00
|
|
|
// Simulate implements the ServiceServer.Simulate RPC method.
|
|
|
|
func (s txServer) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*txtypes.SimulateResponse, error) {
|
2020-12-02 09:50:40 -08:00
|
|
|
if req == nil || req.Tx == nil {
|
2020-10-30 05:32:02 -07:00
|
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := req.Tx.UnpackInterfaces(s.interfaceRegistry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txBytes, err := req.Tx.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
gasInfo, result, err := s.simulate(txBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &txtypes.SimulateResponse{
|
|
|
|
GasInfo: &gasInfo,
|
|
|
|
Result: result,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTx implements the ServiceServer.GetTx RPC method.
|
|
|
|
func (s txServer) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) {
|
2020-12-02 09:50:40 -08:00
|
|
|
if req == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
|
|
|
}
|
|
|
|
|
2020-10-30 05:32:02 -07:00
|
|
|
// We get hash as a hex string in the request, convert it to bytes.
|
|
|
|
hash, err := hex.DecodeString(req.Hash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO We should also check the proof flag in gRPC header.
|
|
|
|
// https://github.com/cosmos/cosmos-sdk/issues/7036.
|
|
|
|
result, err := s.clientCtx.Client.Tx(ctx, hash, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a proto codec, we need it to unmarshal the tx bytes.
|
|
|
|
cdc := codec.NewProtoCodec(s.clientCtx.InterfaceRegistry)
|
|
|
|
|
2020-11-12 06:22:54 -08:00
|
|
|
var protoTx txtypes.Tx
|
2020-10-30 05:32:02 -07:00
|
|
|
if err := cdc.UnmarshalBinaryBare(result.Tx, &protoTx); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-12 06:22:54 -08:00
|
|
|
txResp := txResultToTxResponse(&result.TxResult)
|
|
|
|
txResp.Height = result.Height
|
|
|
|
txResp.TxHash = result.Hash.String()
|
|
|
|
|
2020-10-30 05:32:02 -07:00
|
|
|
return &txtypes.GetTxResponse{
|
2020-11-12 06:22:54 -08:00
|
|
|
Tx: &protoTx,
|
|
|
|
TxResponse: txResp,
|
2020-10-30 05:32:02 -07:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-12-02 09:50:40 -08:00
|
|
|
func (s txServer) BroadcastTx(ctx context.Context, req *txtypes.BroadcastTxRequest) (*txtypes.BroadcastTxResponse, error) {
|
|
|
|
return client.TxServiceBroadcast(ctx, s.clientCtx, req)
|
|
|
|
}
|
|
|
|
|
2020-10-30 05:32:02 -07:00
|
|
|
// RegisterTxService registers the tx service on the gRPC router.
|
|
|
|
func RegisterTxService(
|
|
|
|
qrt gogogrpc.Server,
|
|
|
|
clientCtx client.Context,
|
|
|
|
simulateFn baseAppSimulateFn,
|
|
|
|
interfaceRegistry codectypes.InterfaceRegistry,
|
|
|
|
) {
|
|
|
|
txtypes.RegisterServiceServer(
|
|
|
|
qrt,
|
|
|
|
NewTxServer(clientCtx, simulateFn, interfaceRegistry),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterGRPCGatewayRoutes mounts the tx service's GRPC-gateway routes on the
|
|
|
|
// given Mux.
|
|
|
|
func RegisterGRPCGatewayRoutes(clientConn gogogrpc.ClientConn, mux *runtime.ServeMux) {
|
|
|
|
txtypes.RegisterServiceHandlerClient(context.Background(), mux, txtypes.NewServiceClient(clientConn))
|
|
|
|
}
|
2020-11-12 06:22:54 -08:00
|
|
|
|
|
|
|
func txResultToTxResponse(respTx *abci.ResponseDeliverTx) *sdk.TxResponse {
|
|
|
|
logs, _ := sdk.ParseABCILogs(respTx.Log)
|
|
|
|
return &sdk.TxResponse{
|
|
|
|
Code: respTx.Code,
|
|
|
|
Codespace: respTx.Codespace,
|
|
|
|
GasUsed: respTx.GasUsed,
|
|
|
|
GasWanted: respTx.GasWanted,
|
|
|
|
Info: respTx.Info,
|
|
|
|
Logs: logs,
|
|
|
|
}
|
|
|
|
}
|