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 gas-related parameters to client config #12723

Closed
wants to merge 13 commits into from
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ Ref: https://keepachangelog.com/en/1.0.0/

### CLI Breaking Changes

* /
* (client) [#12723](https://github.com/cosmos/cosmos-sdk/pull/12723) The client config now includes default values for several gas-related parameters, which previously needed to be provided by `--gas`, `--gas-adjustment`, and `--gas-prices` flags.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, why do you consider this CLI breaking and not a feature or improvement?

Copy link
Contributor Author

@larry0x larry0x Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because users need to add these three lines to their .simapp/config/client.toml:

gas = "200000"
gas-adjustment = "1"
gas-prices = ""

either by running simd init again or by manually editing the file, of the tx command won't work.

this is because when creating the ClientCtx the client will attempt to read these three params from client.toml, and if not found it will throw an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add exactly that in the upgrading.md ? Under a Configuration section or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add exactly that in the upgrading.md ? Under a Configuration section or something?

done: 7c099f8

Copy link
Contributor

@alexanderbez alexanderbez Jul 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noooo, I don't think this is wise. We should not be requiring any fields in client.toml whatsoever. I'm strongly opposed to this. client.toml should be completely opt-in and read only when it exists.

AFAIU, the way client.toml works is that if a supported field exists in that file, it takes precedence over a default value and a flag value. But in no circumstance should a field ever be required to exist in that file.

In general the order of precedence is:

env var > file > flag > default

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noooo, I don't think this is wise. We should not be requiring any fields in client.toml whatsoever. I'm strongly opposed to this. client.toml should be completely opt-in and read only when it exists.

this is in fact already the case for other fields currently in client.toml. the chain id for example, if you neither have it configured in client.toml nor specify it by the CLI flag, then the client won't be able to sign txs:

} else if f.chainID == "" {
return nil, fmt.Errorf("chain ID required but not specified")
}

the gas parameters aren't different from this. you need to either configure them in client.toml, or specify with CLI flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexanderbez ok here's how i think we can make this work. currently, clientCtx is created by parsing client.toml:

// cosmos-sdk/client/config/config.go
func ReadFromClientConfig(ctx client.Context) (client.Context, err) {
	// parse gas setting
	gasSetting, err := client.ParseGasSetting(conf.Gas)
	if err != nil {
		return ctx, fmt.Errorf("couldn't get gas setting from client config: %v", err)
	}

	ctx = ctx.WithGasSetting(gasSetting)

	// parse gas adjustment
	gasAdj, err := strconv.ParseFloat(conf.GasAdjustment, 64)
	if err != nil {
		gasAdj, _ = strconv.ParseFloat(gasAdjustment, 64)
	}

	ctx = ctx.WithGasAdjustment(gasAdj)

	// parse gas prices
	gasPrices, err := sdk.ParseDecCoins(conf.GasPrices)
	if err != nil {
		return ctx, fmt.Errorf("couldn't get gas prices from client config: %v", err)
	}

	ctx = ctx.WithGasPrices(gasPrices)

	// parse other client.toml fields, e.g. chain id, rpc node, sign mode, etc.
	// ...

	return ctx
}

take gas-adjustment as an example: assume it is NOT present in client.toml (e.g. if client.toml was generated by an old version of SimApp). in this case conf.GasAdjustment will be an empty string "", which causes strconv.ParseFloat to fail.

this is the reason that the three gas parameters are mandatory in client.toml.

however we can make them optional simply by, instead of returning err here, we use some default values. this way, users won't need to update their client.toml files. sounds good?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the order of precedence is:

env var > file > flag > default

let me research how other config params are handled. i'll keep this PR as draft for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

however we can make them optional simply by, instead of returning err here, we use some default values. this way, users won't need to update their client.toml files. sounds good?

Yes, in general this is the idea. I would do whatever we do for all the values we support in client.toml. I haven't looked at how that file is parsed and handled, but I imagine it only parses when non-empty.

These default values can be configured with the `simd config` command or by editing `.simapp/config/client.toml`.
When submitting a tx, if any of these flags is not specified, the value in the client config will be used.

### Bug Fixes

