From 1a144ee5de3a65f233728bafff18006d3c2b41c7 Mon Sep 17 00:00:00 2001 From: Lorenz Herzberger Date: Tue, 25 Jun 2024 13:12:39 +0200 Subject: [PATCH] wip: trying to add antehandler to e2e test setup, jump to last commit for working TestAttestMachine w/o ante handler in place Signed-off-by: Lorenz Herzberger --- app/ante/ante.go | 77 ++++++++++ app/ante/check_machine_decorator.go | 80 +++++++++++ app/ante/check_mint_address_decorator.go | 38 +++++ app/ante/check_validator_decorator.go | 64 +++++++++ app/ante/deduct_fee_decorator.go | 175 +++++++++++++++++++++++ app/ante/error.go | 6 + app/ante/expected_keepers.go | 51 +++++++ app/ante/gaskv_cost_decorator.go | 50 +++++++ app/ante/setup_context_decorator.go | 92 ++++++++++++ app/app_config.go | 6 +- tests/e2e/machine/cli_test.go | 57 +++++++- 11 files changed, 693 insertions(+), 3 deletions(-) create mode 100644 app/ante/ante.go create mode 100644 app/ante/check_machine_decorator.go create mode 100644 app/ante/check_mint_address_decorator.go create mode 100644 app/ante/check_validator_decorator.go create mode 100644 app/ante/deduct_fee_decorator.go create mode 100644 app/ante/error.go create mode 100644 app/ante/expected_keepers.go create mode 100644 app/ante/gaskv_cost_decorator.go create mode 100644 app/ante/setup_context_decorator.go diff --git a/app/ante/ante.go b/app/ante/ante.go new file mode 100644 index 0000000..1233925 --- /dev/null +++ b/app/ante/ante.go @@ -0,0 +1,77 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + ante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + // authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authsigning "cosmossdk.io/x/tx/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + AccountKeeper AccountKeeper + BankKeeper BankKeeper + ExtensionOptionChecker ante.ExtensionOptionChecker + FeegrantKeeper FeegrantKeeper + SignModeHandler authsigning.SignModeHandler + SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error + TxFeeChecker TxFeeChecker + MachineKeeper MachineKeeper + // DaoKeeper DaoKeeper + StakingKeeper StakingKeeper +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + if options.MachineKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "machine keeper is required for ante builder") + } + // 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{ + // NewSetUpContextDecorator(options.DaoKeeper), // outermost AnteDecorator. SetUpContext must be called first + NewSetUpContextDecorator(), + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + NewGasKVCostDecorator(options.StakingKeeper), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + NewCheckValidatorDecorator(options.StakingKeeper), + NewCheckMachineDecorator(options.MachineKeeper), + // NewCheckMintAddressDecorator(options.DaoKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/app/ante/check_machine_decorator.go b/app/ante/check_machine_decorator.go new file mode 100644 index 0000000..f31c84e --- /dev/null +++ b/app/ante/check_machine_decorator.go @@ -0,0 +1,80 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + // assettypes "github.com/planetmint/planetmint-go/x/asset/types" + // daotypes "github.com/planetmint/planetmint-go/x/dao/types" + machinetypes "github.com/planetmint/planetmint-go/x/machine/types" +) + +type CheckMachineDecorator struct { + mk MachineKeeper +} + +func NewCheckMachineDecorator(mk MachineKeeper) CheckMachineDecorator { + return CheckMachineDecorator{ + mk: mk, + } +} + +func (cm CheckMachineDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (_ sdk.Context, err error) { + for _, msg := range tx.GetMsgs() { + switch sdk.MsgTypeURL(msg) { + // case "/planetmintgo.asset.MsgNotarizeAsset": + // notarizeMsg, ok := msg.(*assettypes.MsgNotarizeAsset) + // if ok { + // ctx, err = cm.handleNotarizeAsset(ctx, notarizeMsg) + // } + case "/planetmintgo.machine.MsgAttestMachine": + attestMsg, ok := msg.(*machinetypes.MsgAttestMachine) + if ok { + ctx, err = cm.handleAttestMachine(ctx, attestMsg) + } + // case "planetmintgo.dao.MsgReportPoPResult": + // popMsg, ok := msg.(*daotypes.MsgReportPopResult) + // if ok { + // ctx, err = cm.handlePopResult(ctx, popMsg) + // } + default: + continue + } + } + + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +// func (cm CheckMachineDecorator) handleNotarizeAsset(ctx sdk.Context, notarizeMsg *assettypes.MsgNotarizeAsset) (sdk.Context, error) { +// _, found := cm.mk.GetMachineIndexByAddress(ctx, notarizeMsg.GetCreator()) +// if !found { +// return ctx, errorsmod.Wrapf(machinetypes.ErrMachineNotFound, ErrorAnteContext) +// } +// return ctx, nil +// } + +func (cm CheckMachineDecorator) handleAttestMachine(ctx sdk.Context, attestMsg *machinetypes.MsgAttestMachine) (sdk.Context, error) { + if attestMsg.GetCreator() != attestMsg.Machine.GetAddress() { + return ctx, errorsmod.Wrapf(machinetypes.ErrMachineIsNotCreator, ErrorAnteContext) + } + _, activated, found := cm.mk.GetTrustAnchor(ctx, attestMsg.Machine.MachineId) + if !found { + return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorNotFound, ErrorAnteContext) + } + if activated { + return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorAlreadyInUse, ErrorAnteContext) + } + return ctx, nil +} + +// func (cm CheckMachineDecorator) handlePopResult(ctx sdk.Context, popMsg *daotypes.MsgReportPopResult) (sdk.Context, error) { +// _, found := cm.mk.GetMachineIndexByAddress(ctx, popMsg.GetCreator()) +// if !found { +// return ctx, errorsmod.Wrapf(machinetypes.ErrMachineNotFound, ErrorAnteContext) +// } +// return ctx, nil +// } diff --git a/app/ante/check_mint_address_decorator.go b/app/ante/check_mint_address_decorator.go new file mode 100644 index 0000000..f95613c --- /dev/null +++ b/app/ante/check_mint_address_decorator.go @@ -0,0 +1,38 @@ +package ante + +// import ( +// errorsmod "cosmossdk.io/errors" +// sdk "github.com/cosmos/cosmos-sdk/types" +// daotypes "github.com/planetmint/planetmint-go/x/dao/types" +// ) + +// type CheckMintAddressDecorator struct { +// dk DaoKeeper +// } + +// func NewCheckMintAddressDecorator(dk DaoKeeper) CheckMintAddressDecorator { +// return CheckMintAddressDecorator{ +// dk: dk, +// } +// } + +// func (cmad CheckMintAddressDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { +// for _, msg := range tx.GetMsgs() { +// if sdk.MsgTypeURL(msg) != "/planetmintgo.dao.MsgMintToken" { +// continue +// } +// mintMsg, ok := msg.(*daotypes.MsgMintToken) +// if !ok { +// continue +// } +// if mintMsg.Creator != cmad.dk.GetMintAddress(ctx) { +// return ctx, errorsmod.Wrapf(daotypes.ErrInvalidMintAddress, "expected: %s; got: %s", cmad.dk.GetMintAddress(ctx), mintMsg.Creator) +// } +// _, found := cmad.dk.GetMintRequestByHash(ctx, mintMsg.GetMintRequest().GetLiquidTxHash()) +// if found { +// return ctx, errorsmod.Wrapf(daotypes.ErrAlreadyMinted, "liquid tx hash %s has already been minted", mintMsg.GetMintRequest().GetLiquidTxHash()) +// } +// } + +// return next(ctx, tx, simulate) +// } diff --git a/app/ante/check_validator_decorator.go b/app/ante/check_validator_decorator.go new file mode 100644 index 0000000..5414a0f --- /dev/null +++ b/app/ante/check_validator_decorator.go @@ -0,0 +1,64 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + // "github.com/planetmint/planetmint-go/x/dao/types" +) + +type CheckValidatorDecorator struct { + sk StakingKeeper +} + +func NewCheckValidatorDecorator(sk StakingKeeper) CheckValidatorDecorator { + return CheckValidatorDecorator{sk: sk} +} + +func (cv CheckValidatorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (_ sdk.Context, err error) { + if simulate || ctx.BlockHeight() == 0 { + return next(ctx, tx, simulate) + } + + for _, msg := range tx.GetMsgs() { + switch sdk.MsgTypeURL(msg) { + case "/planetmintgo.dao.MsgInitPop": + fallthrough + case "/planetmintgo.dao.MsgDistributionRequest": + fallthrough + case "/planetmintgo.dao.MsgDistributionResult": + fallthrough + case "/planetmintgo.dao.MsgReissueRDDLProposal": + fallthrough + case "/planetmintgo.dao.MsgReissueRDDLResult": + fallthrough + case "/planetmintgo.dao.MsgUpdateRedeemClaim": + fallthrough + case "/planetmintgo.machine.MsgNotarizeLiquidAsset": + fallthrough + case "/planetmintgo.machine.MsgRegisterTrustAnchor": + ctx, err = cv.handleMsg(ctx, msg) + default: + continue + } + } + + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +func (cv CheckValidatorDecorator) handleMsg(ctx sdk.Context, msg sdk.Msg) (_ sdk.Context, err error) { + signatureMsg, ok := msg.(sdk.Signature) + if !ok { + return ctx, errorsmod.Error{} + } + signer := signatureMsg.GetPubKey().Address() + _, found := cv.sk.GetValidator(ctx, sdk.ValAddress(signer)) + if !found { + return ctx, errorsmod.Error{} + // return ctx, errorsmod.Wrapf(types.ErrRestrictedMsg, ErrorAnteContext) + } + return ctx, nil +} diff --git a/app/ante/deduct_fee_decorator.go b/app/ante/deduct_fee_decorator.go new file mode 100644 index 0000000..e2fe4b7 --- /dev/null +++ b/app/ante/deduct_fee_decorator.go @@ -0,0 +1,175 @@ +package ante + +import ( + "bytes" + "fmt" + "slices" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// TxFeeChecker check if the provided fee is enough and returns the effective fee and tx priority, +// the effective fee should be deducted later, and the priority should be returned in abci response. +type TxFeeChecker func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, error) + +// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx. +// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error. +// Call next AnteHandler if fees successfully deducted. +// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator +type DeductFeeDecorator struct { + accountKeeper AccountKeeper + bankKeeper authtypes.BankKeeper + feegrantKeeper FeegrantKeeper + txFeeChecker TxFeeChecker +} + +func NewDeductFeeDecorator(ak AccountKeeper, bk authtypes.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) DeductFeeDecorator { + if tfc == nil { + tfc = checkTxFee + } + + return DeductFeeDecorator{ + accountKeeper: ak, + bankKeeper: bk, + feegrantKeeper: fk, + txFeeChecker: tfc, + } +} + +func checkTxFee(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, ErrorTxFeeTx) + } + + feeCoins := feeTx.GetFee() + + if !ctx.IsCheckTx() { + return feeCoins, nil + } + minGasPrices := ctx.MinGasPrices() + if minGasPrices.IsZero() { + return feeCoins, nil + } + feeDenoms := feeCoins.Denoms() + if len(feeDenoms) != 1 { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "fee must be exactly one coin; got: %s", feeDenoms) + } + + gasDenom := minGasPrices.GetDenomByIndex(0) + if slices.Contains(feeDenoms, gasDenom) { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "received wrong fee denom; got: %s required: %s", feeDenoms[0], gasDenom) + } + + requiredFees := sdk.Coins{sdk.NewInt64Coin(gasDenom, 1)} + + if !feeCoins.IsAnyGTE(requiredFees) { + return nil, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + + return feeCoins, nil +} + +func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, ErrorTxFeeTx) + } + + if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas") + } + + var ( + err error + ) + msgs := tx.GetMsgs() + if len(msgs) == 1 && sdk.MsgTypeURL(msgs[0]) == "/planetmintgo.machine.MsgAttestMachine" { + return next(ctx, tx, simulate) + } + + fee := feeTx.GetFee() + if !simulate { + fee, err = dfd.txFeeChecker(ctx, tx) + if err != nil { + return ctx, err + } + } + if err := dfd.checkDeductFee(ctx, tx, fee); err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error { + feeTx, ok := sdkTx.(sdk.FeeTx) + if !ok { + return errorsmod.Wrap(sdkerrors.ErrTxDecode, ErrorTxFeeTx) + } + + if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil { + return fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) + } + + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + deductFeesFrom := feePayer + + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dfd.feegrantKeeper == nil { + return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + } else if !bytes.Equal(feeGranter, feePayer) { + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs()) + if err != nil { + return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) + } + + // deduct the fees + if !fee.IsZero() { + err := dfd.deductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee) + if err != nil { + return err + } + } + + events := sdk.Events{ + sdk.NewEvent( + sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), + sdk.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()), + ), + } + ctx.EventManager().EmitEvents(events) + + return nil +} + +// DeductFees deducts fees from the given account. +func (dfd DeductFeeDecorator) deductFees(bankKeeper authtypes.BankKeeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins) error { + // check if exactly one fee is provided and is greater than 0 + if !fees.IsValid() && len(fees) == 1 { + return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees) + } + + err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), authtypes.FeeCollectorName, fees) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + } + + return nil +} diff --git a/app/ante/error.go b/app/ante/error.go new file mode 100644 index 0000000..d855dab --- /dev/null +++ b/app/ante/error.go @@ -0,0 +1,6 @@ +package ante + +var ( + ErrorAnteContext = "error during CheckTx or ReCheckTx" + ErrorTxFeeTx = "Tx must be a FeeTx" +) diff --git a/app/ante/expected_keepers.go b/app/ante/expected_keepers.go new file mode 100644 index 0000000..2f35c1d --- /dev/null +++ b/app/ante/expected_keepers.go @@ -0,0 +1,51 @@ +package ante + +import ( + "github.com/planetmint/planetmint-go/x/machine/types" + + 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" +) + +type MachineKeeper interface { + GetMachineIndexByPubKey(ctx sdk.Context, pubKey string) (val types.MachineIndex, found bool) + GetMachineIndexByAddress(ctx sdk.Context, address string) (val types.MachineIndex, found bool) + GetTrustAnchor(ctx sdk.Context, pubKey string) (val types.TrustAnchor, activated bool, found bool) +} + +// AccountKeeper defines the contract needed for AccountKeeper related APIs. +// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators. +type AccountKeeper interface { + GetParams(ctx sdk.Context) (params authtypes.Params) + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + SetAccount(ctx sdk.Context, acc authtypes.AccountI) + GetModuleAddress(moduleName string) sdk.AccAddress +} + +// FeegrantKeeper defines the expected feegrant keeper. +type FeegrantKeeper interface { + UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error +} + +type BankKeeper interface { + IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error + SendCoins(ctx sdk.Context, from, to sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin +} + +// type DaoKeeper interface { +// GetMintRequestByHash(ctx sdk.Context, hash string) (val daotypes.MintRequest, found bool) +// GetMintAddress(ctx sdk.Context) (mintAddress string) +// GetTxGasLimit(ctx sdk.Context) (txGasLimit uint64) +// GetClaimAddress(ctx sdk.Context) (claimAddress string) +// IsValidReissuanceProposal(ctx sdk.Context, msg *daotypes.MsgReissueRDDLProposal) (isValid bool) +// GetRedeemClaim(ctx sdk.Context, benficiary string, id uint64) (val daotypes.RedeemClaim, found bool) +// GetParams(ctx sdk.Context) (params daotypes.Params) +// } + +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..ea32ac3 --- /dev/null +++ b/app/ante/gaskv_cost_decorator.go @@ -0,0 +1,50 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + 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 { + return next(ctx, tx, simulate) + } + + msgs := tx.GetMsgs() + + signatureMsg, ok := msgs[0].(sdk.Signature) + if !ok { + return ctx, errorsmod.Error{} + } + signer := signatureMsg.GetPubKey().Address() + // signers := msgs[0].GetSigners() + // signer := signers[0] + + valAddr := sdk.ValAddress(signer) + _, found := gc.sk.GetValidator(ctx, valAddr) + + if !found { + return next(ctx, tx, simulate) + } + + ctx = ctx.WithKVGasConfig(storetypes.GasConfig{ + HasCost: 0, + DeleteCost: 0, + ReadCostFlat: 0, + ReadCostPerByte: 0, + WriteCostFlat: 0, + WriteCostPerByte: 0, + IterNextCostFlat: 0, + }) + + return next(ctx, tx, simulate) +} diff --git a/app/ante/setup_context_decorator.go b/app/ante/setup_context_decorator.go new file mode 100644 index 0000000..2d85b48 --- /dev/null +++ b/app/ante/setup_context_decorator.go @@ -0,0 +1,92 @@ +package ante + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// GasTx defines a Tx with a GetGas() method which is needed to use SetUpContextDecorator +type GasTx interface { + sdk.Tx + GetGas() uint64 +} + +// SetUpContextDecorator sets the GasMeter in the Context and wraps the next AnteHandler with a defer clause +// to recover from any downstream OutOfGas panics in the AnteHandler chain to return an error with information +// on gas provided and gas used. +// CONTRACT: Must be first decorator in the chain +// CONTRACT: Tx must implement GasTx interface +type SetUpContextDecorator struct { + // dk DaoKeeper +} + +// func NewSetUpContextDecorator(dk DaoKeeper) SetUpContextDecorator { +func NewSetUpContextDecorator() SetUpContextDecorator { + return SetUpContextDecorator{ + // dk: dk, + } +} + +func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // all transactions must implement GasTx + gasTx, ok := tx.(GasTx) + if !ok { + // Set a gas meter with limit 0 as to prevent an infinite gas meter attack + // during runTx. + newCtx = SetGasMeter(simulate, ctx, 0) + return newCtx, errorsmod.Wrapf(sdkerrors.ErrTxDecode, "Tx must be GasTx") + } + + // gasLimit := sud.dk.GetTxGasLimit(ctx) + // if gasLimit == 0 { + // gasLimit = gasTx.GetGas() + // } + gasLimit := gasTx.GetGas() + + newCtx = SetGasMeter(simulate, ctx, gasLimit) + + if cp := ctx.ConsensusParams(); cp != nil && cp.Block != nil { + // If there exists a maximum block gas limit, we must ensure that the tx + // does not exceed it. + if cp.Block.MaxGas > 0 && gasTx.GetGas() > uint64(cp.Block.MaxGas) { + return newCtx, errorsmod.Wrapf(sdkerrors.ErrInvalidGasLimit, "tx gas limit %d exceeds block max gas %d", gasTx.GetGas(), cp.Block.MaxGas) + } + } + + // Decorator will catch an OutOfGasPanic caused in the next antehandler + // AnteHandlers must have their own defer/recover in order for the BaseApp + // to know how much gas was used! This is because the GasMeter is created in + // the AnteHandler, but if it panics the context won't be set properly in + // runTx's recover call. + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case storetypes.ErrorOutOfGas: + log := fmt.Sprintf( + "out of gas in location: %v; gasWanted: %d, gasUsed: %d", + rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed()) + + err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } + } + }() + + return next(newCtx, tx, simulate) +} + +// SetGasMeter returns a new context with a gas meter set from a given context. +func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context { + // In various cases such as simulation and during the genesis block, we do not + // meter any gas utilization. + if simulate || ctx.BlockHeight() == 0 { + return ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) + } + + return ctx.WithGasMeter(storetypes.NewGasMeter(gasLimit)) +} diff --git a/app/app_config.go b/app/app_config.go index d97b073..6746204 100644 --- a/app/app_config.go +++ b/app/app_config.go @@ -241,8 +241,10 @@ var ( Config: appconfig.WrapAny(¶msmodulev1.Module{}), }, { - Name: "tx", - Config: appconfig.WrapAny(&txconfigv1.Config{}), + Name: "tx", + Config: appconfig.WrapAny(&txconfigv1.Config{ + SkipAnteHandler: true, + }), }, { Name: genutiltypes.ModuleName, diff --git a/tests/e2e/machine/cli_test.go b/tests/e2e/machine/cli_test.go index 6329cfe..59a4e17 100644 --- a/tests/e2e/machine/cli_test.go +++ b/tests/e2e/machine/cli_test.go @@ -3,17 +3,72 @@ package machine import ( "testing" + "cosmossdk.io/depinject" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/planetmint/planetmint-go/app" + "github.com/planetmint/planetmint-go/app/ante" "github.com/stretchr/testify/suite" + + pruningtypes "cosmossdk.io/store/pruning/types" + servertypes "github.com/cosmos/cosmos-sdk/server/types" +) + +var ( + cfg network.Config + appConfig depinject.Config ) func TestE2EMachineTestSuite(t *testing.T) { - cfg, err := network.DefaultConfigWithAppConfig(app.AppConfig()) + appConfig = app.AppConfig() + var err error + cfg, err = network.DefaultConfigWithAppConfig(appConfig) if err != nil { panic("error while setting up application config") } cfg.NumValidators = 3 cfg.MinGasPrices = "0.000003stake" + + cfg.AppConstructor = appConstructor + suite.Run(t, NewE2ETestSuite(cfg)) } + +func appConstructor(val network.ValidatorI) servertypes.Application { + // we build a unique app instance for every validator here + var appBuilder *runtime.AppBuilder + if err := depinject.Inject( + depinject.Configs( + appConfig, + depinject.Supply(val.GetCtx().Logger), + ), + &appBuilder); err != nil { + panic(err) + } + app := appBuilder.Build( + dbm.NewMemDB(), + nil, + baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), + baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices), + baseapp.SetChainID(cfg.ChainID), + ) + + testdata.RegisterQueryServer(app.GRPCQueryRouter(), testdata.QueryImpl{}) + + if err := app.Load(true); err != nil { + panic(err) + } + + anteOpts := ante.HandlerOptions{} + anteHandler, err := ante.NewAnteHandler(anteOpts) + if err != nil { + panic(err) + } + + app.SetAnteHandler(anteHandler) + + return app +}