Skip to content

Commit

Permalink
feat: min gas prices config (#3340)
Browse files Browse the repository at this point in the history
The minimum gas price configuration is a mechanism to prevent spam and
ensure that only transactions with sufficient fees are processed. It
achieves this by setting a minimum cost per unit of gas that a
transaction must meet to be included in the mempool.

The following condition is checked by the validator: the gas and fees
provided by the user must meet this condition before the transaction can
be included in the mempool and subsequently processed by the VM.
 
Gas Fee  => Gas Wanted x Min Gas Prices 

A node operator can set the min gas prices as following

`gnoland config set application.min_gas_prices "1000ugnot/1gas"
`

Co-authored-by: Nathan Toups <[email protected]>
  • Loading branch information
piux2 and n2p5 authored Jan 8, 2025
1 parent faf70cb commit 27073ec
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 7 deletions.
4 changes: 3 additions & 1 deletion gno.land/cmd/gnoland/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/events"
osm "github.com/gnolang/gno/tm2/pkg/os"

"github.com/gnolang/gno/tm2/pkg/std"
"github.com/gnolang/gno/tm2/pkg/telemetry"
"go.uber.org/zap"
Expand Down Expand Up @@ -234,9 +235,10 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error {

// Create a top-level shared event switch
evsw := events.NewEventSwitch()
minGasPrices := cfg.Application.MinGasPrices

// Create application and node
cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger)
cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger, minGasPrices)
if err != nil {
return fmt.Errorf("unable to create the Gnoland app, %w", err)
}
Expand Down
11 changes: 9 additions & 2 deletions gno.land/pkg/gnoland/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type AppOptions struct {
EventSwitch events.EventSwitch // required
VMOutput io.Writer // optional
InitChainerConfig // options related to InitChainer
MinGasPrices string // optional
}

// TestAppOptions provides a "ready" default [AppOptions] for use with
Expand Down Expand Up @@ -79,9 +80,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
mainKey := store.NewStoreKey("main")
baseKey := store.NewStoreKey("base")

// set sdk app options
var appOpts []func(*sdk.BaseApp)
if cfg.MinGasPrices != "" {
appOpts = append(appOpts, sdk.SetMinGasPrices(cfg.MinGasPrices))
}
// Create BaseApp.
// TODO: Add a consensus based min gas prices for the node, by default it does not check
baseApp := sdk.NewBaseApp("gnoland", cfg.Logger, cfg.DB, baseKey, mainKey)
baseApp := sdk.NewBaseApp("gnoland", cfg.Logger, cfg.DB, baseKey, mainKey, appOpts...)
baseApp.SetAppVersion("dev")

// Set mounts for BaseApp's MultiStore.
Expand Down Expand Up @@ -181,6 +186,7 @@ func NewApp(
skipFailingGenesisTxs bool,
evsw events.EventSwitch,
logger *slog.Logger,
minGasPrices string,
) (abci.Application, error) {
var err error

Expand All @@ -191,6 +197,7 @@ func NewApp(
GenesisTxResultHandler: PanicOnFailingTxResultHandler,
StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"),
},
MinGasPrices: minGasPrices,
}
if skipFailingGenesisTxs {
cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler
Expand Down
2 changes: 1 addition & 1 deletion gno.land/pkg/gnoland/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func TestNewApp(t *testing.T) {
// NewApp should have good defaults and manage to run InitChain.
td := t.TempDir()

app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger())
app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger(), "")
require.NoError(t, err, "NewApp should be successful")

resp := app.InitChain(abci.RequestInitChain{
Expand Down
7 changes: 7 additions & 0 deletions tm2/pkg/bft/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/gnolang/gno/tm2/pkg/errors"
osm "github.com/gnolang/gno/tm2/pkg/os"
p2p "github.com/gnolang/gno/tm2/pkg/p2p/config"
sdk "github.com/gnolang/gno/tm2/pkg/sdk/config"
telemetry "github.com/gnolang/gno/tm2/pkg/telemetry/config"
)

Expand Down Expand Up @@ -55,6 +56,7 @@ type Config struct {
Consensus *cns.ConsensusConfig `json:"consensus" toml:"consensus" comment:"##### consensus configuration options #####"`
TxEventStore *eventstore.Config `json:"tx_event_store" toml:"tx_event_store" comment:"##### event store #####"`
Telemetry *telemetry.Config `json:"telemetry" toml:"telemetry" comment:"##### node telemetry #####"`
Application *sdk.AppConfig `json:"application" toml:"application" comment:"##### app settings #####"`
}

// DefaultConfig returns a default configuration for a Tendermint node
Expand All @@ -67,6 +69,7 @@ func DefaultConfig() *Config {
Consensus: cns.DefaultConsensusConfig(),
TxEventStore: eventstore.DefaultEventStoreConfig(),
Telemetry: telemetry.DefaultTelemetryConfig(),
Application: sdk.DefaultAppConfig(),
}
}

