@@ -3,7 +3,9 @@ package rpc
33import (
44 "context"
55 "encoding/hex"
6+ "encoding/json"
67 "fmt"
8+ "io"
79 "strings"
810 "time"
911
@@ -16,8 +18,11 @@ import (
1618 "github.com/cosmos/cosmos-sdk/client/flags"
1719 sdk "github.com/cosmos/cosmos-sdk/types"
1820 "github.com/cosmos/cosmos-sdk/types/errors"
21+ "github.com/cosmos/cosmos-sdk/version"
1922)
2023
24+ const TimeoutFlag = "timeout"
25+
2126func newTxResponseCheckTx (res * coretypes.ResultBroadcastTxCommit ) * sdk.TxResponse {
2227 if res == nil {
2328 return nil
@@ -84,18 +89,36 @@ func newResponseFormatBroadcastTxCommit(res *coretypes.ResultBroadcastTxCommit)
8489 return newTxResponseDeliverTx (res )
8590}
8691
87- // QueryEventForTxCmd returns a CLI command that subscribes to a WebSocket connection and waits for a transaction event with the given hash .
92+ // QueryEventForTxCmd is an alias for WaitTxCmd, kept for backwards compatibility .
8893func QueryEventForTxCmd () * cobra.Command {
94+ return WaitTxCmd ()
95+ }
96+
97+ // WaitTx returns a CLI command that waits for a transaction with the given hash to be included in a block.
98+ func WaitTxCmd () * cobra.Command {
8999 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 ),
100+ Use : "wait-tx [hash]" ,
101+ Aliases : []string {"event-query-tx-for" },
102+ Short : "Wait for a transaction to be included in a block" ,
103+ Long : `Subscribes to a CometBFT WebSocket connection and waits for a transaction event with the given hash.` ,
104+ Example : fmt .Sprintf (`By providing the transaction hash:
105+ $ %[1]sd q wait-tx [hash]
106+
107+ Or, by piping a "tx" command:
108+ $ %[1]sd tx [flags] | %[1]sd q wait-tx
109+ ` , version .AppName ),
110+ Args : cobra .MaximumNArgs (1 ),
94111 RunE : func (cmd * cobra.Command , args []string ) error {
95112 clientCtx , err := client .GetClientTxContext (cmd )
96113 if err != nil {
97114 return err
98115 }
116+
117+ timeout , err := cmd .Flags ().GetDuration (TimeoutFlag )
118+ if err != nil {
119+ return err
120+ }
121+
99122 c , err := rpchttp .New (clientCtx .NodeURI , "/websocket" )
100123 if err != nil {
101124 return err
@@ -105,18 +128,54 @@ func QueryEventForTxCmd() *cobra.Command {
105128 }
106129 defer c .Stop () //nolint:errcheck // ignore stop error
107130
108- ctx , cancel := context .WithTimeout (context .Background (), time . Second * 15 )
131+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
109132 defer cancel ()
110133
111- hash := args [0 ]
112- query := fmt .Sprintf ("%s='%s' AND %s='%s'" , tmtypes .EventTypeKey , tmtypes .EventTx , tmtypes .TxHashKey , hash )
134+ var hash []byte
135+ if len (args ) == 0 {
136+ // read hash from stdin
137+ in , err := io .ReadAll (cmd .InOrStdin ())
138+ if err != nil {
139+ return err
140+ }
141+ hashByt , err := parseHashFromInput (in )
142+ if err != nil {
143+ return err
144+ }
145+
146+ hash = hashByt
147+ } else {
148+ // read hash from args
149+ hashByt , err := hex .DecodeString (args [0 ])
150+ if err != nil {
151+ return err
152+ }
153+
154+ hash = hashByt
155+ }
156+
157+ // subscribe to websocket events
158+ query := fmt .Sprintf ("%s='%s' AND %s='%X'" , tmtypes .EventTypeKey , tmtypes .EventTx , tmtypes .TxHashKey , hash )
113159 const subscriber = "subscriber"
114160 eventCh , err := c .Subscribe (ctx , subscriber , query )
115161 if err != nil {
116162 return fmt .Errorf ("failed to subscribe to tx: %w" , err )
117163 }
118164 defer c .UnsubscribeAll (context .Background (), subscriber ) //nolint:errcheck // ignore unsubscribe error
119165
166+ // return immediately if tx is already included in a block
167+ res , err := c .Tx (ctx , hash , false )
168+ if err == nil {
169+ // tx already included in a block
170+ res := & coretypes.ResultBroadcastTxCommit {
171+ TxResult : res .TxResult ,
172+ Hash : res .Hash ,
173+ Height : res .Height ,
174+ }
175+ return clientCtx .PrintProto (newResponseFormatBroadcastTxCommit (res ))
176+ }
177+
178+ // tx not yet included in a block, wait for event on websocket
120179 select {
121180 case evt := <- eventCh :
122181 if txe , ok := evt .Data .(tmtypes.EventDataTx ); ok {
@@ -128,13 +187,32 @@ func QueryEventForTxCmd() *cobra.Command {
128187 return clientCtx .PrintProto (newResponseFormatBroadcastTxCommit (res ))
129188 }
130189 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" )
190+ return errors .ErrLogic .Wrapf ("timed out waiting for transaction %X to be included in a block" , hash )
132191 }
133192 return nil
134193 },
135194 }
136195
137- flags .AddTxFlagsToCmd (cmd )
196+ cmd .Flags ().Duration (TimeoutFlag , 15 * time .Second , "The maximum time to wait for the transaction to be included in a block" )
197+ flags .AddQueryFlagsToCmd (cmd )
138198
139199 return cmd
140200}
201+
202+ func parseHashFromInput (in []byte ) ([]byte , error ) {
203+ var resultTx coretypes.ResultTx
204+ if err := json .Unmarshal (in , & resultTx ); err == nil {
205+ // input was JSON, return the hash
206+ return resultTx .Hash , nil
207+ }
208+
209+ // try to parse the hash from the output of a tx command
210+ lines := strings .Split (string (in ), "\n " )
211+ for _ , line := range lines {
212+ if strings .HasPrefix (line , "txhash:" ) {
213+ hash := strings .TrimSpace (line [len ("txhash:" ):])
214+ return hex .DecodeString (hash )
215+ }
216+ }
217+ return nil , fmt .Errorf ("txhash not found" )
218+ }
0 commit comments