@@ -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,30 @@ 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.
88
- func QueryEventForTxCmd () * cobra.Command {
92
+ // WaitTx returns a CLI command that waits for a transaction with the given hash to be included in a block .
93
+ func WaitTxCmd () * cobra.Command {
89
94
cmd := & cobra.Command {
90
- Use : "event-query-tx-for [hash]" ,
91
- Short : "Query for a transaction by hash " ,
95
+ Use : "wait-tx [hash]" ,
96
+ Short : "Wait for a transaction to be included in a block " ,
92
97
Long : `Subscribes to a CometBFT WebSocket connection and waits for a transaction event with the given hash.` ,
93
- Args : cobra .ExactArgs (1 ),
98
+ Example : fmt .Sprintf (`By providing the transaction hash:
99
+ $ %[1]sd q wait-tx [hash]
100
+
101
+ Or, by piping a "tx" command:
102
+ $ %[1]sd tx [flags] | %[1]sd q wait-tx
103
+ ` , version .AppName ),
104
+ Args : cobra .MaximumNArgs (1 ),
94
105
RunE : func (cmd * cobra.Command , args []string ) error {
95
106
clientCtx , err := client .GetClientTxContext (cmd )
96
107
if err != nil {
97
108
return err
98
109
}
110
+
111
+ timeout , err := cmd .Flags ().GetDuration (TimeoutFlag )
112
+ if err != nil {
113
+ return err
114
+ }
115
+
99
116
c , err := rpchttp .New (clientCtx .NodeURI , "/websocket" )
100
117
if err != nil {
101
118
return err
@@ -105,18 +122,54 @@ func QueryEventForTxCmd() *cobra.Command {
105
122
}
106
123
defer c .Stop () //nolint:errcheck // ignore stop error
107
124
108
- ctx , cancel := context .WithTimeout (context .Background (), time . Second * 15 )
125
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
109
126
defer cancel ()
110
127
111
- hash := args [0 ]
112
- query := fmt .Sprintf ("%s='%s' AND %s='%s'" , tmtypes .EventTypeKey , tmtypes .EventTx , tmtypes .TxHashKey , hash )
128
+ var hash []byte
129
+ if len (args ) == 0 {
130
+ // read hash from stdin
131
+ in , err := io .ReadAll (cmd .InOrStdin ())
132
+ if err != nil {
133
+ return err
134
+ }
135
+ hashByt , err := parseHashFromInput (in )
136
+ if err != nil {
137
+ return err
138
+ }
139
+
140
+ hash = hashByt
141
+ } else {
142
+ // read hash from args
143
+ hashByt , err := hex .DecodeString (args [0 ])
144
+ if err != nil {
145
+ return err
146
+ }
147
+
148
+ hash = hashByt
149
+ }
150
+
151
+ // subscribe to websocket events
152
+ query := fmt .Sprintf ("%s='%s' AND %s='%X'" , tmtypes .EventTypeKey , tmtypes .EventTx , tmtypes .TxHashKey , hash )
113
153
const subscriber = "subscriber"
114
154
eventCh , err := c .Subscribe (ctx , subscriber , query )
115
155
if err != nil {
116
156
return fmt .Errorf ("failed to subscribe to tx: %w" , err )
117
157
}
118
158
defer c .UnsubscribeAll (context .Background (), subscriber ) //nolint:errcheck // ignore unsubscribe error
119
159
160
+ // return immediately if tx is already included in a block
161
+ res , err := c .Tx (ctx , hash , false )
162
+ if err == nil {
163
+ // tx already included in a block
164
+ res := & coretypes.ResultBroadcastTxCommit {
165
+ TxResult : res .TxResult ,
166
+ Hash : res .Hash ,
167
+ Height : res .Height ,
168
+ }
169
+ return clientCtx .PrintProto (newResponseFormatBroadcastTxCommit (res ))
170
+ }
171
+
172
+ // tx not yet included in a block, wait for event on websocket
120
173
select {
121
174
case evt := <- eventCh :
122
175
if txe , ok := evt .Data .(tmtypes.EventDataTx ); ok {
@@ -128,13 +181,32 @@ func QueryEventForTxCmd() *cobra.Command {
128
181
return clientCtx .PrintProto (newResponseFormatBroadcastTxCommit (res ))
129
182
}
130
183
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" )
184
+ return errors .ErrLogic .Wrapf ("timed out waiting for transaction %X to be included in a block" , hash )
132
185
}
133
186
return nil
134
187
},
135
188
}
136
189
137
- flags .AddTxFlagsToCmd (cmd )
190
+ cmd .Flags ().Duration (TimeoutFlag , 15 * time .Second , "The maximum time to wait for the transaction to be included in a block" )
191
+ flags .AddQueryFlagsToCmd (cmd )
138
192
139
193
return cmd
140
194
}
195
+
196
+ func parseHashFromInput (in []byte ) ([]byte , error ) {
197
+ var resultTx coretypes.ResultTx
198
+ if err := json .Unmarshal (in , & resultTx ); err == nil {
199
+ // input was JSON, return the hash
200
+ return resultTx .Hash , nil
201
+ }
202
+
203
+ // try to parse the hash from the output of a tx command
204
+ lines := strings .Split (string (in ), "\n " )
205
+ for _ , line := range lines {
206
+ if strings .HasPrefix (line , "txhash:" ) {
207
+ hash := strings .TrimSpace (line [len ("txhash:" ):])
208
+ return hex .DecodeString (hash )
209
+ }
210
+ }
211
+ return nil , fmt .Errorf ("txhash not found" )
212
+ }
0 commit comments