Expand Down Expand Up @@ -183,6 +186,7 @@ func TestConfig() *Config {
Consensus: cns.TestConsensusConfig(),
TxEventStore: eventstore.DefaultEventStoreConfig(),
Telemetry: telemetry.DefaultTelemetryConfig(),
Application: sdk.DefaultAppConfig(),
}
}

Expand Down Expand Up @@ -238,6 +242,9 @@ func (cfg *Config) ValidateBasic() error {
if err := cfg.Consensus.ValidateBasic(); err != nil {
return errors.Wrap(err, "Error in [consensus] section")
}
if err := cfg.Application.ValidateBasic(); err != nil {
return errors.Wrap(err, "Error in [application] section")
}
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion tm2/pkg/sdk/auth/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,10 @@ func EnsureSufficientMempoolFees(ctx sdk.Context, fee std.Fee) sdk.Result {
if prod1.Cmp(prod2) >= 0 {
return sdk.Result{}
} else {
fee := new(big.Int).Quo(prod2, gpg)
return abciResult(std.ErrInsufficientFee(
fmt.Sprintf(
"insufficient fees; got: {Gas-Wanted: %d, Gas-Fee %s}, fee required: %+v as minimum gas price set by the node", feeGasPrice.Gas, feeGasPrice.Price, gp,
"insufficient fees; got: {Gas-Wanted: %d, Gas-Fee %s}, fee required: %d with %+v as minimum gas price set by the node", feeGasPrice.Gas, feeGasPrice.Price, fee, gp,
),
))
}
Expand Down
35 changes: 35 additions & 0 deletions tm2/pkg/sdk/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package config

import (
"github.com/gnolang/gno/tm2/pkg/errors"
"github.com/gnolang/gno/tm2/pkg/std"
)

// -----------------------------------------------------------------------------
// Application Config

// AppConfig defines the configuration options for the Application
type AppConfig struct {
// Lowest gas prices accepted by a validator in the form of "100tokenA/3gas;10tokenB/5gas" separated by semicolons
MinGasPrices string `json:"min_gas_prices" toml:"min_gas_prices" comment:"Lowest gas prices accepted by a validator"`
}

// DefaultAppConfig returns a default configuration for the application
func DefaultAppConfig() *AppConfig {
return &AppConfig{
MinGasPrices: "",
}
}

// ValidateBasic performs basic validation, checking format and param bounds, etc., and
// returns an error if any check fails.
func (cfg *AppConfig) ValidateBasic() error {
if cfg.MinGasPrices == "" {
return nil
}
if _, err := std.ParseGasPrices(cfg.MinGasPrices); err != nil {
return errors.Wrap(err, "invalid min gas prices")
}

return nil
}
36 changes: 36 additions & 0 deletions tm2/pkg/sdk/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package config

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestValidateAppConfig(t *testing.T) {
c := DefaultAppConfig()
c.MinGasPrices = "" // empty

testCases := []struct {
testName string
minGasPrices string
expectErr bool
}{
{"invalid min gas prices invalid gas", "10token/1", true},
{"invalid min gas prices invalid gas denom", "9token/0gs", true},
{"invalid min gas prices zero gas", "10token/0gas", true},
{"invalid min gas prices no gas", "10token/gas", true},
{"invalid min gas prices negtive gas", "10token/-1gas", true},
{"invalid min gas prices invalid denom", "10$token/2gas", true},
{"invalid min gas prices invalid second denom", "10token/2gas;10/3gas", true},
{"valid min gas prices", "10foo/3gas;5bar/3gas", false},
}

cfg := DefaultAppConfig()
for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
cfg.MinGasPrices = tc.minGasPrices
assert.Equal(t, tc.expectErr, cfg.ValidateBasic() != nil)
})
}
}
6 changes: 4 additions & 2 deletions tm2/pkg/std/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ func ParseGasPrice(gasprice string) (GasPrice, error) {
if gas.Denom != "gas" {
return GasPrice{}, errors.New("invalid gas price: %s (invalid gas denom)", gasprice)
}
if gas.Amount == 0 {
return GasPrice{}, errors.New("invalid gas price: %s (gas can not be zero)", gasprice)

if gas.Amount <= 0 {
return GasPrice{}, errors.New("invalid gas price: %s (invalid gas amount)", gasprice)
}

return GasPrice{
Gas: gas.Amount,
Price: price,
Expand Down

0 comments on commit 27073ec

Please sign in to comment.