152 lines
4.0 KiB
Go
152 lines
4.0 KiB
Go
package tx
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
coretypes "github.com/cometbft/cometbft/rpc/core/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
querytypes "github.com/cosmos/cosmos-sdk/types/query"
|
|
)
|
|
|
|
// QueryTxsByEvents retrieves a list of paginated transactions from CometBFT's
|
|
// TxSearch RPC method given a set of pagination criteria and an events query.
|
|
// Note, the events query must be valid based on CometBFT's query semantics.
|
|
// An error is returned if the query or parsing fails or if the query is empty.
|
|
//
|
|
// Note, if an empty orderBy is provided, the default behavior is ascending. If
|
|
// negative values are provided for page or limit, defaults will be used.
|
|
func QueryTxsByEvents(clientCtx client.Context, page, limit int, query, orderBy string) (*sdk.SearchTxsResult, error) {
|
|
if len(query) == 0 {
|
|
return nil, errors.New("query cannot be empty")
|
|
}
|
|
|
|
// CometBFT node.TxSearch that is used for querying txs defines pages
|
|
// starting from 1, so we default to 1 if not provided in the request.
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
if limit <= 0 {
|
|
limit = querytypes.DefaultLimit
|
|
}
|
|
|
|
node, err := clientCtx.GetNode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resTxs, err := node.TxSearch(context.Background(), query, false, &page, &limit, orderBy)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search for txs: %w", 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
|
|
}
|
|
|
|
return sdk.NewSearchTxsResult(uint64(resTxs.TotalCount), uint64(len(txs)), uint64(page), uint64(limit), txs), 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
|
|
}
|
|
|
|
resTx, err := node.Tx(context.Background(), hash, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resBlocks, err := getBlocksForTxResults(clientCtx, []*coretypes.ResultTx{resTx})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out, err := mkTxResult(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 []*coretypes.ResultTx, resBlocks map[int64]*coretypes.ResultBlock) ([]*sdk.TxResponse, error) {
|
|
var err error
|
|
out := make([]*sdk.TxResponse, len(resTxs))
|
|
for i := range resTxs {
|
|
out[i], err = mkTxResult(txConfig, resTxs[i], resBlocks[resTxs[i].Height])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func getBlocksForTxResults(clientCtx client.Context, resTxs []*coretypes.ResultTx) (map[int64]*coretypes.ResultBlock, error) {
|
|
node, err := clientCtx.GetNode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resBlocks := make(map[int64]*coretypes.ResultBlock)
|
|
|
|
for _, resTx := range resTxs {
|
|
resTx := resTx
|
|
|
|
if _, ok := resBlocks[resTx.Height]; !ok {
|
|
resBlock, err := node.Block(context.Background(), &resTx.Height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resBlocks[resTx.Height] = resBlock
|
|
}
|
|
}
|
|
|
|
return resBlocks, nil
|
|
}
|
|
|
|
func mkTxResult(txConfig client.TxConfig, resTx *coretypes.ResultTx, resBlock *coretypes.ResultBlock) (*sdk.TxResponse, error) {
|
|
txb, err := txConfig.TxDecoder()(resTx.Tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p, ok := txb.(*gogoTxWrapper)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected type, wanted gogoTxWrapper, got: %T", txb)
|
|
}
|
|
|
|
tx, err := p.AsTx()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
anyTx, err := codectypes.NewAnyWithValue(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sdk.NewResponseResultTx(resTx, anyTx, resBlock.Block.Time.Format(time.RFC3339)), nil
|
|
}
|