Skip to content

Commit 32b23f5

Browse files
mergify[bot]mmsqejulienrbrttac0turtle
authored
feat: add event-query-tx-for cmd to subscribe and wait for transaction (backport #17274) (#17435)
Co-authored-by: mmsqe <[email protected]> Co-authored-by: Julien Robert <[email protected]> Co-authored-by: marbar3778 <[email protected]>
1 parent 612f0fe commit 32b23f5

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
3939

4040
### Features
4141

42+
* (client/rpc) [#17274](https://github.com/cosmos/cosmos-sdk/pull/17274) Add `QueryEventForTxCmd` cmd to subscribe and wait event for transaction by hash.
4243
* (keyring) [#17424](https://github.com/cosmos/cosmos-sdk/pull/17424) Allows to import private keys encoded in hex.
4344

4445
### Improvements

client/rpc/tx.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package rpc
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
11+
coretypes "github.com/cometbft/cometbft/rpc/core/types"
12+
tmtypes "github.com/cometbft/cometbft/types"
13+
"github.com/spf13/cobra"
14+
15+
"github.com/cosmos/cosmos-sdk/client"
16+
"github.com/cosmos/cosmos-sdk/client/flags"
17+
sdk "github.com/cosmos/cosmos-sdk/types"
18+
"github.com/cosmos/cosmos-sdk/types/errors"
19+
)
20+
21+
func newTxResponseCheckTx(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
22+
if res == nil {
23+
return nil
24+
}
25+
26+
var txHash string
27+
if res.Hash != nil {
28+
txHash = res.Hash.String()
29+
}
30+
31+
parsedLogs, _ := sdk.ParseABCILogs(res.CheckTx.Log)
32+
33+
return &sdk.TxResponse{
34+
Height: res.Height,
35+
TxHash: txHash,
36+
Codespace: res.CheckTx.Codespace,
37+
Code: res.CheckTx.Code,
38+
Data: strings.ToUpper(hex.EncodeToString(res.CheckTx.Data)),
39+
RawLog: res.CheckTx.Log,
40+
Logs: parsedLogs,
41+
Info: res.CheckTx.Info,
42+
GasWanted: res.CheckTx.GasWanted,
43+
GasUsed: res.CheckTx.GasUsed,
44+
Events: res.CheckTx.Events,
45+
}
46+
}
47+
48+
func newTxResponseDeliverTx(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
49+
if res == nil {
50+
return nil
51+
}
52+
53+
var txHash string
54+
if res.Hash != nil {
55+
txHash = res.Hash.String()
56+
}
57+
58+
parsedLogs, _ := sdk.ParseABCILogs(res.DeliverTx.Log)
59+
60+
return &sdk.TxResponse{
61+
Height: res.Height,
62+
TxHash: txHash,
63+
Codespace: res.DeliverTx.Codespace,
64+
Code: res.DeliverTx.Code,
65+
Data: strings.ToUpper(hex.EncodeToString(res.DeliverTx.Data)),
66+
RawLog: res.DeliverTx.Log,
67+
Logs: parsedLogs,
68+
Info: res.DeliverTx.Info,
69+
GasWanted: res.DeliverTx.GasWanted,
70+
GasUsed: res.DeliverTx.GasUsed,
71+
Events: res.DeliverTx.Events,
72+
}
73+
}
74+
75+
func newResponseFormatBroadcastTxCommit(res *coretypes.ResultBroadcastTxCommit) *sdk.TxResponse {
76+
if res == nil {
77+
return nil
78+
}
79+
80+
if !res.CheckTx.IsOK() {
81+
return newTxResponseCheckTx(res)
82+
}
83+
84+
return newTxResponseDeliverTx(res)
85+
}
86+
87+
// QueryEventForTxCmd returns a CLI command that subscribes to a WebSocket connection and waits for a transaction event with the given hash.
88+
func QueryEventForTxCmd() *cobra.Command {
89+
cmd := &cobra.Command{
90+
Use: "event-query-tx-for [hash]",
91+
Short: "Query for a transaction by hash",
92+
Long: `Subscribes to a CometBFT WebSocket connection and waits for a transaction event with the given hash.`,
93+
Args: cobra.ExactArgs(1),
94+
RunE: func(cmd *cobra.Command, args []string) error {
95+
clientCtx, err := client.GetClientTxContext(cmd)
96+
if err != nil {
97+
return err
98+
}
99+
c, err := rpchttp.New(clientCtx.NodeURI, "/websocket")
100+
if err != nil {
101+
return err
102+
}
103+
if err := c.Start(); err != nil {
104+
return err
105+
}
106+
defer c.Stop() //nolint:errcheck // ignore stop error
107+
108+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
109+
defer cancel()
110+
111+
hash := args[0]
112+
query := fmt.Sprintf("%s='%s' AND %s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, tmtypes.TxHashKey, hash)
113+
const subscriber = "subscriber"
114+
eventCh, err := c.Subscribe(ctx, subscriber, query)
115+
if err != nil {
116+
return fmt.Errorf("failed to subscribe to tx: %w", err)
117+
}
118+
defer c.UnsubscribeAll(context.Background(), subscriber) //nolint:errcheck // ignore unsubscribe error
119+
120+
select {
121+
case evt := <-eventCh:
122+
if txe, ok := evt.Data.(tmtypes.EventDataTx); ok {
123+
res := &coretypes.ResultBroadcastTxCommit{
124+
DeliverTx: txe.Result,
125+
Hash: tmtypes.Tx(txe.Tx).Hash(),
126+
Height: txe.Height,
127+
}
128+
return clientCtx.PrintProto(newResponseFormatBroadcastTxCommit(res))
129+
}
130+
case <-ctx.Done():
131+
return errors.ErrLogic.Wrapf("timed out waiting for event, the transaction could have already been included or wasn't yet included")
132+
}
133+
return nil
134+
},
135+
}
136+
137+
flags.AddTxFlagsToCmd(cmd)
138+
139+
return cmd
140+
}

simapp/simd/cmd/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ func queryCommand() *cobra.Command {
214214
}
215215

216216
cmd.AddCommand(
217+
rpc.QueryEventForTxCmd(),
217218
authcmd.GetAccountCmd(),
218219
rpc.ValidatorCommand(),
219220
rpc.BlockCommand(),

0 commit comments

Comments
 (0)