-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: in-protocol minimum gas price (#107)
Resolves babylonlabs-io/pm#49 This PR introduces the in-protocol minimum gas price mechanism, in which the consensus (more specifically the AnteHandler) enforces every tx has to set a gas price at least 0.002 ubbn. The PR also provides relevant tests. The impl closely follows celestiaorg/celestia-app#2985. In addition, this PR creates a new package `app/ante` to abstract out the construction of the AnteHandler for Babylon, following the practice at Celestia. - RFC: babylonlabs-io/pm#56 - ADR: babylonlabs-io/pm#61
- Loading branch information
1 parent
c11bbb3
commit ae2e53e
Showing
18 changed files
with
380 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
Oops, something went wrong.