Expand Down
17 changes: 15 additions & 2 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ This guide provides instructions for upgrading to specific versions of Cosmos SD

Remove `Querier`, `Route` and `LegacyQuerier` from the app module interface. This removes and fully deprecates all legacy queriers. All modules no longer support the REST API previously known as the LCD, and the `sdk.Msg#Route` method won't be used anymore.



### SimApp

SimApp's `app.go` is using App Wiring, the dependency injection framework of the Cosmos SDK.
Expand All @@ -26,6 +24,21 @@ The constructor, `NewSimApp` has been simplified:
`simapp.MakeTestEncodingConfig()` was deprecated and has been removed. Instead you can use the `TestEncodingConfig` from the `types/module/testutil` package.
This means you can replace your usage of `simapp.MakeTestEncodingConfig` in tests to `moduletestutil.MakeTestEncodingConfig`, which takes a series of relevant `AppModuleBasic` as input (the module being tested and any potential dependencies).

### Client Configuration

Several gas-related parameters have been added to the client config. Using SimApp as an example, the client config is recorded in the file `~/.simapp/config/client.toml`. If you have this file generated by a previous version of the Cosmos SDK, it is necessary to add the following lines:

```toml
# Gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically
gas = "200000"
# Adjustment factor to be multiplied against the estimate returned by the tx simulation
gas-adjustment = "1"
# Gas prices in decimal format to determine the transaction fee
gas-prices = ""
```

This can be done either by re-running `simd init`, or by manually editing the file using a text editor.

## [v0.46.x](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.0)

