Skip to content

Commit 05a1887

Browse files
tbruyelleAlex Johnson
andauthored
feat: improve node tx cmd (ignite#2813)
* feat(cosmosclient): more contextualized errors This mitigates the problem 1) listed in ignite#2788. If the tendermint RPC client returns a JSON error, now it's prefixed properly with a label mentionning the node address. * fix: node q tx because of empty keyring backend * add test removed with TestNew * remove unecessary comments * add TODO about wrapper removal condition * Update ignite/cmd/node.go Co-authored-by: Alex Johnson <alex@shmeeload.xyz> * style: use pkg/errors Co-authored-by: Alex Johnson <alex@shmeeload.xyz>
1 parent 5461922 commit 05a1887

File tree

8 files changed

+207
-15
lines changed

8 files changed

+207
-15
lines changed

ignite/cmd/account.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func printAccounts(cmd *cobra.Command, accounts ...cosmosaccount.Account) error
7070

7171
func flagSetKeyringBackend() *flag.FlagSet {
7272
fs := flag.NewFlagSet("", flag.ContinueOnError)
73-
fs.String(flagKeyringBackend, "test", "Keyring backend to store your account keys")
73+
fs.String(flagKeyringBackend, string(cosmosaccount.KeyringTest), "Keyring backend to store your account keys")
7474
return fs
7575
}
7676

