diff --git a/ignite/cmd/node.go b/ignite/cmd/node.go index e3e5c3aaac..c928e733af 100644 --- a/ignite/cmd/node.go +++ b/ignite/cmd/node.go @@ -36,6 +36,7 @@ func newNodeCosmosClient(cmd *cobra.Command) (cosmosclient.Client, error) { gas = getGas(cmd) gasPrices = getGasPrices(cmd) fees = getFees(cmd) + broadcastMode = getBroadcastMode(cmd) ) options := []cosmosclient.Option{ @@ -44,6 +45,7 @@ func newNodeCosmosClient(cmd *cobra.Command) (cosmosclient.Client, error) { cosmosclient.WithKeyringBackend(keyringBackend), cosmosclient.WithKeyringDir(keyringDir), cosmosclient.WithNodeAddress(xurl.HTTPEnsurePort(node)), + cosmosclient.WithBroadcastMode(broadcastMode), } if gas != "" { diff --git a/ignite/cmd/node_query.go b/ignite/cmd/node_query.go index c5295e90da..0acdd4499f 100644 --- a/ignite/cmd/node_query.go +++ b/ignite/cmd/node_query.go @@ -26,6 +26,7 @@ func NewNodeQuery() *cobra.Command { } c.AddCommand(NewNodeQueryBank()) + c.AddCommand(NewNodeQueryTx()) return c } diff --git a/ignite/cmd/node_query_tx.go b/ignite/cmd/node_query_tx.go new file mode 100644 index 0000000000..39d84ee0b7 --- /dev/null +++ b/ignite/cmd/node_query_tx.go @@ -0,0 +1,41 @@ +package ignitecmd + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + sdkclient "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +func NewNodeQueryTx() *cobra.Command { + c := &cobra.Command{ + Use: "tx [hash]", + Short: "Query for transaction by hash", + RunE: nodeQueryTxHandler, + Args: cobra.ExactArgs(1), + } + return c +} + +func nodeQueryTxHandler(cmd *cobra.Command, args []string) error { + bz, err := hex.DecodeString(args[0]) + if err != nil { + return err + } + rpc, err := sdkclient.NewClientFromNode(getRPC(cmd)) + if err != nil { + return err + } + resp, err := rpc.Tx(cmd.Context(), bz, false) + if err != nil { + return err + } + bz, err = json.MarshalIndent(resp, "", " ") + if err != nil { + return err + } + fmt.Println(string(bz)) + return nil +} diff --git a/ignite/cmd/node_tx.go b/ignite/cmd/node_tx.go index ad3e332600..006bec27d2 100644 --- a/ignite/cmd/node_tx.go +++ b/ignite/cmd/node_tx.go @@ -3,6 +3,7 @@ package ignitecmd import ( "fmt" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) @@ -10,10 +11,11 @@ import ( const ( flagGenerateOnly = "generate-only" - gasFlagAuto = "auto" - flagGasPrices = "gas-prices" - flagGas = "gas" - flagFees = "fees" + gasFlagAuto = "auto" + flagGasPrices = "gas-prices" + flagGas = "gas" + flagFees = "fees" + flagBroadcastMode = "broadcast-mode" ) func NewNodeTx() *cobra.Command { @@ -21,6 +23,14 @@ func NewNodeTx() *cobra.Command { Use: "tx", Short: "Transactions subcommands", } + c.PersistentFlags().AddFlagSet(flagSetHome()) + c.PersistentFlags().AddFlagSet(flagSetKeyringBackend()) + c.PersistentFlags().AddFlagSet(flagSetAccountPrefixes()) + c.PersistentFlags().AddFlagSet(flagSetKeyringDir()) + c.PersistentFlags().AddFlagSet(flagSetGenerateOnly()) + c.PersistentFlags().AddFlagSet(flagSetGasFlags()) + c.PersistentFlags().String(flagFees, "", "Fees to pay along with transaction; eg: 10uatom") + c.PersistentFlags().String(flagBroadcastMode, flags.BroadcastBlock, "Transaction broadcasting mode (sync|async|block), use sync if you encounter timeouts") c.AddCommand(NewNodeTxBank()) @@ -59,3 +69,8 @@ func getFees(cmd *cobra.Command) string { fees, _ := cmd.Flags().GetString(flagFees) return fees } + +func getBroadcastMode(cmd *cobra.Command) string { + broadcastMode, _ := cmd.Flags().GetString(flagBroadcastMode) + return broadcastMode +} diff --git a/ignite/cmd/node_tx_bank_send.go b/ignite/cmd/node_tx_bank_send.go index ddfb5aa976..5665c6f690 100644 --- a/ignite/cmd/node_tx_bank_send.go +++ b/ignite/cmd/node_tx_bank_send.go @@ -1,6 +1,7 @@ package ignitecmd import ( + "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ignite/cli/ignite/pkg/cliui" "github.com/spf13/cobra" @@ -14,14 +15,6 @@ func NewNodeTxBankSend() *cobra.Command { Args: cobra.ExactArgs(3), } - c.Flags().AddFlagSet(flagSetHome()) - c.Flags().AddFlagSet(flagSetKeyringBackend()) - c.Flags().AddFlagSet(flagSetAccountPrefixes()) - c.Flags().AddFlagSet(flagSetKeyringDir()) - c.Flags().AddFlagSet(flagSetGenerateOnly()) - c.Flags().AddFlagSet(flagSetGasFlags()) - c.Flags().String(flagFees, "", "Fees to pay along with transaction; eg: 10uatom") - return c } @@ -73,11 +66,18 @@ func nodeTxBankSendHandler(cmd *cobra.Command, args []string) error { } session.StartSpinner("Sending transaction...") - if _, err := tx.Broadcast(); err != nil { + resp, err := tx.Broadcast() + if err != nil { return err } session.StopSpinner() - session.Println("Transaction broadcast successful!") - return session.Printf("%s sent from %s to %s\n", amount, fromAccountInput, toAccountInput) + session.Printf("Transaction broadcast successful! (hash = %s)\n", resp.TxHash) + session.Printf("%s sent from %s to %s\n", amount, fromAccountInput, toAccountInput) + if getBroadcastMode(cmd) != flags.BroadcastBlock { + session.Println("Transaction waiting to be included in a block.") + session.Println("Run the following command to follow the transaction status:") + session.Printf(" ignite node --node %s q tx %s\n", getRPC(cmd), resp.TxHash) + } + return nil } diff --git a/ignite/pkg/cosmosclient/cosmosclient.go b/ignite/pkg/cosmosclient/cosmosclient.go index 2df1605fb7..c535b97fc8 100644 --- a/ignite/pkg/cosmosclient/cosmosclient.go +++ b/ignite/pkg/cosmosclient/cosmosclient.go @@ -84,9 +84,10 @@ type Client struct { keyringBackend cosmosaccount.KeyringBackend keyringDir string - gas string - gasPrices string - fees string + gas string + gasPrices string + fees string + broadcastMode string } // Option configures your client. @@ -172,6 +173,13 @@ func WithFees(fees string) Option { } } +// WithBroadcastMode sets the broadcast mode +func WithBroadcastMode(broadcastMode string) Option { + return func(c *Client) { + c.broadcastMode = broadcastMode + } +} + // New creates a new client with given options. func New(ctx context.Context, options ...Option) (Client, error) { c := Client{ @@ -183,6 +191,7 @@ func New(ctx context.Context, options ...Option) (Client, error) { faucetMinAmount: defaultFaucetMinAmount, out: io.Discard, gas: strconv.Itoa(defaultGasLimit), + broadcastMode: flags.BroadcastBlock, } var err error @@ -223,7 +232,7 @@ func New(ctx context.Context, options ...Option) (Client, error) { return Client{}, err } - c.context = newContext(c.RPC, c.out, c.chainID, c.homePath).WithKeyring(c.AccountRegistry.Keyring) + c.context = c.newContext() c.Factory = newFactory(c.context) return c, nil @@ -535,12 +544,7 @@ func prepareFactory(clientCtx client.Context, txf tx.Factory) (tx.Factory, error return txf, nil } -func newContext( - c *rpchttp.HTTP, - out io.Writer, - chainID, - home string, -) client.Context { +func (c Client) newContext() client.Context { var ( amino = codec.NewLegacyAmino() interfaceRegistry = codectypes.NewInterfaceRegistry() @@ -556,18 +560,19 @@ func newContext( banktypes.RegisterInterfaces(interfaceRegistry) return client.Context{}. - WithChainID(chainID). + WithChainID(c.chainID). WithInterfaceRegistry(interfaceRegistry). WithCodec(marshaler). WithTxConfig(txConfig). WithLegacyAmino(amino). WithInput(os.Stdin). - WithOutput(out). + WithOutput(c.out). WithAccountRetriever(authtypes.AccountRetriever{}). - WithBroadcastMode(flags.BroadcastBlock). - WithHomeDir(home). - WithClient(c). - WithSkipConfirmation(true) + WithBroadcastMode(c.broadcastMode). + WithHomeDir(c.homePath). + WithClient(c.RPC). + WithSkipConfirmation(true). + WithKeyring(c.AccountRegistry.Keyring) } func newFactory(clientCtx client.Context) tx.Factory { diff --git a/integration/node/cmd_query_tx_test.go b/integration/node/cmd_query_tx_test.go new file mode 100644 index 0000000000..7506d9a915 --- /dev/null +++ b/integration/node/cmd_query_tx_test.go @@ -0,0 +1,97 @@ +package node_test + +import ( + "bytes" + "context" + "regexp" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/ignite/pkg/cmdrunner/step" + "github.com/ignite/cli/ignite/pkg/cosmosclient" + "github.com/ignite/cli/ignite/pkg/randstr" + "github.com/ignite/cli/ignite/pkg/xurl" + envtest "github.com/ignite/cli/integration" +) + +func TestNodeQueryTx(t *testing.T) { + var ( + appname = randstr.Runes(10) + // alice = "alice" + // bob = "bob" + + env = envtest.New(t) + app = env.Scaffold(appname) + home = env.AppHome(appname) + servers = app.RandomizeServerPorts() + + // accKeyringDir = t.TempDir() + ) + + node, err := xurl.HTTP(servers.RPC) + require.NoError(t, err) + + var ( + ctx, cancel = context.WithTimeout(env.Ctx(), envtest.ServeTimeout) + isBackendAliveErr error + ) + + go func() { + defer cancel() + + if isBackendAliveErr = env.IsAppServed(ctx, servers); isBackendAliveErr != nil { + return + } + client, err := cosmosclient.New(context.Background(), + cosmosclient.WithAddressPrefix(testPrefix), + cosmosclient.WithNodeAddress(node), + ) + require.NoError(t, err) + require.NoError(t, client.WaitForNextBlock()) + + b := &bytes.Buffer{} + env.Exec("send 100token from alice to bob", + step.NewSteps(step.New( + step.Exec( + envtest.IgniteApp, + "node", + "tx", + "bank", + "send", + "alice", + "bob", + "100token", + "--node", node, + "--keyring-dir", home, + "--broadcast-mode", "sync", + ), + step.Stdout(b), + )), + ) + require.False(t, env.HasFailed(), b.String()) + + // Parse tx hash from output + res := regexp.MustCompile(`\(hash = (\w+)\)`).FindAllStringSubmatch(b.String(), -1) + require.Len(t, res[0], 2, "can't extract hash from command output") + hash := res[0][1] + require.NoError(t, client.WaitForNextBlock()) + + env.Must(env.Exec("query tx", + step.NewSteps(step.New( + step.Exec( + envtest.IgniteApp, + "node", + "query", + "tx", + hash, + "--node", node, + ), + )), + )) + }() + + env.Must(app.Serve("should serve with Stargate version", envtest.ExecCtx(ctx))) + + require.NoError(t, isBackendAliveErr, "app cannot get online in time") +}