@@ -3,7 +3,9 @@ package rpc
3
3
import (
4
4
"context"
5
5
"encoding/hex"
6
+ "encoding/json"
6
7
"fmt"
8
+ "io"
7
9
"strings"
8
10
"time"
9
11
@@ -16,8 +18,11 @@ import (
16
18
"github.com/cosmos/cosmos-sdk/client/flags"
17
19
sdk "github.com/cosmos/cosmos-sdk/types"
18
20
"github.com/cosmos/cosmos-sdk/types/errors"
21
+ "github.com/cosmos/cosmos-sdk/version"
19
22
)
20
23
24
+ const TimeoutFlag = "timeout"
25
+
21
26
func newTxResponseCheckTx (res * coretypes.ResultBroadcastTxCommit ) * sdk.TxResponse {
22
27
if res == nil {
23
28
return nil
@@ -84,18 +89,36 @@ func newResponseFormatBroadcastTxCommit(res *coretypes.ResultBroadcastTxCommit)
84
89
return newTxResponseDeliverTx (res )
85
90
}
86
91
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 .
88
93
func 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 {
89
99
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 ),
94
111
RunE : func (cmd * cobra.Command , args []string ) error {
95
112
clientCtx , err := client .GetClientTxContext (cmd )
96
113
if err != nil {
97
114
return err
98
115
}
116
+
117
+ timeout , err := cmd .Flags ().GetDuration (TimeoutFlag )
118
+ if err != nil {
119
+ return err
120
+ }
121
+
99
122
c , err := rpchttp .New (clientCtx .NodeURI , "/websocket" )
100
123
if err != nil {
101
124
return err
@@ -105,18 +128,54 @@ func QueryEventForTxCmd() *cobra.Command {
105
128
}
106
129
defer c .Stop () //nolint:errcheck // ignore stop error
107
130
108
- ctx , cancel := context .WithTimeout (context .Background (), time . Second * 15 )
131
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
109
132
defer cancel ()
110
133
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 )
113
159
const subscriber = "subscriber"
114
160
eventCh , err := c .Subscribe (ctx , subscriber , query )
115
161
if err != nil {
116
162
return fmt .Errorf ("failed to subscribe to tx: %w" , err )
117
163
}
118
164
defer c .UnsubscribeAll (context .Background (), subscriber ) //nolint:errcheck // ignore unsubscribe error
119
165
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
+ DeliverTx : 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
120
179
select {
121
180
case evt := <- eventCh :
122
181
if txe , ok := evt .Data .(tmtypes.EventDataTx ); ok {
@@ -128,13 +187,32 @@ func QueryEventForTxCmd() *cobra.Command {
128
187
return clientCtx .PrintProto (newResponseFormatBroadcastTxCommit (res ))
129
188
}
130
189
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 )
132
191
}
133
192
return nil
134
193
},
135
194
}
136
195
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 )
138
198
139
199
return cmd
140
200
}
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