Skip to content

Commit

Permalink
feat: dynamic gas price, keeper implementation (#2838)
Browse files Browse the repository at this point in the history
<!-- please provide a detailed description of the changes made in this
pull request. -->

# Context 

This PR is inspired by EIP-1559 and adjusts the gas price based on the
ratio of gas used in the last block compared to the target block gas.
The gas price is enforced globally across the network. However,
validators can still configure a minimum gas price (min-gas-price) to
reject low-fee transactions and prevent mempool spam. A higher gas price
will take precedence when configured.

Current implementation is an alternative to
[PR2544](#2544) and is based on the
feedbacks.

Here are the main differences:

- Dynamic gas prices are managed by a new auth.GasPriceKeeper, rather
than being saved in the block header.
- Gas price configurations have been moved from consensus parameters to
GnoGenesisState and are stored in a new parameter module.
- The parameters can be modified later through governance proposals,
making it easier to update these configurations without requiring a
chain upgrade.
- All implementations are on the application side, with no changes made
to the consensus layer.


# High level flow

Start a new node from genesis. The initial gas price and formula
parameters are saved in the genesis and loaded into the params keeper
and gas keeper.


![image](https://github.com/user-attachments/assets/6f7bbf56-5196-4ee2-9c77-c55331cbfde6)

When a node receives a new transaction, the application checks if the
user has provided sufficient fees for the transaction. It will reject
the transaction if it does not meet the gas price set by the network and
individual nodes.


![image](https://github.com/user-attachments/assets/c9123370-0f83-4ef9-a4e6-a09c6aad98c9)


The node processes the entire block during the proposal, voting, and
restart phases.
The GasPriceKeeper will calculate and update the gas price according to
the formula in the application’s EndBlock() function.


![image](https://github.com/user-attachments/assets/51d233be-318b-4f05-8a45-3157604657ea)


# Formular 


![image](https://github.com/user-attachments/assets/ba282aba-a145-46d3-80b8-dcc5787d2a0b)


The compressor is used to reduce the impact on price caused by sudden
changes in the gas used within a block

##

When the last gas used in a block is above the target gas, we increase
the gas price



![image](https://github.com/user-attachments/assets/bb31dcbe-aaab-4c1a-b96f-156dafef80fc)



##

When the last gas used in a block is below the target gas, we decrease
the gas price until it returns to the initial gas price in the block.


![image](https://github.com/user-attachments/assets/c200cd1a-d4f3-4b4d-9198-2af08ad657ab)

## Impact

The Cosmos SDK has an optional setting for a minimum gas price. Each
validator can configure their own values to only accept transactions
with a gas price that meets their setting in the mempool. When a user
submits a transaction on-chain, the gas price is calculated as gas-fee /
gas-wanted.

With the addition of the block gas price, a network-wide minimum gas
price is enforced for every validator. Users will need to provide a gas
price that meets the requirements set by both the validator and the
network.



<details><summary>Contributors' checklist...</summary>

- [X] Added new tests
- [X] Provided an example (e.g. screenshot) to aid review
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
  • Loading branch information
piux2 authored Dec 17, 2024
1 parent 9f181d1 commit 273fb27
Show file tree
Hide file tree
Showing 33 changed files with 1,465 additions and 150 deletions.
4 changes: 3 additions & 1 deletion contribs/gnodev/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/gnolang/gno/contribs/gnodev

go 1.22.0
go 1.22

toolchain go1.22.4

replace github.com/gnolang/gno => ../..

Expand Down
31 changes: 13 additions & 18 deletions contribs/gnodev/pkg/dev/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,9 @@ func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) {
initialState: cfg.InitialTxs,
currentStateIndex: len(cfg.InitialTxs),
}

// generate genesis state
genesis := gnoland.GnoGenesisState{
Balances: cfg.BalancesList,
Txs: append(pkgsTxs, cfg.InitialTxs...),
}
genesis := gnoland.DefaultGenState()
genesis.Balances = cfg.BalancesList
genesis.Txs = append(pkgsTxs, cfg.InitialTxs...)

if err := devnode.rebuildNode(ctx, genesis); err != nil {
return nil, fmt.Errorf("unable to initialize the node: %w", err)
Expand Down Expand Up @@ -288,10 +285,9 @@ func (n *Node) Reset(ctx context.Context) error {

// Append initialTxs
txs := append(pkgsTxs, n.initialState...)
genesis := gnoland.GnoGenesisState{
Balances: n.config.BalancesList,
Txs: txs,
}
genesis := gnoland.DefaultGenState()
genesis.Balances = n.config.BalancesList
genesis.Txs = txs

// Reset the node with the new genesis state.
err = n.rebuildNode(ctx, genesis)
Expand Down Expand Up @@ -413,10 +409,10 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error {
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}

return n.rebuildNode(ctx, gnoland.GnoGenesisState{
Balances: n.config.BalancesList, Txs: txs,
})
genesis := gnoland.DefaultGenState()
genesis.Balances = n.config.BalancesList
genesis.Txs = txs
return n.rebuildNode(ctx, genesis)
}

state, err := n.getBlockStoreState(ctx)
Expand All @@ -431,10 +427,9 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error {
}

// Create genesis with loaded pkgs + previous state
genesis := gnoland.GnoGenesisState{
Balances: n.config.BalancesList,
Txs: append(pkgsTxs, state...),
}
genesis := gnoland.DefaultGenState()
genesis.Balances = n.config.BalancesList
genesis.Txs = append(pkgsTxs, state...)

// Reset the node with the new genesis state.
err = n.rebuildNode(ctx, genesis)
Expand Down
16 changes: 8 additions & 8 deletions contribs/gnodev/pkg/dev/node_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,9 @@ func (n *Node) MoveBy(ctx context.Context, x int) error {
newState := n.state[:newIndex]

// Create genesis with loaded pkgs + previous state
genesis := gnoland.GnoGenesisState{
Balances: n.config.BalancesList,
Txs: append(pkgsTxs, newState...),
}
genesis := gnoland.DefaultGenState()
genesis.Balances = n.config.BalancesList
genesis.Txs = append(pkgsTxs, newState...)

// Reset the node with the new genesis state.
if err = n.rebuildNode(ctx, genesis); err != nil {
Expand Down Expand Up @@ -132,10 +131,11 @@ func (n *Node) ExportStateAsGenesis(ctx context.Context) (*bft.GenesisDoc, error

// Get current blockstore state
doc := *n.Node.GenesisDoc() // copy doc
doc.AppState = gnoland.GnoGenesisState{
Balances: n.config.BalancesList,
Txs: state,
}

genState := doc.AppState.(gnoland.GnoGenesisState)
genState.Balances = n.config.BalancesList
genState.Txs = state
doc.AppState = genState

return &doc, nil
}
8 changes: 4 additions & 4 deletions gno.land/cmd/gnoland/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,10 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro
genesisTxs = append(pkgsTxs, genesisTxs...)

// Construct genesis AppState.
gen.AppState = gnoland.GnoGenesisState{
Balances: balances,
Txs: genesisTxs,
}
defaultGenState := gnoland.DefaultGenState()
defaultGenState.Balances = balances
defaultGenState.Txs = genesisTxs
gen.AppState = defaultGenState

// Write genesis state
if err := gen.SaveAs(genesisFile); err != nil {
Expand Down
16 changes: 8 additions & 8 deletions gno.land/pkg/gnoclient/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestCallSingle_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -92,7 +92,7 @@ func TestCallMultiple_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestSendSingle_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestSendMultiple_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -290,7 +290,7 @@ func TestRunSingle_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -358,7 +358,7 @@ func TestRunMultiple_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2300000),
GasWanted: 23000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -451,7 +451,7 @@ func TestAddPackageSingle_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down Expand Up @@ -536,7 +536,7 @@ func TestAddPackageMultiple_Integration(t *testing.T) {

// Make Tx config
baseCfg := BaseTxCfg{
GasFee: ugnot.ValueString(10000),
GasFee: ugnot.ValueString(2100000),
GasWanted: 21000000,
AccountNumber: 0,
SequenceNumber: 0,
Expand Down
31 changes: 26 additions & 5 deletions gno.land/pkg/gnoland/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,18 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB)

// Construct keepers.
acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount)
bankKpr := bank.NewBankKeeper(acctKpr)
paramsKpr := params.NewParamsKeeper(mainKey, "vm")
acctKpr := auth.NewAccountKeeper(mainKey, paramsKpr, ProtoGnoAccount)
gpKpr := auth.NewGasPriceKeeper(mainKey)
bankKpr := bank.NewBankKeeper(acctKpr)

vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr)
vmk.Output = cfg.VMOutput

// Set InitChainer
icc := cfg.InitChainerConfig
icc.baseApp = baseApp
icc.acctKpr, icc.bankKpr, icc.vmKpr, icc.paramsKpr = acctKpr, bankKpr, vmk, paramsKpr
icc.acctKpr, icc.bankKpr, icc.vmKpr, icc.paramsKpr, icc.gpKpr = acctKpr, bankKpr, vmk, paramsKpr, gpKpr
baseApp.SetInitChainer(icc.InitChainer)

// Set AnteHandler
Expand All @@ -112,9 +114,11 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
func(ctx sdk.Context, tx std.Tx, simulate bool) (
newCtx sdk.Context, res sdk.Result, abort bool,
) {
// Add last gas price in the context
ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpKpr.LastGasPrice(ctx))

// Override auth params.
ctx = ctx.
WithValue(auth.AuthParamsContextKey{}, auth.DefaultParams())
ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx))
// Continue on with default auth ante handler.
newCtx, res, abort = authAnteHandler(ctx, tx, simulate)
return
Expand Down Expand Up @@ -145,6 +149,8 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
baseApp.SetEndBlocker(
EndBlocker(
c,
acctKpr,
gpKpr,
vmk,
baseApp,
),
Expand Down Expand Up @@ -236,6 +242,7 @@ type InitChainerConfig struct {
acctKpr auth.AccountKeeperI
bankKpr bank.BankKeeperI
paramsKpr params.ParamsKeeperI
gpKpr auth.GasPriceKeeperI
}

// InitChainer is the function that can be used as a [sdk.InitChainer].
Expand Down Expand Up @@ -293,6 +300,10 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci
if !ok {
return nil, fmt.Errorf("invalid AppState of type %T", appState)
}
cfg.acctKpr.InitGenesis(ctx, state.Auth)
params := cfg.acctKpr.GetParams(ctx)
ctx = ctx.WithValue(auth.AuthParamsContextKey{}, params)
auth.InitChainer(ctx, cfg.gpKpr.(auth.GasPriceKeeper), params.InitialGasPrice)

// Apply genesis balances.
for _, bal := range state.Balances {
Expand Down Expand Up @@ -370,13 +381,23 @@ type endBlockerApp interface {
// validator set changes
func EndBlocker(
collector *collector[validatorUpdate],
acctKpr auth.AccountKeeperI,
gpKpr auth.GasPriceKeeperI,
vmk vm.VMKeeperI,
app endBlockerApp,
) func(
ctx sdk.Context,
req abci.RequestEndBlock,
) abci.ResponseEndBlock {
return func(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock {
// set the auth params value in the ctx. The EndBlocker will use InitialGasPrice in
// the params to calculate the updated gas price.
if acctKpr != nil {
ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx))
}
if acctKpr != nil && gpKpr != nil {
auth.EndBlocker(ctx, gpKpr)
}
// Check if there was a valset change
if len(collector.getEvents()) == 0 {
// No valset updates
Expand Down
Loading

0 comments on commit 273fb27

Please sign in to comment.