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

fix: exempt system txs from min gas check and fee deduction #1605

Merged
merged 19 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
94 changes: 66 additions & 28 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@ import (
"fmt"
"runtime/debug"

stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethante "github.com/evmos/ethermint/app/ante"
cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types"

tmlog "github.com/tendermint/tendermint/libs/log"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/authz"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
tmlog "github.com/tendermint/tendermint/libs/log"
cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types"
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
)

func ValidateHandlerOptions(options ethante.HandlerOptions) error {
func ValidateHandlerOptions(options HandlerOptions) error {
if options.AccountKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler")
}
Expand All @@ -47,14 +46,17 @@ func ValidateHandlerOptions(options ethante.HandlerOptions) error {
if options.EvmKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler")
}
if options.ObserverKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "observer keeper is required for AnteHandler")
}
return nil
}

// NewAnteHandler returns an ante handler responsible for attempting to route an
// Ethereum or SDK transaction to an internal ante handler for performing
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
func NewAnteHandler(options ethante.HandlerOptions) (sdk.AnteHandler, error) {
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if err := ValidateHandlerOptions(options); err != nil {
return nil, err
}
Expand Down Expand Up @@ -94,28 +96,26 @@ func NewAnteHandler(options ethante.HandlerOptions) (sdk.AnteHandler, error) {
// handle as totally normal Cosmos SDK tx
switch tx.(type) {
case sdk.Tx:
found := false
for _, msg := range tx.GetMsgs() {
switch msg.(type) {
// treat these three msg types differently because they might call EVM which results in massive gas consumption
// For these two msg types, we don't check gas limit by using a different ante handler
case *cctxtypes.MsgGasPriceVoter, *cctxtypes.MsgVoteOnObservedInboundTx:
found = true
break
case *stakingtypes.MsgCreateValidator:
if ctx.BlockHeight() == 0 {
found = true
break
}
}
// default: handle as normal Cosmos SDK tx
anteHandler = newCosmosAnteHandler(options)

// if tx is a system tx, and singer is authorized, use system tx handler

isAuthorized := func(creator string) bool {
return options.ObserverKeeper.IsAuthorized(ctx, creator)
}
if len(tx.GetMsgs()) == 1 && found {
// this differs newCosmosAnteHandler only in that it doesn't check gas limit
// by using an Infinite Gas Meter.
anteHandler = newCosmosAnteHandlerNoGasLimit(options)
} else {
anteHandler = newCosmosAnteHandler(options)
if IsSystemTx(tx, isAuthorized) {
anteHandler = newCosmosAnteHandlerForSystemTx(options)
}

// if tx is MsgCreatorValidator, use the newCosmosAnteHandlerForSystemTx handler to
// exempt gas fee requirement in genesis because it's not possible to pay gas fee in genesis
if len(tx.GetMsgs()) == 1 {
if _, ok := tx.GetMsgs()[0].(*stakingtypes.MsgCreateValidator); ok && ctx.BlockHeight() == 0 {
anteHandler = newCosmosAnteHandlerForSystemTx(options)
}
}

default:
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx)
}
Expand Down Expand Up @@ -145,3 +145,41 @@ func Recover(logger tmlog.Logger, err *error) {
}
}
}

// IsSystemTx determines whether tx is a system tx that's signed by an authorized signer
// system tx are special types of txs (see in the switch below), or such txs wrapped inside a MsgExec
// the parameter isAuthorizedSigner is a caller specified function that determines whether the signer of
// the tx is authorized.
func IsSystemTx(tx sdk.Tx, isAuthorizedSigner func(string) bool) bool {
lumtis marked this conversation as resolved.
Show resolved Hide resolved
// the following determines whether the tx is a system tx which will uses different handler
// System txs are always single Msg txs, optionally wrapped by one level of MsgExec
if len(tx.GetMsgs()) != 1 { // this is not a system tx
return false
}
msg := tx.GetMsgs()[0]

// if wrapped inside a MsgExec, unwrap it and reveal the innerMsg.
var innerMsg sdk.Msg
innerMsg = msg
if mm, ok := msg.(*authz.MsgExec); ok { // authz tx; look inside it
msgs, err := mm.GetMessages()
if err == nil && len(msgs) == 1 {
innerMsg = msgs[0]
}
}
switch innerMsg.(type) {
case *cctxtypes.MsgGasPriceVoter,
*cctxtypes.MsgVoteOnObservedInboundTx,
*cctxtypes.MsgVoteOnObservedOutboundTx,
*cctxtypes.MsgAddToOutTxTracker,
*cctxtypes.MsgCreateTSSVoter,
*observertypes.MsgAddBlockHeader,
*observertypes.MsgAddBlameVote:
signers := innerMsg.GetSigners()
if len(signers) == 1 {
return isAuthorizedSigner(signers[0].String())
}
}

return false
}
216 changes: 215 additions & 1 deletion app/ante/ante_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
package ante_test

import sdk "github.com/cosmos/cosmos-sdk/types"
import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/app"
"github.com/zeta-chain/zetacore/app/ante"
"github.com/zeta-chain/zetacore/testutil/sample"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
)

var _ sdk.AnteHandler = (&MockAnteHandler{}).AnteHandle

Expand All @@ -16,3 +29,204 @@ func (mah *MockAnteHandler) AnteHandle(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.C
mah.CalledCtx = ctx
return ctx, nil
}

