Skip to content

Commit 2251c49

Browse files
Pitasimmsqe
authored andcommitted
feat(client): replace event-query-tx-for with wait-tx (cosmos#19870)
1 parent 9bd1e03 commit 2251c49

File tree

2 files changed

+92
-10
lines changed

2 files changed

+92
-10
lines changed

CHANGELOG.md

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

4040
* (x/staking) Fix a possible bypass of delagator slashing: [GHSA-86h5-xcpx-cfqc](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-86h5-xcpx-cfqc)
4141

42+
### Features
43+
44+
* (client) [#19870](https://github.com/cosmos/cosmos-sdk/pull/19870) Add new query command `wait-tx`. Alias `event-query-tx-for` to `wait-tx` for backward compatibility.
45+
4246
## [v0.47.9](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.9) - 2024-02-19
4347

4448
### Bug Fixes

client/rpc/tx.go

+88-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package rpc
33
import (
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+
2126
func 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.
8893
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 {
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+
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
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

Comments
 (0)