Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event-query-tx-for cmd to subscribe and wait for transaction #17274

Merged
merged 14 commits into from
Aug 17, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features

* (x/bank) [#16795](https://github.com/cosmos/cosmos-sdk/pull/16852) Add `DenomMetadataByQueryString` query in bank module to support metadata query by query string.
* (client/rpc) [#17274](https://github.com/cosmos/cosmos-sdk/pull/17274) Add `QueryEventForTxCmd` cmd to subscribe and wait event for transaction by hash.
* (baseapp) [#16239](https://github.com/cosmos/cosmos-sdk/pull/16239) Add Gas Limits to allow node operators to resource bound queries.
* (baseapp) [#17393](https://github.com/cosmos/cosmos-sdk/pull/17394) Check BlockID Flag on Votes in `ValidateVoteExtensions`

Expand Down
140 changes: 140 additions & 0 deletions client/rpc/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package rpc

import (
"context"
"encoding/hex"
"fmt"
"strings"
"time"

rpchttp "github.com/cometbft/cometbft/rpc/client/http"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
tmtypes "github.com/cometbft/cometbft/types"
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
)

func newTxResponseCheckTx(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
if res == nil {
return nil
}

var txHash string
if res.Hash != nil {
txHash = res.Hash.String()
}

parsedLogs, _ := sdk.ParseABCILogs(res.CheckTx.Log)

return &sdk.TxResponse{
Height: res.Height,
TxHash: txHash,
Codespace: res.CheckTx.Codespace,
Code: res.CheckTx.Code,
Data: strings.ToUpper(hex.EncodeToString(res.CheckTx.Data)),
RawLog: res.CheckTx.Log,
Logs: parsedLogs,
Info: res.CheckTx.Info,
GasWanted: res.CheckTx.GasWanted,
GasUsed: res.CheckTx.GasUsed,
Events: res.CheckTx.Events,
}
}

func newTxResponseDeliverTx(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
if res == nil {
return nil
}

var txHash string
if res.Hash != nil {
txHash = res.Hash.String()
}

parsedLogs, _ := sdk.ParseABCILogs(res.TxResult.Log)

return &sdk.TxResponse{
Height: res.Height,
TxHash: txHash,
Codespace: res.TxResult.Codespace,
Code: res.TxResult.Code,
Data: strings.ToUpper(hex.EncodeToString(res.TxResult.Data)),
RawLog: res.TxResult.Log,
Logs: parsedLogs,
Info: res.TxResult.Info,
GasWanted: res.TxResult.GasWanted,
GasUsed: res.TxResult.GasUsed,
Events: res.TxResult.Events,
}
}

func newResponseFormatBroadcastTxCommit(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
if res == nil {
return nil
}

if !res.CheckTx.IsOK() {
return newTxResponseCheckTx(res)
}

return newTxResponseDeliverTx(res)
}

// QueryEventForTxCmd returns a CLI command that subscribes to a WebSocket connection and waits for a transaction event with the given hash.
func QueryEventForTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "event-query-tx-for [hash]",
Short: "Query for a transaction by hash",
Long: `Subscribes to a CometBFT WebSocket connection and waits for a transaction event with the given hash.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
c, err := rpchttp.New(clientCtx.NodeURI, "/websocket")
if err != nil {
return err
}
if err := c.Start(); err != nil {
return err
}
defer c.Stop() //nolint:errcheck // ignore stop error

ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()

hash := args[0]
query := fmt.Sprintf("%s='%s' AND %s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, tmtypes.TxHashKey, hash)
const subscriber = "subscriber"
eventCh, err := c.Subscribe(ctx, subscriber, query)
if err != nil {
return fmt.Errorf("failed to subscribe to tx: %w", err)
}
defer c.UnsubscribeAll(context.Background(), subscriber) //nolint:errcheck // ignore unsubscribe error

select {
case evt := <-eventCh:
if txe, ok := evt.Data.(tmtypes.EventDataTx); ok {
res := &coretypes.ResultBroadcastTxCommit{
TxResult: txe.Result,
Hash: tmtypes.Tx(txe.Tx).Hash(),
Height: txe.Height,
}
return clientCtx.PrintProto(newResponseFormatBroadcastTxCommit(res))
}
case <-ctx.Done():
return errors.ErrLogic.Wrapf("timed out waiting for event, the transaction could have already been included or wasn't yet included")
}
return nil
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
2 changes: 2 additions & 0 deletions simapp/simd/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/pruning"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/snapshot"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
Expand Down Expand Up @@ -158,6 +159,7 @@ func queryCommand() *cobra.Command {
}

cmd.AddCommand(
rpc.QueryEventForTxCmd(),
server.QueryBlockCmd(),
authcmd.QueryTxsByEventsCmd(),
server.QueryBlocksCmd(),
Expand Down