Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add CLI commands: 1) simulate a transaction, 2) query block results #16887

Merged
merged 8 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* (cli) [#16887](https://github.com/cosmos/cosmos-sdk/pull/16887) Add two new CLI commands: `tx simulate` for simulating a transaction; `query block-results` for querying CometBFT RPC for block results.

### Improvements

* (cli) [#16856](https://github.com/cosmos/cosmos-sdk/pull/16856) Improve `simd prune` UX by using the app default home directory and set pruning method as first variable argument (defaults to default).
Expand Down
1 change: 1 addition & 0 deletions client/cometbft.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type CometRPC interface {
Status(context.Context) (*coretypes.ResultStatus, error)
Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error)
BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error)
BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error)
BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error)
Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error)
Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error)
Expand Down
75 changes: 68 additions & 7 deletions server/cmt_cmds.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package server

import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
Expand Down Expand Up @@ -206,18 +208,13 @@ $ %s query block --%s=%s <hash>
return fmt.Errorf("argument should be a block height")
}

var height *int64

// optional height
var height *int64
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
height, err = parseOptionalHeight(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}

output, err := rpc.GetBlockByHeight(clientCtx, height)
Expand Down Expand Up @@ -261,6 +258,70 @@ $ %s query block --%s=%s <hash>
return cmd
}

// QueryBlockResultCmd implements the default command for a BlockResults query.
func QueryBlockResultsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "block-results [height]",
Short: "Query for a committed block's results by height",
Long: "Query for a specific committed block's results using the CometBFT RPC `block_results` method",
Args: cobra.RangeArgs(0, 1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

node, err := clientCtx.GetNode()
if err != nil {
return err
}

// optional height
var height *int64
if len(args) > 0 {
height, err = parseOptionalHeight(args[0])
if err != nil {
return err
}
}

blockRes, err := node.BlockResults(context.Background(), height)
if err != nil {
return err
}

// coretypes.ResultBlockResults doesn't implement proto.Message interface
// so we can't print it using clientCtx.PrintProto
// we choose to serialize it to json and print the json instead
blockResStr, err := json.Marshal(blockRes)
if err != nil {
return err
}

return clientCtx.PrintString(string(blockResStr) + "\n")
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func parseOptionalHeight(heightStr string) (*int64, error) {
h, err := strconv.Atoi(heightStr)
if err != nil {
return nil, err
}

if h == 0 {
return nil, nil
}

tmp := int64(h)

return &tmp, nil
}

func BootstrapStateCmd(appCreator types.AppCreator) *cobra.Command {
cmd := &cobra.Command{
Use: "bootstrap-state",
Expand Down
2 changes: 2 additions & 0 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ func queryCommand() *cobra.Command {
authcmd.QueryTxsByEventsCmd(),
server.QueryBlocksCmd(),
authcmd.QueryTxCmd(),
server.QueryBlockResultsCmd(),
)

return cmd
Expand All @@ -270,6 +271,7 @@ func txCommand() *cobra.Command {
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetAuxToFeeCommand(),
authcmd.GetSimulateCmd(),
)

return cmd
Expand Down
2 changes: 2 additions & 0 deletions simapp/simd/cmd/root_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ func queryCommand() *cobra.Command {
authcmd.QueryTxsByEventsCmd(),
server.QueryBlocksCmd(),
authcmd.QueryTxCmd(),
server.QueryBlockResultsCmd(),
)

return cmd
Expand All @@ -287,6 +288,7 @@ func txCommand() *cobra.Command {
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetAuxToFeeCommand(),
authcmd.GetSimulateCmd(),
)

return cmd
Expand Down
101 changes: 101 additions & 0 deletions x/auth/client/cli/tx_simulate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cli

import (
"strings"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
)

// GetSimulateCmd returns a command that simulates whether a transaction will be
// successful.
func GetSimulateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "simulate /path/to/unsigned-tx.json --from keyname",
Short: "Simulate the gas usage of a transaction",
Long: strings.TrimSpace(`Simulate whether a transaction will be successful:

- if successful, the simulation result is printed, which includes the gas
consumption, message response data, and events emitted;
- if unsuccessful, the error message is printed.

The user must provide the path to a JSON-encoded unsigned transaction, typically
generated by any transaction command with the --generate-only flag. It should
look like below. Note that the "signer_infos" and "signatures" fields are left
empty; they will be auto-populated by dummy data for simulation purpose.

{
"body": {
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "cosmos1...",
"to_address": "cosmos1...",
"amount": [
{
"denom": "utoken",
"amount": "12345"
}
]
}
],
"memo": "",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": []
},
"auth_info": {
"signer_infos": [],
"fee": {
"amount": [],
"gas_limit": "200000",
"payer": "",
"granter": ""
},
"tip": null
},
"signatures": []
}

The --from flag is mandatory, as the signer account's correct sequence number is
necessary for simulation.
`),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
if err != nil {
return err
}

txf, err = txf.Prepare(clientCtx)
if err != nil {
return err
}

stdTx, err := authclient.ReadTxFromFile(clientCtx, args[0])
if err != nil {
return err
}

simRes, _, err := tx.CalculateGas(clientCtx, txf, stdTx.GetMsgs()...)
if err != nil {
return err
}

return clientCtx.PrintProto(simRes)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}