package client import ( "encoding/hex" "errors" "fmt" "strings" "time" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // QueryTxsByEvents performs a search for transactions for a given set of events // via the Tendermint RPC. An event takes the form of: // "{eventAttribute}.{attributeKey} = '{attributeValue}'". Each event is // concatenated with an 'AND' operand. It returns a slice of Info object // containing txs and metadata. An error is returned if the query fails. // If an empty string is provided it will order txs by asc func QueryTxsByEvents(clientCtx client.Context, events []string, page, limit int, orderBy string) (*sdk.SearchTxsResult, error) { if len(events) == 0 { return nil, errors.New("must declare at least one event to search") } if page <= 0 { return nil, errors.New("page must greater than 0") } if limit <= 0 { return nil, errors.New("limit must greater than 0") } // XXX: implement ANY query := strings.Join(events, " AND ") node, err := clientCtx.GetNode() if err != nil { return nil, err } // TODO: this may not always need to be proven // https://github.com/cosmos/cosmos-sdk/issues/6807 resTxs, err := node.TxSearch(query, true, &page, &limit, orderBy) if err != nil { return nil, err } resBlocks, err := getBlocksForTxResults(clientCtx, resTxs.Txs) if err != nil { return nil, err } txs, err := formatTxResults(clientCtx.TxConfig, resTxs.Txs, resBlocks) if err != nil { return nil, err } result := sdk.NewSearchTxsResult(uint64(resTxs.TotalCount), uint64(len(txs)), uint64(page), uint64(limit), txs) return result, nil } // QueryTx queries for a single transaction by a hash string in hex format. An // error is returned if the transaction does not exist or cannot be queried. func QueryTx(clientCtx client.Context, hashHexStr string) (*sdk.TxResponse, error) { hash, err := hex.DecodeString(hashHexStr) if err != nil { return nil, err } node, err := clientCtx.GetNode() if err != nil { return nil, err } //TODO: this may not always need to be proven // https://github.com/cosmos/cosmos-sdk/issues/6807 resTx, err := node.Tx(hash, true) if err != nil { return nil, err } resBlocks, err := getBlocksForTxResults(clientCtx, []*ctypes.ResultTx{resTx}) if err != nil { return nil, err } out, err := formatTxResult(clientCtx.TxConfig, resTx, resBlocks[resTx.Height]) if err != nil { return out, err } return out, nil } // formatTxResults parses the indexed txs into a slice of TxResponse objects. func formatTxResults(txConfig client.TxConfig, resTxs []*ctypes.ResultTx, resBlocks map[int64]*ctypes.ResultBlock) ([]*sdk.TxResponse, error) { var err error out := make([]*sdk.TxResponse, len(resTxs)) for i := range resTxs { out[i], err = formatTxResult(txConfig, resTxs[i], resBlocks[resTxs[i].Height]) if err != nil { return nil, err } } return out, nil } func getBlocksForTxResults(clientCtx client.Context, resTxs []*ctypes.ResultTx) (map[int64]*ctypes.ResultBlock, error) { node, err := clientCtx.GetNode() if err != nil { return nil, err } resBlocks := make(map[int64]*ctypes.ResultBlock) for _, resTx := range resTxs { if _, ok := resBlocks[resTx.Height]; !ok { resBlock, err := node.Block(&resTx.Height) if err != nil { return nil, err } resBlocks[resTx.Height] = resBlock } } return resBlocks, nil } func formatTxResult(txConfig client.TxConfig, resTx *ctypes.ResultTx, resBlock *ctypes.ResultBlock) (*sdk.TxResponse, error) { anyTx, err := parseTx(txConfig, resTx.Tx) if err != nil { return nil, err } return sdk.NewResponseResultTx(resTx, anyTx.AsAny(), resBlock.Block.Time.Format(time.RFC3339)), nil } func parseTx(txConfig client.TxConfig, txBytes []byte) (codectypes.IntoAny, error) { var tx sdk.Tx tx, err := txConfig.TxDecoder()(txBytes) if err != nil { return nil, err } anyTx, ok := tx.(codectypes.IntoAny) if !ok { return nil, fmt.Errorf("tx cannot be packed into Any") } return anyTx, nil }