From 4042968dff972b18c4452fe2b0480cf15bb7cc73 Mon Sep 17 00:00:00 2001 From: Lorenz Herzberger <64837895+LaurentMontBlanc@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:49:48 +0100 Subject: [PATCH] Add gaskv ante decorator (#268) * feature: add gaskv ante handler * test: add test case for gaskv consumption * chore: fix typo * test: split up consumption test cases * test: replace contains with equal * fix: linter error --------- Signed-off-by: Lorenz Herzberger --- app/ante/ante.go | 5 ++ app/ante/expected_keepers.go | 5 ++ app/ante/gaskv_cost_decorator.go | 38 +++++++++++ app/app.go | 1 + tests/e2e/dao/cli_test.go | 5 ++ tests/e2e/dao/gas_consumption_suite.go | 88 ++++++++++++++++++++++++++ 6 files changed, 142 insertions(+) create mode 100644 app/ante/gaskv_cost_decorator.go create mode 100644 tests/e2e/dao/gas_consumption_suite.go diff --git a/app/ante/ante.go b/app/ante/ante.go index 64feccb..f5b0b79 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -21,6 +21,7 @@ type HandlerOptions struct { TxFeeChecker TxFeeChecker MachineKeeper MachineKeeper DaoKeeper DaoKeeper + StakingKeeper StakingKeeper } // NewAnteHandler returns an AnteHandler that checks and increments sequence @@ -45,10 +46,14 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { if options.DaoKeeper == nil { return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "dao keeper is required for ante builder") } + if options.StakingKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "staking keeper is required for ante builder") + } anteDecorators := []sdk.AnteDecorator{ ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + NewGasKVCostDecorator(options.StakingKeeper), ante.NewValidateBasicDecorator(), ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), diff --git a/app/ante/expected_keepers.go b/app/ante/expected_keepers.go index 34a2856..420c8c4 100644 --- a/app/ante/expected_keepers.go +++ b/app/ante/expected_keepers.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" daotypes "github.com/planetmint/planetmint-go/x/dao/types" ) @@ -39,3 +40,7 @@ type DaoKeeper interface { GetMintAddress(ctx sdk.Context) (mintAddress string) IsValidReissuanceProposal(ctx sdk.Context, msg *daotypes.MsgReissueRDDLProposal) (isValid bool) } + +type StakingKeeper interface { + GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) +} diff --git a/app/ante/gaskv_cost_decorator.go b/app/ante/gaskv_cost_decorator.go new file mode 100644 index 0000000..4bb2869 --- /dev/null +++ b/app/ante/gaskv_cost_decorator.go @@ -0,0 +1,38 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type GasKVCostDecorator struct { + sk StakingKeeper +} + +func NewGasKVCostDecorator(sk StakingKeeper) GasKVCostDecorator { + return GasKVCostDecorator{sk: sk} +} + +func (gc GasKVCostDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (_ sdk.Context, err error) { + if !simulate && ctx.BlockHeight() != 0 { + msgs := tx.GetMsgs() + signers := msgs[0].GetSigners() + signer := signers[0] + + valAddr := sdk.ValAddress(signer) + _, found := gc.sk.GetValidator(ctx, valAddr) + + if found { + ctx = ctx.WithKVGasConfig(sdk.GasConfig{ + HasCost: 0, + DeleteCost: 0, + ReadCostFlat: 0, + ReadCostPerByte: 0, + WriteCostFlat: 0, + WriteCostPerByte: 0, + IterNextCostFlat: 0, + }) + } + } + + return next(ctx, tx, simulate) +} diff --git a/app/app.go b/app/app.go index 9973519..d092654 100644 --- a/app/app.go +++ b/app/app.go @@ -779,6 +779,7 @@ func New( SigGasConsumer: ante.DefaultSigVerificationGasConsumer, MachineKeeper: app.MachineKeeper, DaoKeeper: app.DaoKeeper, + StakingKeeper: app.StakingKeeper, }, ) if err != nil { diff --git a/tests/e2e/dao/cli_test.go b/tests/e2e/dao/cli_test.go index 3753fda..250a190 100644 --- a/tests/e2e/dao/cli_test.go +++ b/tests/e2e/dao/cli_test.go @@ -17,3 +17,8 @@ func TestPopE2ETestSuite(t *testing.T) { cfg := network.DefaultConfig() suite.Run(t, NewPopSelectionE2ETestSuite(cfg)) } + +func TestGasConsumptionE2ETestSuite(t *testing.T) { + cfg := network.DefaultConfig() + suite.Run(t, NewGasConsumptionE2ETestSuite(cfg)) +} diff --git a/tests/e2e/dao/gas_consumption_suite.go b/tests/e2e/dao/gas_consumption_suite.go new file mode 100644 index 0000000..50bc891 --- /dev/null +++ b/tests/e2e/dao/gas_consumption_suite.go @@ -0,0 +1,88 @@ +package dao + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/planetmint/planetmint-go/config" + "github.com/planetmint/planetmint-go/lib" + clitestutil "github.com/planetmint/planetmint-go/testutil/cli" + e2etestutil "github.com/planetmint/planetmint-go/testutil/e2e" + "github.com/planetmint/planetmint-go/testutil/network" + "github.com/planetmint/planetmint-go/testutil/sample" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type GasConsumptionE2ETestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func NewGasConsumptionE2ETestSuite(cfg network.Config) *GasConsumptionE2ETestSuite { + return &GasConsumptionE2ETestSuite{cfg: cfg} +} + +func (s *GasConsumptionE2ETestSuite) SetupSuite() { + s.T().Log("setting up e2e test suite") + conf := config.GetConfig() + conf.FeeDenom = "stake" + s.network = network.New(s.T(), s.cfg) + account, err := e2etestutil.CreateAccount(s.network, sample.Name, sample.Mnemonic) + s.Require().NoError(err) + err = e2etestutil.FundAccount(s.network, account) + s.Require().NoError(err) +} + +func (s *GasConsumptionE2ETestSuite) TearDownSuite() { + s.T().Log("tearing down e2e test suites") +} + +func (s *GasConsumptionE2ETestSuite) TestValidatorConsumption() { + val := s.network.Validators[0] + + k, err := val.ClientCtx.Keyring.Key(sample.Name) + s.Require().NoError(err) + addr, _ := k.GetAddress() + + // send huge tx but as val and with no gas kv costs + msgs := createMsgs(val.Address, addr, 10) + + out, err := lib.BroadcastTxWithFileLock(val.Address, msgs...) + s.Require().NoError(err) + + s.Require().NoError(s.network.WaitForNextBlock()) + + _, err = clitestutil.GetRawLogFromTxOut(val, out) + s.Require().NoError(err) +} + +func (s *GasConsumptionE2ETestSuite) TestNonValidatorConsumptionOverflow() { + val := s.network.Validators[0] + + k, err := val.ClientCtx.Keyring.Key(sample.Name) + s.Require().NoError(err) + addr, _ := k.GetAddress() + + // exceed gas limit with too many msgs as non validator + msgs := createMsgs(addr, val.Address, 10) + + out, err := lib.BroadcastTxWithFileLock(addr, msgs...) + s.Require().NoError(err) + + s.Require().NoError(s.network.WaitForNextBlock()) + + _, err = clitestutil.GetRawLogFromTxOut(val, out) + s.Require().Error(err) + assert.Equal(s.T(), err.Error(), "out of gas in location: Has; gasWanted: 200000, gasUsed: 200241: out of gas") +} + +func createMsgs(from sdk.AccAddress, to sdk.AccAddress, n int) (msgs []sdk.Msg) { + coins := sdk.NewCoins(sdk.NewInt64Coin("stake", 10)) + for i := 0; i < n; i++ { + msg := banktypes.NewMsgSend(from, to, coins) + msgs = append(msgs, msg) + } + return +}