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 limits to queries (backport #16239) #17324

Merged
merged 1 commit into from
Aug 9, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features

* (x/bank) [#16795](https://github.com/cosmos/cosmos-sdk/pull/16852) Add `DenomMetadataByQueryString` query in bank module to support metadata query by query string.
* (baseapp) [#16239](https://github.com/cosmos/cosmos-sdk/pull/16239) Add Gas Limits to allow node operators to resource bound queries.

### Improvements

Expand Down
3 changes: 2 additions & 1 deletion baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,8 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
// branch the commit multi-store for safety
ctx := sdk.NewContext(cacheMS, app.checkState.ctx.BlockHeader(), true, app.logger).
WithMinGasPrices(app.minGasPrices).
WithBlockHeight(height)
WithBlockHeight(height).
WithGasMeter(storetypes.NewGasMeter(app.queryGasLimit))

if height != lastBlockHeight {
rms, ok := app.cms.(*rootmulti.Store)
Expand Down
5 changes: 5 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baseapp
import (
"context"
"fmt"
"math"
"sort"
"strconv"

Expand Down Expand Up @@ -123,6 +124,9 @@ type BaseApp struct {
// application parameter store.
paramStore ParamStore

// queryGasLimit defines the maximum gas for queries; unbounded if 0.
queryGasLimit uint64

// The minimum gas prices a validator is willing to accept for processing a
// transaction. This is mainly used for DoS and spam prevention.
minGasPrices sdk.DecCoins
Expand Down Expand Up @@ -192,6 +196,7 @@ func NewBaseApp(
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
queryGasLimit: math.MaxUint64,
}

for _, option := range options {
Expand Down
74 changes: 74 additions & 0 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ func NewBaseAppSuite(t *testing.T, opts ...func(*baseapp.BaseApp)) *BaseAppSuite
}
}

func getQueryBaseapp(t *testing.T) *baseapp.BaseApp {
t.Helper()

db := dbm.NewMemDB()
name := t.Name()
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)

_, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)

_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)

return app
}

func NewBaseAppSuiteWithSnapshots(t *testing.T, cfg SnapshotsConfig, opts ...func(*baseapp.BaseApp)) *BaseAppSuite {
snapshotTimeout := 1 * time.Minute
snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), testutil.GetTempDir(t))
Expand Down Expand Up @@ -614,6 +634,60 @@ func TestSetMinGasPrices(t *testing.T) {
require.Equal(t, minGasPrices, ctx.MinGasPrices())
}

type ctxType string

const (
QueryCtx ctxType = "query"
CheckTxCtx ctxType = "checkTx"
)

var ctxTypes = []ctxType{QueryCtx, CheckTxCtx}

func (c ctxType) GetCtx(t *testing.T, bapp *baseapp.BaseApp) sdk.Context {
t.Helper()
if c == QueryCtx {
ctx, err := bapp.CreateQueryContext(1, false)
require.NoError(t, err)
return ctx
} else if c == CheckTxCtx {
return getCheckStateCtx(bapp)
}
// TODO: Not supported yet
return getFinalizeBlockStateCtx(bapp)
}

func TestQueryGasLimit(t *testing.T) {
testCases := []struct {
queryGasLimit uint64
gasActuallyUsed uint64
shouldQueryErr bool
}{
{queryGasLimit: 100, gasActuallyUsed: 50, shouldQueryErr: false}, // Valid case
{queryGasLimit: 100, gasActuallyUsed: 150, shouldQueryErr: true}, // gasActuallyUsed > queryGasLimit
{queryGasLimit: 0, gasActuallyUsed: 50, shouldQueryErr: false}, // fuzzing with queryGasLimit = 0
{queryGasLimit: 0, gasActuallyUsed: 0, shouldQueryErr: false}, // both queryGasLimit and gasActuallyUsed are 0
{queryGasLimit: 200, gasActuallyUsed: 200, shouldQueryErr: false}, // gasActuallyUsed == queryGasLimit
{queryGasLimit: 100, gasActuallyUsed: 1000, shouldQueryErr: true}, // gasActuallyUsed > queryGasLimit
}

for _, tc := range testCases {
for _, ctxType := range ctxTypes {
t.Run(fmt.Sprintf("%s: %d - %d", ctxType, tc.queryGasLimit, tc.gasActuallyUsed), func(t *testing.T) {
app := getQueryBaseapp(t)
baseapp.SetQueryGasLimit(tc.queryGasLimit)(app)
ctx := ctxType.GetCtx(t, app)

// query gas limit should have no effect when CtxType != QueryCtx
if tc.shouldQueryErr && ctxType == QueryCtx {
require.Panics(t, func() { ctx.GasMeter().ConsumeGas(tc.gasActuallyUsed, "test") })
} else {
require.NotPanics(t, func() { ctx.GasMeter().ConsumeGas(tc.gasActuallyUsed, "test") })
}
})
}
}
}

func TestGetMaximumBlockGas(t *testing.T) {
suite := NewBaseAppSuite(t)
_, err := suite.baseApp.InitChain(&abci.RequestInitChain{})
Expand Down
10 changes: 10 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baseapp
import (
"fmt"
"io"
"math"

dbm "github.com/cosmos/cosmos-db"

Expand Down Expand Up @@ -36,6 +37,15 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
return func(bapp *BaseApp) { bapp.setMinGasPrices(gasPrices) }
}

// SetQueryGasLimit returns an option that sets a gas limit for queries.
func SetQueryGasLimit(queryGasLimit uint64) func(*BaseApp) {
if queryGasLimit == 0 {
queryGasLimit = math.MaxUint64
}

return func(bapp *BaseApp) { bapp.queryGasLimit = queryGasLimit }
}

// SetHaltHeight returns a BaseApp option function that sets the halt block height.
func SetHaltHeight(blockHeight uint64) func(*BaseApp) {
return func(bapp *BaseApp) { bapp.setHaltHeight(blockHeight) }
Expand Down
5 changes: 5 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ type BaseConfig struct {
// specified in this config (e.g. 0.25token1;0.0001token2).
MinGasPrices string `mapstructure:"minimum-gas-prices"`

// The maximum amount of gas a grpc/Rest query may consume.
// If set to 0, it is unbounded.
QueryGasLimit uint64 `mapstructure:"query-gas-limit"`

Pruning string `mapstructure:"pruning"`
PruningKeepRecent string `mapstructure:"pruning-keep-recent"`
PruningInterval string `mapstructure:"pruning-interval"`
Expand Down Expand Up @@ -225,6 +229,7 @@ func DefaultConfig() *Config {
return &Config{
BaseConfig: BaseConfig{
MinGasPrices: defaultMinGasPrices,
QueryGasLimit: 0,
InterBlockCache: true,
Pruning: pruningtypes.PruningOptionDefault,
PruningKeepRecent: "0",
Expand Down
4 changes: 4 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const DefaultConfigTemplate = `# This is a TOML config file.
# specified in this config (e.g. 0.25token1;0.0001token2).
minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}"

# The maximum gas a query coming over rest/grpc may consume.
# If this is set to zero, the query can consume an unbounded amount of gas.
query-gas-limit = "{{ .BaseConfig.QueryGasLimit }}"

# default: the last 362880 states are kept, pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: 2 latest states will be kept; pruning at 10 block intervals.
Expand Down
2 changes: 2 additions & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
flagTraceStore = "trace-store"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagQueryGasLimit = "query-gas-limit"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
Expand Down Expand Up @@ -180,6 +181,7 @@ is performed. Note, when enabled, gRPC will also be automatically enabled.
cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
cmd.Flags().Uint64(FlagQueryGasLimit, 0, "Maximum gas a Rest/Grpc query can consume. Blank and 0 imply unbounded.")
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
Expand Down
1 change: 1 addition & 0 deletions server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) {
baseapp.SetIAVLDisableFastNode(cast.ToBool(appOpts.Get(FlagDisableIAVLFastNode))),
defaultMempool,
baseapp.SetChainID(chainID),
baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))),
}
}

Expand Down
6 changes: 5 additions & 1 deletion tools/confix/data/v0.50-app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
# specified in this config (e.g. 0.25token1;0.0001token2).
minimum-gas-prices = "0stake"

# The maximum gas a query coming over rest/grpc may consume.
# If this is set to zero, the query can consume an unbounded amount of gas.
query-gas-limit = "0"

# default: the last 362880 states are kept, pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: 2 latest states will be kept; pruning at 10 block intervals.
Expand Down Expand Up @@ -226,4 +230,4 @@ max-txs = "5000"
query_gas_limit = 300000
# This is the number of wasm vm instances we keep cached in memory for speed-up
# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally
lru_size = 0
lru_size = 0