Skip to content

Commit

Permalink
prep ante handlers + migrate BlockHeader().Version.App to consensus…
Browse files Browse the repository at this point in the history
… keeper appversion
  • Loading branch information
julienrbrt committed Jan 15, 2025
1 parent 5b5d22c commit 3cc1a76
Show file tree
Hide file tree
Showing 33 changed files with 408 additions and 201 deletions.
45 changes: 25 additions & 20 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ante

import (
"context"

paramkeeper "cosmossdk.io/x/params/keeper"
"cosmossdk.io/x/tx/signing"
blobante "github.com/celestiaorg/celestia-app/v3/x/blob/ante"
Expand All @@ -13,9 +15,11 @@ import (
)

func NewAnteHandler(
accountKeeper ante.AccountKeeper,
authkeeper ante.AccountKeeper,
accountAbstractionKeeper ante.AccountAbstractionKeeper,
bankKeeper authtypes.BankKeeper,
blobKeeper blob.Keeper,
consensusKeeper ConsensusKeeper,
feegrantKeeper ante.FeegrantKeeper,
signModeHandler *signing.HandlerMap,
sigGasConsumer ante.SignatureVerificationGasConsumer,
Expand All @@ -31,41 +35,38 @@ func NewAnteHandler(
msgVersioningGateKeeper,
// Set up the context with a gas meter.
// Must be called before gas consumption occurs in any other decorator.
ante.NewSetUpContextDecorator(),
ante.NewSetUpContextDecorator(authkeeper.GetEnvironment(), consensusKeeper),
// Ensure the tx is not larger than the configured threshold.
NewMaxTxSizeDecorator(),
NewMaxTxSizeDecorator(consensusKeeper),
// Ensure the tx does not contain any extension options.
ante.NewExtensionOptionsDecorator(nil),
// Ensure the tx passes ValidateBasic.
ante.NewValidateBasicDecorator(),
ante.NewValidateBasicDecorator(authkeeper.GetEnvironment()),
// Ensure the tx has not reached a height timeout.
ante.NewTxTimeoutHeightDecorator(),
ante.NewTxTimeoutHeightDecorator(authkeeper.GetEnvironment()),
// Ensure the tx memo <= max memo characters.
ante.NewValidateMemoDecorator(accountKeeper),
ante.NewValidateMemoDecorator(authkeeper),
// Ensure the tx's gas limit is > the gas consumed based on the tx size.
// Side effect: consumes gas from the gas meter.
NewConsumeGasForTxSizeDecorator(accountKeeper),
NewConsumeGasForTxSizeDecorator(authkeeper, consensusKeeper),
// Ensure the feepayer (fee granter or first signer) has enough funds to pay for the tx.
// Ensure the gas price >= network min gas price if app version >= 2.
// Side effect: deducts fees from the fee payer. Sets the tx priority in context.
ante.NewDeductFeeDecorator(accountKeeper, bankKeeper, feegrantKeeper, ValidateTxFeeWrapper(paramKeeper)),
// Set public keys in the context for fee-payer and all signers.
// Contract: must be called before all signature verification decorators.
ante.NewSetPubKeyDecorator(accountKeeper),
ante.NewDeductFeeDecorator(authkeeper, bankKeeper, feegrantKeeper, ValidateTxFeeWrapper(paramKeeper, consensusKeeper)),
// Ensure that the tx's count of signatures is <= the tx signature limit.
ante.NewValidateSigCountDecorator(accountKeeper),
// Ensure that the tx's gas limit is > the gas consumed based on signature verification.
// Side effect: consumes gas from the gas meter.
ante.NewSigGasConsumeDecorator(accountKeeper, sigGasConsumer),
ante.NewValidateSigCountDecorator(authkeeper),
// Ensure that the tx's signatures are valid. For each signature, ensure
// that the signature's sequence number (a.k.a nonce) matches the
// account sequence number of the signer.
// Note: does not consume gas from the gas meter.
ante.NewSigVerificationDecorator(accountKeeper, signModeHandler),
// Ensure that the tx's gas limit is > the gas consumed based on signature verification.
// Set public keys in the context for fee-payer and all signers.
// Side effect: consumes gas from the gas meter.
// Side effect: increment the nonce for all tx signers.
ante.NewSigVerificationDecorator(authkeeper, signModeHandler, sigGasConsumer, accountAbstractionKeeper),
// Ensure that the tx's gas limit is > the gas consumed based on the blob size(s).
// Contract: must be called after all decorators that consume gas.
// Note: does not consume gas from the gas meter.
blobante.NewMinGasPFBDecorator(blobKeeper),
blobante.NewMinGasPFBDecorator(blobKeeper, consensusKeeper),
// Ensure that the tx's total blob size is <= the max blob size.
// Only applies to app version == 1.
blobante.NewMaxTotalBlobSizeDecorator(blobKeeper),

Check failure on line 72 in app/ante/ante.go

View workflow job for this annotation

GitHub Actions / test / test-fuzz

not enough arguments in call to blobante.NewMaxTotalBlobSizeDecorator
Expand All @@ -76,11 +77,15 @@ func NewAnteHandler(
// Ensure that tx's with a MsgSubmitProposal have at least one proposal
// message.
NewGovProposalDecorator(),
// Side effect: increment the nonce for all tx signers.
ante.NewIncrementSequenceDecorator(accountKeeper),
// Ensure that the tx is not an IBC packet or update message that has already been processed.
ibcante.NewRedundantRelayDecorator(channelKeeper),
)
}

var DefaultSigVerificationGasConsumer = ante.DefaultSigVerificationGasConsumer

// ConsensusKeeper is the expected interface of the consensus keeper
type ConsensusKeeper interface {
ante.ConsensusKeeper
AppVersion(context.Context) (uint64, error)
}
28 changes: 19 additions & 9 deletions app/ante/fee_checker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ante

import (
"context"

"cosmossdk.io/core/transaction"
errors "cosmossdk.io/errors"
"cosmossdk.io/math"
params "cosmossdk.io/x/params/keeper"
Expand All @@ -19,16 +22,18 @@ const (

// The purpose of this wrapper is to enable the passing of an additional paramKeeper parameter in
// ante.NewDeductFeeDecorator whilst still satisfying the ante.TxFeeChecker type.
func ValidateTxFeeWrapper(paramKeeper params.Keeper) ante.TxFeeChecker {
return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
return ValidateTxFee(ctx, tx, paramKeeper)
func ValidateTxFeeWrapper(paramKeeper params.Keeper, consensusKeeper ConsensusKeeper) ante.TxFeeChecker {
return func(ctx context.Context, tx transaction.Tx) (sdk.Coins, int64, error) {
return ValidateTxFee(ctx, tx, paramKeeper, consensusKeeper)
}
}

// ValidateTxFee implements default fee validation logic for transactions.
// It ensures that the provided transaction fee meets a minimum threshold for the node
// as well as a network minimum threshold and computes the tx priority based on the gas price.
func ValidateTxFee(ctx sdk.Context, tx sdk.Tx, paramKeeper params.Keeper) (sdk.Coins, int64, error) {
func ValidateTxFee(ctx context.Context, tx transaction.Tx, paramKeeper params.Keeper, consensusKeeper ConsensusKeeper) (sdk.Coins, int64, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)

feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, 0, errors.Wrap(sdkerror.ErrTxDecode, "Tx must be a FeeTx")
Expand All @@ -40,8 +45,8 @@ func ValidateTxFee(ctx sdk.Context, tx sdk.Tx, paramKeeper params.Keeper) (sdk.C
// Ensure that the provided fee meets a minimum threshold for the node.
// This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.IsCheckTx() {
minGasPrice := ctx.MinGasPrices().AmountOf(appconsts.BondDenom)
if sdkCtx.IsCheckTx() {
minGasPrice := sdkCtx.MinGasPrices().AmountOf(appconsts.BondDenom)
if !minGasPrice.IsZero() {
err := verifyMinFee(fee, gas, minGasPrice, "insufficient minimum gas price for this node")
if err != nil {
Expand All @@ -52,20 +57,25 @@ func ValidateTxFee(ctx sdk.Context, tx sdk.Tx, paramKeeper params.Keeper) (sdk.C

// Ensure that the provided fee meets a network minimum threshold.
// Network minimum fee only applies to app versions greater than one.
if ctx.BlockHeader().Version.App > v1.Version {
appVersion, err := consensusKeeper.AppVersion(sdkCtx)
if err != nil {
return nil, 0, errors.Wrap(sdkerror.ErrLogic, "failed to get app version")
}

if appVersion > v1.Version {
subspace, exists := paramKeeper.GetSubspace(minfee.ModuleName)
if !exists {
return nil, 0, errors.Wrap(sdkerror.ErrInvalidRequest, "minfee is not a registered subspace")
}

if !subspace.Has(ctx, minfee.KeyNetworkMinGasPrice) {
if !subspace.Has(sdkCtx, minfee.KeyNetworkMinGasPrice) {
return nil, 0, errors.Wrap(sdkerror.ErrKeyNotFound, "NetworkMinGasPrice")
}

var networkMinGasPrice math.LegacyDec
// Gets the network minimum gas price from the param store.
// Panics if not configured properly.
subspace.Get(ctx, minfee.KeyNetworkMinGasPrice, &networkMinGasPrice)
subspace.Get(sdkCtx, minfee.KeyNetworkMinGasPrice, &networkMinGasPrice)

err := verifyMinFee(fee, gas, networkMinGasPrice, "insufficient gas price for the network")
if err != nil {
Expand Down
32 changes: 29 additions & 3 deletions app/ante/gov.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,54 @@ package ante

import (
"cosmossdk.io/errors"
"cosmossdk.io/x/authz"
gov "cosmossdk.io/x/gov/types"
govv1 "cosmossdk.io/x/gov/types/v1"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// GovProposalDecorator ensures that a tx with a MsgSubmitProposal has at least
// one message in the proposal.
// GovProposalDecorator ensures that a tx with a MsgSubmitProposal has at least one message in the proposal.
// Additionally it replace the x/paramfilter module that existed in v3 and earlier versions.
type GovProposalDecorator struct{}

func NewGovProposalDecorator() GovProposalDecorator {
return GovProposalDecorator{}
}

// AnteHandle implements the AnteHandler interface. It ensures that MsgSubmitProposal has at least one message
// AnteHandle implements the AnteHandler interface.
// It ensures that MsgSubmitProposal has at least one message
// It ensures params are filtered within messages
func (d GovProposalDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
for _, m := range tx.GetMsgs() {
if proposal, ok := m.(*govv1.MsgSubmitProposal); ok {
if len(proposal.Messages) == 0 {
return ctx, errors.Wrapf(gov.ErrNoProposalMsgs, "must include at least one message in proposal")
}
}

// we need to check if a gov proposal wasn't contains in a authz message
if msgExec, ok := m.(*authz.MsgExec); ok {
for _, msg := range msgExec.Msgs {
_ = msg
}
}
}

return next(ctx, tx, simulate)
}

// TODO: To be moved to antehandler
// BlockedParams returns the params that require a hardfork to change, and
// cannot be changed via governance.
// func (app *App) BlockedParams() [][2]string {
// return [][2]string{
// // bank.SendEnabled
// {banktypes.ModuleName, string(banktypes.KeySendEnabled)},
// // staking.UnbondingTime
// {stakingtypes.ModuleName, string(stakingtypes.KeyUnbondingTime)},
// // staking.BondDenom
// {stakingtypes.ModuleName, string(stakingtypes.KeyBondDenom)},
// // consensus.validator.PubKeyTypes
// {baseapp.Paramspace, string(baseapp.ParamStoreKeyValidatorParams)},
// }
// }
7 changes: 4 additions & 3 deletions app/ante/gov_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ante_test
import (
"testing"

"cosmossdk.io/math"
banktypes "cosmossdk.io/x/bank/types"
govtypes "cosmossdk.io/x/gov/types/v1"
"github.com/celestiaorg/celestia-app/v3/app"
Expand All @@ -19,11 +20,11 @@ func TestGovDecorator(t *testing.T) {
decorator := ante.NewGovProposalDecorator()
anteHandler := types.ChainAnteDecorators(decorator)
accounts := testfactory.GenerateAccounts(1)
coins := types.NewCoins(types.NewCoin(appconsts.BondDenom, types.NewInt(10)))
coins := types.NewCoins(types.NewCoin(appconsts.BondDenom, math.NewInt(10)))

msgSend := banktypes.NewMsgSend(
testnode.RandomAddress().(types.AccAddress),
testnode.RandomAddress().(types.AccAddress),
testnode.RandomAddress().String(),
testnode.RandomAddress().String(),
coins,
)
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
Expand Down
21 changes: 16 additions & 5 deletions app/ante/max_tx_size.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,39 @@ package ante
import (
"fmt"

errors "cosmossdk.io/errors"
"github.com/celestiaorg/celestia-app/v3/pkg/appconsts"
v3 "github.com/celestiaorg/celestia-app/v3/pkg/appconsts/v3"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerror "github.com/cosmos/cosmos-sdk/types/errors"
)

// MaxTxSizeDecorator ensures that a tx can not be larger than
// application's configured versioned constant.
type MaxTxSizeDecorator struct{}
type MaxTxSizeDecorator struct {
consensusKeeper ConsensusKeeper
}

func NewMaxTxSizeDecorator() MaxTxSizeDecorator {
return MaxTxSizeDecorator{}
func NewMaxTxSizeDecorator(consensusKeeper ConsensusKeeper) MaxTxSizeDecorator {
return MaxTxSizeDecorator{
consensusKeeper: consensusKeeper,
}
}

// AnteHandle implements the AnteHandler interface. It ensures that tx size is under application's configured threshold.
func (d MaxTxSizeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
appVersion, err := d.consensusKeeper.AppVersion(ctx)
if err != nil {
return ctx, errors.Wrap(sdkerror.ErrLogic, "failed to get app version")
}

// Tx size rule applies to app versions v3 and onwards.
if ctx.BlockHeader().Version.App < v3.Version {
if appVersion < v3.Version {
return next(ctx, tx, simulate)
}

currentTxSize := len(ctx.TxBytes())
maxTxSize := appconsts.MaxTxSize(ctx.BlockHeader().Version.App)
maxTxSize := appconsts.MaxTxSize(appVersion)
if currentTxSize > maxTxSize {
bytesOverLimit := currentTxSize - maxTxSize
return ctx, fmt.Errorf("tx size %d bytes is larger than the application's configured threshold of %d bytes. Please reduce the size by %d bytes", currentTxSize, maxTxSize, bytesOverLimit)
Expand Down
5 changes: 2 additions & 3 deletions app/ante/max_tx_size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import (
)

func TestMaxTxSizeDecorator(t *testing.T) {
decorator := ante.NewMaxTxSizeDecorator()
anteHandler := sdk.ChainAnteDecorators(decorator)

testCases := []struct {
name string
txSize int
Expand Down Expand Up @@ -54,6 +51,8 @@ func TestMaxTxSizeDecorator(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, isCheckTx := range tc.isCheckTx {
decorator := ante.NewMaxTxSizeDecorator(mockConsensusKeeper{appVersion: tc.appVersion})
anteHandler := sdk.ChainAnteDecorators(decorator)

ctx := sdk.NewContext(nil, isCheckTx, nil)

Expand Down
2 changes: 1 addition & 1 deletion app/ante/min_fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestValidateTxFee(t *testing.T) {
subspace = minfee.RegisterMinFeeParamTable(subspace)
subspace.Set(ctx, minfee.KeyNetworkMinGasPrice, networkMinGasPriceDec)

_, _, err = ante.ValidateTxFee(ctx, tx, paramsKeeper)
_, _, err = ante.ValidateTxFee(ctx, tx, paramsKeeper, &mockConsensusKeeper{appVersion: tc.appVersion})
if tc.expErr {
require.Error(t, err)
} else {
Expand Down
31 changes: 21 additions & 10 deletions app/ante/msg_gatekeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,54 +18,65 @@ var (
type MsgVersioningGateKeeper struct {
// acceptedMsgs is a map from appVersion -> msgTypeURL -> struct{}.
// If a msgTypeURL is present in the map it should be accepted for that appVersion.
acceptedMsgs map[uint64]map[string]struct{}
acceptedMsgs map[uint64]map[string]struct{}
consensusKeeper ConsensusKeeper
}

func NewMsgVersioningGateKeeper(acceptedList map[uint64]map[string]struct{}) *MsgVersioningGateKeeper {
func NewMsgVersioningGateKeeper(acceptedList map[uint64]map[string]struct{}, consensusKeeper ConsensusKeeper) *MsgVersioningGateKeeper {
return &MsgVersioningGateKeeper{
acceptedMsgs: acceptedList,
acceptedMsgs: acceptedList,
consensusKeeper: consensusKeeper,
}
}

// AnteHandle implements the ante.Decorator interface
func (mgk MsgVersioningGateKeeper) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
acceptedMsgs, exists := mgk.acceptedMsgs[ctx.BlockHeader().Version.App]
appVersion, err := mgk.consensusKeeper.AppVersion(ctx)
if err != nil {
return ctx, err
}

acceptedMsgs, exists := mgk.acceptedMsgs[appVersion]
if !exists {
return ctx, sdkerrors.ErrNotSupported.Wrapf("app version %d is not supported", ctx.BlockHeader().Version.App)
return ctx, sdkerrors.ErrNotSupported.Wrapf("app version %d is not supported", appVersion)
}

if err := mgk.hasInvalidMsg(ctx, acceptedMsgs, tx.GetMsgs()); err != nil {
if err := mgk.hasInvalidMsg(ctx, acceptedMsgs, tx.GetMsgs(), appVersion); err != nil {
return ctx, err
}

return next(ctx, tx, simulate)
}

func (mgk MsgVersioningGateKeeper) hasInvalidMsg(ctx sdk.Context, acceptedMsgs map[string]struct{}, msgs []sdk.Msg) error {
func (mgk MsgVersioningGateKeeper) hasInvalidMsg(ctx sdk.Context, acceptedMsgs map[string]struct{}, msgs []sdk.Msg, appVersion uint64) error {
for _, msg := range msgs {
// Recursively check for invalid messages in nested authz messages.
if execMsg, ok := msg.(*authz.MsgExec); ok {
nestedMsgs, err := execMsg.GetMessages()
if err != nil {
return err
}
if err = mgk.hasInvalidMsg(ctx, acceptedMsgs, nestedMsgs); err != nil {
if err = mgk.hasInvalidMsg(ctx, acceptedMsgs, nestedMsgs, appVersion); err != nil {
return err
}
}

msgTypeURL := sdk.MsgTypeURL(msg)
_, exists := acceptedMsgs[msgTypeURL]
if !exists {
return sdkerrors.ErrNotSupported.Wrapf("message type %s is not supported in version %d", msgTypeURL, ctx.BlockHeader().Version.App)
return sdkerrors.ErrNotSupported.Wrapf("message type %s is not supported in version %d", msgTypeURL, appVersion)
}
}

return nil
}

func (mgk MsgVersioningGateKeeper) IsAllowed(ctx context.Context, msgName string) (bool, error) {
appVersion := sdk.UnwrapSDKContext(ctx).BlockHeader().Version.App
appVersion, err := mgk.consensusKeeper.AppVersion(ctx)
if err != nil {
return false, sdkerrors.ErrLogic.Wrap("failed to get app version")
}

acceptedMsgs, exists := mgk.acceptedMsgs[appVersion]
if !exists {
return false, sdkerrors.ErrNotSupported.Wrapf("app version %d is not supported", appVersion)
Expand Down
Loading

0 comments on commit 3cc1a76

Please sign in to comment.