### Client Changes
Expand Down
12 changes: 12 additions & 0 deletions client/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ func runConfigCmd(cmd *cobra.Command, args []string) error {
cmd.Println(conf.Node)
case flags.FlagBroadcastMode:
cmd.Println(conf.BroadcastMode)
case flags.FlagGas:
cmd.Println(conf.Gas)
case flags.FlagGasAdjustment:
cmd.Println(conf.GasAdjustment)
case flags.FlagGasPrices:
cmd.Println(conf.GasPrices)
default:
err := errUnknownConfigKey(key)
return fmt.Errorf("couldn't get the value for the key: %v, error: %v", key, err)
Expand All @@ -78,6 +84,12 @@ func runConfigCmd(cmd *cobra.Command, args []string) error {
conf.SetNode(value)
case flags.FlagBroadcastMode:
conf.SetBroadcastMode(value)
case flags.FlagGas:
conf.SetGas(value)
case flags.FlagGasAdjustment:
conf.SetGasAdjustment(value)
case flags.FlagGasPrices:
conf.SetGasPrices(value)
default:
return errUnknownConfigKey(key)
}
Expand Down
44 changes: 43 additions & 1 deletion client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// Default constants
Expand All @@ -15,6 +17,9 @@ const (
output = "text"
node = "tcp://localhost:26657"
broadcastMode = "sync"
gas = "200000"
gasAdjustment = "1"
gasPrices = ""
)

type ClientConfig struct {
Expand All @@ -23,11 +28,14 @@ type ClientConfig struct {
Output string `mapstructure:"output" json:"output"`
Node string `mapstructure:"node" json:"node"`
BroadcastMode string `mapstructure:"broadcast-mode" json:"broadcast-mode"`
Gas string `mapstructure:"gas" json:"gas"`
GasAdjustment string `mapstructure:"gas-adjustment" json:"gas-adjustment"`
GasPrices string `mapstructure:"gas-prices" json:"gas-prices"`
}

// defaultClientConfig returns the reference to ClientConfig with default values.
func defaultClientConfig() *ClientConfig {
return &ClientConfig{chainID, keyringBackend, output, node, broadcastMode}
return &ClientConfig{chainID, keyringBackend, output, node, broadcastMode, gas, gasAdjustment, gasPrices}
}

func (c *ClientConfig) SetChainID(chainID string) {
Expand All @@ -50,6 +58,18 @@ func (c *ClientConfig) SetBroadcastMode(broadcastMode string) {
c.BroadcastMode = broadcastMode
}

func (c *ClientConfig) SetGas(gas string) {
c.Gas = gas
}

func (c *ClientConfig) SetGasAdjustment(gasAdj string) {
c.GasAdjustment = gasAdj
}

func (c *ClientConfig) SetGasPrices(gasPrices string) {
c.GasPrices = gasPrices
}

// ReadFromClientConfig reads values from client.toml file and updates them in client Context
func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
configPath := filepath.Join(ctx.HomeDir, "config")
Expand All @@ -75,6 +95,28 @@ func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
if err != nil {
return ctx, fmt.Errorf("couldn't get client config: %v", err)
}

gasSetting, err := client.ParseGasSetting(conf.Gas)
if err != nil {
return ctx, fmt.Errorf("couldn't get gas setting from client config: %v", err)
}

ctx = ctx.WithGasSetting(gasSetting)

gasAdj, err := strconv.ParseFloat(conf.GasAdjustment, 64)
if err != nil {
return ctx, fmt.Errorf("couldn't get gas adjustment from client config: %v", err)
}

ctx = ctx.WithGasAdjustment(gasAdj)

gasPrices, err := sdk.ParseDecCoins(conf.GasPrices)
if err != nil {
return ctx, fmt.Errorf("couldn't get gas prices from client config: %v", err)
}

ctx = ctx.WithGasPrices(gasPrices)

// we need to update KeyringDir field on Client Context first cause it is used in NewKeyringFromBackend
ctx = ctx.WithOutputFormat(conf.Output).
WithChainID(conf.ChainID).
Expand Down
6 changes: 6 additions & 0 deletions client/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ output = "{{ .Output }}"
node = "{{ .Node }}"
# Transaction broadcasting mode (sync|async|block)
broadcast-mode = "{{ .BroadcastMode }}"
# Gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically
gas = "{{ .Gas }}"
# Adjustment factor to be multiplied against the estimate returned by the tx simulation
gas-adjustment = "{{ .GasAdjustment }}"
# Gas prices in decimal format to determine the transaction fee
gas-prices = "{{ .GasPrices }}"
`

// writeConfigToFile parses defaultConfigTemplate, renders config using the template and writes it to
Expand Down
21 changes: 21 additions & 0 deletions client/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type Context struct {
FeePayer sdk.AccAddress
FeeGranter sdk.AccAddress
Viper *viper.Viper
GasSetting GasSetting
GasAdjustment float64
GasPrices sdk.DecCoins

// IsAux is true when the signer is an auxiliary signer (e.g. the tipper).
IsAux bool
Expand Down Expand Up @@ -260,6 +263,24 @@ func (ctx Context) WithViper(prefix string) Context {
return ctx
}

// WithGasSetting returns the context with an updated GasSetting
func (ctx Context) WithGasSetting(gasSetting GasSetting) Context {
ctx.GasSetting = gasSetting
return ctx
}

// WithGasAdjustment returns the context with an updated GasAdjustment
func (ctx Context) WithGasAdjustment(gasAdj float64) Context {
ctx.GasAdjustment = gasAdj
return ctx
}

// WithGasPrices returns the context with an updated GasPrices
func (ctx Context) WithGasPrices(gasPrices sdk.DecCoins) Context {
ctx.GasPrices = gasPrices
return ctx
}

// WithAux returns a copy of the context with an updated IsAux value.
func (ctx Context) WithAux(isAux bool) Context {
ctx.IsAux = isAux
Expand Down
41 changes: 2 additions & 39 deletions client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package flags

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
tmcli "github.com/tendermint/tendermint/libs/cli"
Expand Down Expand Up @@ -112,7 +111,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().String(FlagGasPrices, "", "Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)")
cmd.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
cmd.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
cmd.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
cmd.Flags().Float64(FlagGasAdjustment, 0, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still be 1.0 IMO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the client will now use the value in client.toml as the default, instead of what's defined here. setting this to 0 means there is no default for the flag.

Copy link
Contributor Author

@larry0x larry0x Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in other words, if i have it as 1.0 here, the CLI help text will say (default: 1), while in fact the default value is taken from client.toml and may not be 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so again, client.toml should not be defining any defaults. The file should be completely opt-in. So "" is treated as an OK value which means it would read the default value from the CLI flags.

cmd.Flags().StringP(FlagBroadcastMode, "b", BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
cmd.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible)")
cmd.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name)")
Expand All @@ -127,7 +126,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().Bool(FlagAux, false, "Generate aux signer data instead of sending a tx")

// --gas can accept integers and "auto"
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit))
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically", GasFlagAuto))
}

// AddPaginationFlagsToCmd adds common pagination flags to cmd
Expand All @@ -139,39 +138,3 @@ func AddPaginationFlagsToCmd(cmd *cobra.Command, query string) {
cmd.Flags().Bool(FlagCountTotal, false, fmt.Sprintf("count total number of records in %s to query for", query))
cmd.Flags().Bool(FlagReverse, false, "results are sorted in descending order")
}

// GasSetting encapsulates the possible values passed through the --gas flag.
type GasSetting struct {
Simulate bool
Gas uint64
}

func (v *GasSetting) String() string {
if v.Simulate {
return GasFlagAuto
}

return strconv.FormatUint(v.Gas, 10)
}

// ParseGasSetting parses a string gas value. The value may either be 'auto',
// which indicates a transaction should be executed in simulate mode to
// automatically find a sufficient gas value, or a string integer. It returns an
// error if a string integer is provided which cannot be parsed.
func ParseGasSetting(gasStr string) (GasSetting, error) {
switch gasStr {
case "":
return GasSetting{false, DefaultGasLimit}, nil

case GasFlagAuto:
return GasSetting{true, 0}, nil

default:
gas, err := strconv.ParseUint(gasStr, 10, 64)
if err != nil {
return GasSetting{}, fmt.Errorf("gas must be either integer or %s", GasFlagAuto)
}

return GasSetting{false, gas}, nil
}
}
49 changes: 49 additions & 0 deletions client/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package client

import (
"fmt"
"strconv"

"github.com/cosmos/cosmos-sdk/client/flags"
)

// GasSetting encapsulates the possible values passed through the --gas flag.
type GasSetting struct {
Simulate bool
Gas uint64
}

func (v *GasSetting) String() string {
if v.Simulate {
return flags.GasFlagAuto
}

return strconv.FormatUint(v.Gas, 10)
}

// DefaultGasSetting returns the default gas setting
func DefaultGasSetting() GasSetting {
return GasSetting{false, flags.DefaultGasLimit}
}

// ParseGasSetting parses a string gas value. The value may either be 'auto',
// which indicates a transaction should be executed in simulate mode to
// automatically find a sufficient gas value, or a string integer. It returns an
// error if a string integer is provided which cannot be parsed.
func ParseGasSetting(gasStr string) (GasSetting, error) {
switch gasStr {
case "":
return DefaultGasSetting(), nil

case flags.GasFlagAuto:
return GasSetting{true, 0}, nil

default:
gas, err := strconv.ParseUint(gasStr, 10, 64)
if err != nil {
return GasSetting{}, fmt.Errorf("gas must be either integer or %s", flags.GasFlagAuto)
}

return GasSetting{false, gas}, nil
}
}
15 changes: 8 additions & 7 deletions client/flags/flags_test.go → client/gas_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
package flags_test
package client_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
)

func TestParseGasSetting(t *testing.T) {
testCases := []struct {
name string
input string
expected flags.GasSetting
expected client.GasSetting
expectErr bool
}{
{"empty input", "", flags.GasSetting{false, flags.DefaultGasLimit}, false},
{"auto", flags.GasFlagAuto, flags.GasSetting{true, 0}, false},
{"valid custom gas", "73800", flags.GasSetting{false, 73800}, false},
{"invalid custom gas", "-73800", flags.GasSetting{false, 0}, true},
{"empty input", "", client.DefaultGasSetting(), false},
{"auto", flags.GasFlagAuto, client.GasSetting{true, 0}, false},
{"valid custom gas", "73800", client.GasSetting{false, 73800}, false},
{"invalid custom gas", "-73800", client.GasSetting{false, 0}, true},
}

for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
gs, err := flags.ParseGasSetting(tc.input)
gs, err := client.ParseGasSetting(tc.input)

if tc.expectErr {
require.Error(t, err)
Expand Down
Loading