func TestIsSystemTx(t *testing.T) {
// system tx types:
// *cctxtypes.MsgGasPriceVoter,
// *cctxtypes.MsgVoteOnObservedInboundTx,
// *cctxtypes.MsgVoteOnObservedOutboundTx,
// *cctxtypes.MsgAddToOutTxTracker,
// *cctxtypes.MsgCreateTSSVoter,
// *observertypes.MsgAddBlockHeader,
// *observertypes.MsgAddBlameVote:
buildTxFromMsg := func(msg sdk.Msg) sdk.Tx {
txBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder()
txBuilder.SetMsgs(msg)
return txBuilder.GetTx()
}
buildAuthzTxFromMsg := func(msg sdk.Msg) sdk.Tx {
txBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder()
msgExec := authz.NewMsgExec(sample.Bech32AccAddress(), []sdk.Msg{msg})
txBuilder.SetMsgs(&msgExec)
return txBuilder.GetTx()
}
isAuthorized := func(_ string) bool {
return true
}
isAuthorizedFalse := func(_ string) bool {
return false
}

tests := []struct {
name string
tx sdk.Tx
isAuthorized func(string) bool
wantIs bool
}{
{
"MsgCreateTSSVoter",
buildTxFromMsg(&crosschaintypes.MsgCreateTSSVoter{
Creator: sample.AccAddress(),
TssPubkey: "pubkey1234",
}),
isAuthorizedFalse,
false,
},
{
"MsgCreateTSSVoter",
buildTxFromMsg(&crosschaintypes.MsgCreateTSSVoter{
Creator: sample.AccAddress(),
TssPubkey: "pubkey1234",
}),
isAuthorized,
true,
},
{
"MsgExec{MsgCreateTSSVoter}",
buildAuthzTxFromMsg(&crosschaintypes.MsgCreateTSSVoter{
Creator: sample.AccAddress(),
TssPubkey: "pubkey1234",
}),
isAuthorized,

true,
},
{
"MsgSend",
buildTxFromMsg(&banktypes.MsgSend{}),
isAuthorized,

false,
},
{
"MsgExec{MsgSend}",
buildAuthzTxFromMsg(&banktypes.MsgSend{}),
isAuthorized,

false,
},
{
"MsgCreateValidator",
buildTxFromMsg(&stakingtypes.MsgCreateValidator{}),
isAuthorized,

false,
},

{
"MsgVoteOnObservedInboundTx",
buildTxFromMsg(&crosschaintypes.MsgVoteOnObservedInboundTx{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgVoteOnObservedInboundTx}",
buildAuthzTxFromMsg(&crosschaintypes.MsgVoteOnObservedInboundTx{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},

{
"MsgVoteOnObservedOutboundTx",
buildTxFromMsg(&crosschaintypes.MsgVoteOnObservedOutboundTx{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgVoteOnObservedOutboundTx}",
buildAuthzTxFromMsg(&crosschaintypes.MsgVoteOnObservedOutboundTx{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgAddToOutTxTracker",
buildTxFromMsg(&crosschaintypes.MsgAddToOutTxTracker{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgAddToOutTxTracker}",
buildAuthzTxFromMsg(&crosschaintypes.MsgAddToOutTxTracker{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgCreateTSSVoter",
buildTxFromMsg(&crosschaintypes.MsgCreateTSSVoter{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgCreateTSSVoter}",
buildAuthzTxFromMsg(&crosschaintypes.MsgCreateTSSVoter{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgAddBlockHeader",
buildTxFromMsg(&observertypes.MsgAddBlockHeader{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgAddBlockHeader}",
buildAuthzTxFromMsg(&observertypes.MsgAddBlockHeader{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgAddBlameVote",
buildTxFromMsg(&observertypes.MsgAddBlameVote{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
{
"MsgExec{MsgAddBlameVote}",
buildAuthzTxFromMsg(&observertypes.MsgAddBlameVote{
Creator: sample.AccAddress(),
}),
isAuthorized,

true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
is := ante.IsSystemTx(tt.tx, tt.isAuthorized)
require.Equal(t, tt.wantIs, is)
})
}
}
11 changes: 9 additions & 2 deletions app/ante/fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import (
evmtypes "github.com/evmos/ethermint/x/evm/types"
)

var (
GasPriceReductionRate = "0.01" // 1% of regular tx gas price for system txs
)

// MinGasPriceDecorator will check if the transaction's fee is at least as large
// as the MinGasPrices param. If fee is too low, decorator returns error and tx
// is rejected. This applies for both CheckTx and DeliverTx
Expand Down Expand Up @@ -92,13 +96,16 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
return next(ctx, tx, simulate)
}

// Short-circuit genesis txs gentx
// Short-circuit genesis txs gentx at block 0 (there is no way to pay fee in genesis file)
if len(tx.GetMsgs()) == 1 {
if _, ok := tx.GetMsgs()[0].(*stakingtypes.MsgCreateValidator); ok {
if _, ok := tx.GetMsgs()[0].(*stakingtypes.MsgCreateValidator); ok && ctx.BlockHeight() == 0 {
return next(ctx, tx, simulate)
}
}

reductionRate := sdk.MustNewDecFromStr(GasPriceReductionRate)
minGasPrice = minGasPrice.Mul(reductionRate) // discounts min gas price for system tx

evmParams := mpd.evmKeeper.GetParams(ctx)
evmDenom := evmParams.GetEvmDenom()
minGasPrices := sdk.DecCoins{
Expand Down
Loading
Loading