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: in-protocol minimum gas price #107

Merged
merged 9 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### State Machine Breaking

* [#107](https://github.com/babylonlabs-io/babylon/pull/107) Implement ADR-027 and
enable in-protocol minimum gas price
* [#55](https://github.com/babylonlabs-io/babylon/pull/55) Remove `x/zoneconcierge`
module

### Bug Fixes

### Misc Improvements

## v0.10.1

### Bug Fixes
Expand Down
24 changes: 0 additions & 24 deletions app/ante.go

This file was deleted.

91 changes: 91 additions & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package ante

import (
"cosmossdk.io/core/store"
circuitkeeper "cosmossdk.io/x/circuit/keeper"
txsigning "cosmossdk.io/x/tx/signing"
wasmapp "github.com/CosmWasm/wasmd/app"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
bbn "github.com/babylonlabs-io/babylon/types"
btcckeeper "github.com/babylonlabs-io/babylon/x/btccheckpoint/keeper"
epochingkeeper "github.com/babylonlabs-io/babylon/x/epoching/keeper"
sdk "github.com/cosmos/cosmos-sdk/types"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper"
)

// NewAnteHandler creates a new AnteHandler for the Babylon chain.
func NewAnteHandler(
accountKeeper authante.AccountKeeper,
bankKeeper authtypes.BankKeeper,
feegrantKeeper authante.FeegrantKeeper,
signModeHandler *txsigning.HandlerMap,
ibcKeeper *ibckeeper.Keeper,
wasmConfig *wasmtypes.WasmConfig,
wasmKeeper *wasmkeeper.Keeper,
circuitKeeper *circuitkeeper.Keeper,
epochingKeeper *epochingkeeper.Keeper,
btcConfig *bbn.BtcConfig,
btccKeeper *btcckeeper.Keeper,
txCounterStoreService store.KVStoreService,
) sdk.AnteHandler {
// initialize AnteHandler, which includes
// - authAnteHandler
// - custom wasm ante handler NewLimitSimulationGasDecorator and NewCountTXDecorator
// - Extra decorators introduced in Babylon, such as DropValidatorMsgDecorator that delays validator-related messages
//
// We are using constructor from wasmapp as it introduces custom wasm ante handle decorators
// early in chain of ante handlers.
authAnteHandler, err := wasmapp.NewAnteHandler(
wasmapp.HandlerOptions{
HandlerOptions: authante.HandlerOptions{
AccountKeeper: accountKeeper,
BankKeeper: bankKeeper,
SignModeHandler: signModeHandler,
FeegrantKeeper: feegrantKeeper,
SigGasConsumer: authante.DefaultSigVerificationGasConsumer,
// CheckTxFeeWithGlobalMinGasPrices will enforce the global minimum
// gas price for all transactions.
TxFeeChecker: CheckTxFeeWithGlobalMinGasPrices,
Comment on lines +49 to +51
Copy link
Member Author

Choose a reason for hiding this comment

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

NOTE: this is the place where min gas price is enforced

},
IBCKeeper: ibcKeeper,
WasmConfig: wasmConfig,
TXCounterStoreService: txCounterStoreService,
WasmKeeper: wasmKeeper,
CircuitKeeper: circuitKeeper,
},
)

if err != nil {
panic(err)
}

anteHandler := sdk.ChainAnteDecorators(
NewWrappedAnteHandler(authAnteHandler),
epochingkeeper.NewDropValidatorMsgDecorator(epochingKeeper),
NewBtcValidationDecorator(btcConfig, btccKeeper),
)

return anteHandler
}

// WrappedAnteHandler is the wrapped AnteHandler that implements the `AnteDecorator` interface, which has a single function `AnteHandle`.
// It allows us to chain an existing AnteHandler with other decorators by using `sdk.ChainAnteDecorators`.
type WrappedAnteHandler struct {
ah sdk.AnteHandler
}

// NewWrappedAnteHandler creates a new WrappedAnteHandler for a given AnteHandler.
func NewWrappedAnteHandler(ah sdk.AnteHandler) WrappedAnteHandler {
return WrappedAnteHandler{ah}
}

func (wah WrappedAnteHandler) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
newCtx, err = wah.ah(ctx, tx, simulate)
if err != nil {
return newCtx, err
}
return next(newCtx, tx, simulate)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package app
package ante

import (
bbn "github.com/babylonlabs-io/babylon/types"
Expand All @@ -9,12 +9,12 @@ import (
)

type BtcValidationDecorator struct {
BtcCfg bbn.BtcConfig
BtcCfg *bbn.BtcConfig
btccheckpointKeeper *btccheckpointkeeper.Keeper
}

func NewBtcValidationDecorator(
cfg bbn.BtcConfig,
cfg *bbn.BtcConfig,
k *btccheckpointkeeper.Keeper,
) BtcValidationDecorator {
return BtcValidationDecorator{
Expand Down
66 changes: 66 additions & 0 deletions app/ante/fee_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ante

import (
"fmt"

errors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
appparams "github.com/babylonlabs-io/babylon/app/params"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerror "github.com/cosmos/cosmos-sdk/types/errors"
)

const (
// priorityScalingFactor is a scaling factor to convert the gas price to a priority.
priorityScalingFactor = 1_000_000
)

// CheckTxFeeWithGlobalMinGasPrices implements the default fee logic, where the minimum price per
// unit of gas is fixed and set globally, and the tx priority is computed from the gas price.
// adapted from https://github.com/celestiaorg/celestia-app/pull/2985
func CheckTxFeeWithGlobalMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, 0, errors.Wrap(sdkerror.ErrTxDecode, "Tx must be a FeeTx")
}

denom := appparams.DefaultBondDenom

fee := feeTx.GetFee().AmountOf(denom)
gas := feeTx.GetGas()

// convert the global minimum gas price to a big.Int
globalMinGasPrice, err := sdkmath.LegacyNewDecFromStr(fmt.Sprintf("%f", appparams.GlobalMinGasPrice))
if err != nil {
return nil, 0, errors.Wrap(err, "invalid GlobalMinGasPrice")
}

gasInt := sdkmath.NewIntFromUint64(gas)
minFee := globalMinGasPrice.MulInt(gasInt).RoundInt()

if !fee.GTE(minFee) {
return nil, 0, errors.Wrapf(sdkerror.ErrInsufficientFee, "insufficient fees; got: %s required: %s", fee, minFee)
}

priority := getTxPriority(feeTx.GetFee(), int64(gas))
return feeTx.GetFee(), priority, nil
}

// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price
// provided in a transaction.
// NOTE: This implementation should not be used for txs with multiple coins.
func getTxPriority(fee sdk.Coins, gas int64) int64 {
var priority int64
for _, c := range fee {
p := c.Amount.Mul(sdkmath.NewInt(priorityScalingFactor)).QuoRaw(gas)
if !p.IsInt64() {
continue
}
// take the lowest priority as the tx priority
if priority == 0 || p.Int64() < priority {
priority = p.Int64()
}
}

return priority
}
100 changes: 100 additions & 0 deletions app/ante/fee_checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ante_test

import (
"math"
"testing"

bbnapp "github.com/babylonlabs-io/babylon/app"
"github.com/babylonlabs-io/babylon/app/ante"
appparams "github.com/babylonlabs-io/babylon/app/params"
"github.com/babylonlabs-io/babylon/testutil/datagen"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/require"
)

// TestCheckTxFeeWithGlobalMinGasPrices tests the CheckTxFeeWithGlobalMinGasPrices
// function
// adapted from https://github.com/celestiaorg/celestia-app/pull/2985
func TestCheckTxFeeWithGlobalMinGasPrices(t *testing.T) {
encCfg := bbnapp.GetEncodingConfig()

builder := encCfg.TxConfig.NewTxBuilder()
err := builder.SetMsgs(
banktypes.NewMsgSend(
datagen.GenRandomAccount().GetAddress(),
datagen.GenRandomAccount().GetAddress(),
sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, 10)),
),
)
require.NoError(t, err)

feeAmount := int64(1000)
ctx := sdk.Context{}

testCases := []struct {
name string
fee sdk.Coins
gasLimit uint64
appVersion uint64
expErr bool
}{
{
name: "bad tx; fee below required minimum",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, feeAmount-1)),
gasLimit: uint64(float64(feeAmount) / appparams.GlobalMinGasPrice),
appVersion: uint64(2),
expErr: true,
},
{
name: "good tx; fee equal to required minimum",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, feeAmount)),
gasLimit: uint64(float64(feeAmount) / appparams.GlobalMinGasPrice),
appVersion: uint64(2),
expErr: false,
},
{
name: "good tx; fee above required minimum",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, feeAmount+1)),
gasLimit: uint64(float64(feeAmount) / appparams.GlobalMinGasPrice),
appVersion: uint64(2),
expErr: false,
},
{
name: "good tx; gas limit and fee are maximum values",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, math.MaxInt64)),
gasLimit: math.MaxUint64,
appVersion: uint64(2),
expErr: false,
},
{
name: "bad tx; gas limit and fee are 0",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, 0)),
gasLimit: 0,
appVersion: uint64(2),
expErr: false,
},
{
name: "good tx; minFee = 0.8, rounds up to 1",
fee: sdk.NewCoins(sdk.NewInt64Coin(appparams.DefaultBondDenom, feeAmount)),
gasLimit: 400,
appVersion: uint64(2),
expErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
builder.SetGasLimit(tc.gasLimit)
builder.SetFeeAmount(tc.fee)
tx := builder.GetTx()

_, _, err := ante.CheckTxFeeWithGlobalMinGasPrices(ctx, tx)
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
60 changes: 60 additions & 0 deletions app/ante/get_tx_priority_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ante

import (
"testing"

appparams "github.com/babylonlabs-io/babylon/app/params"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
)

// TestGetTxPriority tests the getTxPriority function
// adapted from https://github.com/celestiaorg/celestia-app/pull/2985
func TestGetTxPriority(t *testing.T) {
denom := appparams.DefaultBondDenom

cases := []struct {
name string
fee sdk.Coins
gas int64
expectedPri int64
}{
{
name: "1 bbn fee large gas",
fee: sdk.NewCoins(sdk.NewInt64Coin(denom, 1_000_000)),
gas: 1000000,
expectedPri: 1000000,
},
{
name: "1 ubbn fee small gas",
fee: sdk.NewCoins(sdk.NewInt64Coin(denom, 1)),
gas: 1,
expectedPri: 1000000,
},
{
name: "2 ubbn fee small gas",
fee: sdk.NewCoins(sdk.NewInt64Coin(denom, 2)),
gas: 1,
expectedPri: 2000000,
},
{
name: "1_000_000 bbn fee normal gas tx",
fee: sdk.NewCoins(sdk.NewInt64Coin(denom, 1_000_000_000_000)),
gas: 75000,
expectedPri: 13333333333333,
},
{
name: "0.001 ubbn gas price",
fee: sdk.NewCoins(sdk.NewInt64Coin(denom, 1_000)),
gas: 1_000_000,
expectedPri: 1000,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
pri := getTxPriority(tc.fee, tc.gas)
assert.Equal(t, tc.expectedPri, pri)
})
}
}
Loading
Loading