ignite/cmd/node.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ignitecmd
33
import (
44
"github.com/spf13/cobra"
55

6+
"github.com/ignite/cli/ignite/pkg/cosmosaccount"
67
"github.com/ignite/cli/ignite/pkg/cosmosclient"
78
"github.com/ignite/cli/ignite/pkg/xurl"
89
)
@@ -31,7 +32,7 @@ func newNodeCosmosClient(cmd *cobra.Command) (cosmosclient.Client, error) {
3132
var (
3233
home = getHome(cmd)
3334
prefix = getAddressPrefix(cmd)
34-
node = getRPC(cmd)
35+
node = getNode(cmd)
3536
keyringBackend = getKeyringBackend(cmd)
3637
keyringDir = getKeyringDir(cmd)
3738
gas = getGas(cmd)
@@ -40,6 +41,11 @@ func newNodeCosmosClient(cmd *cobra.Command) (cosmosclient.Client, error) {
4041
broadcastMode = getBroadcastMode(cmd)
4142
generateOnly = getGenerateOnly(cmd)
4243
)
44+
if keyringBackend == "" {
45+
// Makes cosmosclient usable for commands that doesn't expose the keyring
46+
// backend flag (cosmosclient.New returns an error if it's empty).
47+
keyringBackend = cosmosaccount.KeyringTest
48+
}
4349

4450
options := []cosmosclient.Option{
4551
cosmosclient.WithAddressPrefix(prefix),
@@ -64,7 +70,7 @@ func newNodeCosmosClient(cmd *cobra.Command) (cosmosclient.Client, error) {
6470
return cosmosclient.New(cmd.Context(), options...)
6571
}
6672

67-
func getRPC(cmd *cobra.Command) (rpc string) {
68-
rpc, _ = cmd.Flags().GetString(flagNode)
73+
func getNode(cmd *cobra.Command) (node string) {
74+
node, _ = cmd.Flags().GetString(flagNode)
6975
return
7076
}

ignite/cmd/node_query_tx.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/json"
66
"fmt"
77

8-
sdkclient "github.com/cosmos/cosmos-sdk/client"
98
"github.com/spf13/cobra"
109
)
1110

@@ -24,11 +23,12 @@ func nodeQueryTxHandler(cmd *cobra.Command, args []string) error {
2423
if err != nil {
2524
return err
2625
}
27-
rpc, err := sdkclient.NewClientFromNode(getRPC(cmd))
26+
client, err := newNodeCosmosClient(cmd)
2827
if err != nil {
2928
return err
3029
}
31-
resp, err := rpc.Tx(cmd.Context(), bz, false)
30+
31+
resp, err := client.RPC.Tx(cmd.Context(), bz, false)
3232
if err != nil {
3333
return err
3434
}

ignite/cmd/node_tx_bank_send.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func nodeTxBankSendHandler(cmd *cobra.Command, args []string) error {
7878
if getBroadcastMode(cmd) != flags.BroadcastBlock {
7979
session.Println("Transaction waiting to be included in a block.")
8080
session.Println("Run the following command to follow the transaction status:")
81-
session.Printf(" ignite node --node %s q tx %s\n", getRPC(cmd), resp.TxHash)
81+
session.Printf(" ignite node --node %s q tx %s\n", getNode(cmd), resp.TxHash)
8282
}
8383
return nil
8484
}

ignite/pkg/cosmosclient/bank.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func (c Client) BankBalances(ctx context.Context, address string, pagination *qu
2020

2121
resp, err := c.bankQueryClient.AllBalances(ctx, req)
2222
if err != nil {
23-
return nil, err
23+
return nil, rpcError(c.nodeAddress, err)
2424
}
2525
return resp.Balances, nil
2626
}

ignite/pkg/cosmosclient/cosmosclient.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ func New(ctx context.Context, options ...Option) (Client, error) {
273273
return Client{}, err
274274
}
275275
}
276+
// Wrap RPC client to have more contextualized errors
277+
c.RPC = rpcWrapper{
278+
Client: c.RPC,
279+
nodeAddress: c.nodeAddress,
280+
}
276281

277282
statusResp, err := c.RPC.Status(ctx)
278283
if err != nil {

ignite/pkg/cosmosclient/cosmosclient_test.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,14 +273,39 @@ func TestClientStatus(t *testing.T) {
273273
NodeInfo: p2p.DefaultNodeInfo{Network: "mychain"},
274274
}
275275
)
276-
c := newClient(t, func(s suite) {
277-
s.rpcClient.EXPECT().Status(ctx).Return(expectedStatus, nil).Once()
278-
})
276+
tests := []struct {
277+
name string
278+
expectedError string
279+
setup func(suite)
280+
}{
281+
{
282+
name: "ok",
283+
setup: func(s suite) {
284+
s.rpcClient.EXPECT().Status(ctx).Return(expectedStatus, nil).Once()
285+
},
286+
},
287+
{
288+
name: "fail",
289+
expectedError: "error while requesting node 'http://localhost:26657': oups",
290+
setup: func(s suite) {
291+
s.rpcClient.EXPECT().Status(ctx).Return(expectedStatus, errors.New("oups")).Once()
292+
},
293+
},
294+
}
295+
for _, tt := range tests {
296+
t.Run(tt.name, func(t *testing.T) {
297+
c := newClient(t, tt.setup)
279298

280-
status, err := c.Status(ctx)
299+
status, err := c.Status(ctx)
281300

282-
require.NoError(t, err)
283-
assert.Equal(t, expectedStatus, status)
301+
if tt.expectedError != "" {
302+
require.EqualError(t, err, tt.expectedError)
303+
return
304+
}
305+
require.NoError(t, err)
306+
assert.Equal(t, expectedStatus, status)
307+
})
308+
}
284309
}
285310

286311
func TestClientCreateTx(t *testing.T) {

ignite/pkg/cosmosclient/rpc.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package cosmosclient
2+
3+
import (
4+
"context"
5+
6+
"github.com/pkg/errors"
7+
"github.com/tendermint/tendermint/libs/bytes"
8+
"github.com/tendermint/tendermint/rpc/client"
9+
rpcclient "github.com/tendermint/tendermint/rpc/client"
10+
ctypes "github.com/tendermint/tendermint/rpc/core/types"
11+
"github.com/tendermint/tendermint/types"
12+
)
13+
14+
// rpcWrapper is a rpclient.Client but with more contextualized errors.
15+
// Useful because the original implementation may return JSON errors when the
16+
// requested node is busy, which is confusing for the user. With rpcWrapper,
17+
// the error is prefixed with 'error while requesting node xxx: JSON error'.
18+
// TODO(tb): we may remove this wrapper once https://github.com/tendermint/tendermint/issues/9312 is fixed.
19+
type rpcWrapper struct {
20+
rpcclient.Client
21+
nodeAddress string
22+
}
23+
24+
func rpcError(node string, err error) error {
25+
return errors.Wrapf(err, "error while requesting node '%s'", node)
26+
}
27+
28+
func (rpc rpcWrapper) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) {
29+
res, err := rpc.Client.ABCIInfo(ctx)
30+
return res, rpcError(rpc.nodeAddress, err)
31+
}
32+
33+
func (rpc rpcWrapper) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
34+
res, err := rpc.Client.ABCIQuery(ctx, path, data)
35+
return res, rpcError(rpc.nodeAddress, err)
36+
}
37+
38+
func (rpc rpcWrapper) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
39+
res, err := rpc.Client.ABCIQueryWithOptions(ctx, path, data, opts)
40+
return res, rpcError(rpc.nodeAddress, err)
41+
}
42+
43+
func (rpc rpcWrapper) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
44+
res, err := rpc.Client.BroadcastTxCommit(ctx, tx)
45+
return res, rpcError(rpc.nodeAddress, err)
46+
}
47+
48+
func (rpc rpcWrapper) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
49+
res, err := rpc.Client.BroadcastTxAsync(ctx, tx)
50+
return res, rpcError(rpc.nodeAddress, err)
51+
}
52+
53+
func (rpc rpcWrapper) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
54+
res, err := rpc.Client.BroadcastTxSync(ctx, tx)
55+
return res, rpcError(rpc.nodeAddress, err)
56+
}
57+
58+
func (rpc rpcWrapper) GenesisChunked(ctx context.Context, n uint) (*ctypes.ResultGenesisChunk, error) {
59+
res, err := rpc.Client.GenesisChunked(ctx, n)
60+
return res, rpcError(rpc.nodeAddress, err)
61+
}
62+
63+
func (rpc rpcWrapper) BlockchainInfo(ctx context.Context, minHeight int64, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
64+
res, err := rpc.Client.BlockchainInfo(ctx, minHeight, maxHeight)
65+
return res, rpcError(rpc.nodeAddress, err)
66+
}
67+
68+
func (rpc rpcWrapper) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) {
69+
res, err := rpc.Client.NetInfo(ctx)
70+
return res, rpcError(rpc.nodeAddress, err)
71+
}
72+
73+
func (rpc rpcWrapper) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) {
74+
res, err := rpc.Client.DumpConsensusState(ctx)
75+
return res, rpcError(rpc.nodeAddress, err)
76+
}
77+
78+
func (rpc rpcWrapper) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) {
79+
res, err := rpc.Client.ConsensusState(ctx)
80+
return res, rpcError(rpc.nodeAddress, err)
81+
}
82+
83+
func (rpc rpcWrapper) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) {
84+
res, err := rpc.Client.ConsensusParams(ctx, height)
85+
return res, rpcError(rpc.nodeAddress, err)
86+
}
87+
88+
func (rpc rpcWrapper) Health(ctx context.Context) (*ctypes.ResultHealth, error) {
89+
res, err := rpc.Client.Health(ctx)
90+
return res, rpcError(rpc.nodeAddress, err)
91+
}
92+
93+
func (rpc rpcWrapper) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
94+
res, err := rpc.Client.Block(ctx, height)
95+
return res, rpcError(rpc.nodeAddress, err)
96+
}
97+
98+
func (rpc rpcWrapper) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
99+
res, err := rpc.Client.BlockByHash(ctx, hash)
100+
return res, rpcError(rpc.nodeAddress, err)
101+
}
102+
103+
func (rpc rpcWrapper) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
104+
res, err := rpc.Client.BlockResults(ctx, height)
105+
return res, rpcError(rpc.nodeAddress, err)
106+
}
107+
108+
func (rpc rpcWrapper) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
109+
res, err := rpc.Client.Commit(ctx, height)
110+
return res, rpcError(rpc.nodeAddress, err)
111+
}
112+
113+
func (rpc rpcWrapper) Validators(ctx context.Context, height *int64, page *int, perPage *int) (*ctypes.ResultValidators, error) {
114+
res, err := rpc.Client.Validators(ctx, height, page, perPage)
115+
return res, rpcError(rpc.nodeAddress, err)
116+
}
117+
118+
func (rpc rpcWrapper) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
119+
res, err := rpc.Client.Tx(ctx, hash, prove)
120+
return res, rpcError(rpc.nodeAddress, err)
121+
}
122+
123+
func (rpc rpcWrapper) TxSearch(ctx context.Context, query string, prove bool, page *int, perPage *int, orderBy string) (*ctypes.ResultTxSearch, error) {
124+
res, err := rpc.Client.TxSearch(ctx, query, prove, page, perPage, orderBy)
125+
return res, rpcError(rpc.nodeAddress, err)
126+
}
127+
128+
func (rpc rpcWrapper) BlockSearch(ctx context.Context, query string, page *int, perPage *int, orderBy string) (*ctypes.ResultBlockSearch, error) {
129+
res, err := rpc.Client.BlockSearch(ctx, query, page, perPage, orderBy)
130+
return res, rpcError(rpc.nodeAddress, err)
131+
}
132+
133+
func (rpc rpcWrapper) Status(ctx context.Context) (*ctypes.ResultStatus, error) {
134+
res, err := rpc.Client.Status(ctx)
135+
return res, rpcError(rpc.nodeAddress, err)
136+
}
137+
138+
func (rpc rpcWrapper) BroadcastEvidence(ctx context.Context, e types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
139+
res, err := rpc.Client.BroadcastEvidence(ctx, e)
140+
return res, rpcError(rpc.nodeAddress, err)
141+
}
142+
143+
func (rpc rpcWrapper) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) {
144+
res, err := rpc.Client.UnconfirmedTxs(ctx, limit)
145+
return res, rpcError(rpc.nodeAddress, err)
146+
}
147+
148+
func (rpc rpcWrapper) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) {
149+
res, err := rpc.Client.NumUnconfirmedTxs(ctx)
150+
return res, rpcError(rpc.nodeAddress, err)
151+
}
152+
153+
func (rpc rpcWrapper) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
154+
res, err := rpc.Client.CheckTx(ctx, tx)
155+
return res, rpcError(rpc.nodeAddress, err)
156+
}

0 commit comments

Comments
 (0)