diff --git a/app/ante/ante.go b/app/ante/ante.go new file mode 100644 index 0000000..26bb8f7 --- /dev/null +++ b/app/ante/ante.go @@ -0,0 +1,105 @@ +package ante + +import ( + assettypes "planetmint-go/x/asset/types" + machinetypes "planetmint-go/x/machine/types" + + errorsmod "cosmossdk.io/errors" + 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" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/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, error) { + for _, msg := range tx.GetMsgs() { + switch sdk.MsgTypeURL(msg) { + case "/planetmintgo.asset.MsgNotarizeAsset": + notarizeMsg, ok := msg.(*assettypes.MsgNotarizeAsset) + if ok { + _, found := cm.mk.GetMachineIndex(ctx, notarizeMsg.PubKey) + if !found { + return ctx, errorsmod.Wrapf(machinetypes.ErrMachineNotFound, "error during CheckTx or ReCheckTx") + } + } + case "/planetmintgo.machine.MsgAttestMachine": + attestMsg, ok := msg.(*machinetypes.MsgAttestMachine) + if ok { + _, activated, found := cm.mk.GetTrustAnchor(ctx, attestMsg.Machine.MachineId) + if !found { + return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorNotFound, "error during CheckTx or ReCheckTx") + } + if activated { + return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorAlreadyInUse, "error during CheckTx or ReCheckTx") + } + } + default: + continue + } + } + + return next(ctx, tx, simulate) +} + +// 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 sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error + TxFeeChecker ante.TxFeeChecker + MachineKeeper MachineKeeper +} + +// 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") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + NewCheckMachineDecorator(options.MachineKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.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/expected_keepers.go b/app/ante/expected_keepers.go new file mode 100644 index 0000000..1fefa2f --- /dev/null +++ b/app/ante/expected_keepers.go @@ -0,0 +1,33 @@ +package ante + +import ( + "planetmint-go/x/machine/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type MachineKeeper interface { + GetMachineIndex(ctx sdk.Context, pubKey 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 +} diff --git a/app/app.go b/app/app.go index 9448fff..5cd68e9 100644 --- a/app/app.go +++ b/app/app.go @@ -123,6 +123,7 @@ import ( // this line is used by starport scaffolding # stargate/app/moduleImport + pmante "planetmint-go/app/ante" appparams "planetmint-go/app/params" "planetmint-go/docs" ) @@ -763,13 +764,14 @@ func New( app.MountMemoryStores(memKeys) // initialize BaseApp - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ + anteHandler, err := pmante.NewAnteHandler( + pmante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), FeegrantKeeper: app.FeeGrantKeeper, SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + MachineKeeper: app.MachineKeeper, }, ) if err != nil { diff --git a/tests/e2e/asset/rest.go b/tests/e2e/asset/rest.go index a937ab0..1599506 100644 --- a/tests/e2e/asset/rest.go +++ b/tests/e2e/asset/rest.go @@ -31,9 +31,10 @@ func (s *E2ETestSuite) TestNotarizeAssetREST() { cid, signatureHex := sample.Asset(sk) testCases := []struct { - name string - msg assettypes.MsgNotarizeAsset - rawLog string + name string + msg assettypes.MsgNotarizeAsset + rawLog string + expectCheckTxErr bool }{ { "machine not found", @@ -44,6 +45,7 @@ func (s *E2ETestSuite) TestNotarizeAssetREST() { PubKey: "human pubkey", }, "machine not found", + true, }, { "invalid signature hex string", @@ -64,6 +66,7 @@ func (s *E2ETestSuite) TestNotarizeAssetREST() { PubKey: xPubKey, }, "invalid signature", + false, }, { "valid notarization", @@ -74,6 +77,7 @@ func (s *E2ETestSuite) TestNotarizeAssetREST() { PubKey: xPubKey, }, "planetmintgo.asset.MsgNotarizeAsset", + false, }, } @@ -91,9 +95,13 @@ func (s *E2ETestSuite) TestNotarizeAssetREST() { tx, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, broadcastTxResponse.TxResponse.TxHash)) s.Require().NoError(err) - var txRes txtypes.GetTxResponse - err = val.ClientCtx.Codec.UnmarshalJSON(tx, &txRes) - s.Require().NoError(err) - s.Require().Contains(txRes.TxResponse.RawLog, tc.rawLog) + if !tc.expectCheckTxErr { + var txRes txtypes.GetTxResponse + err = val.ClientCtx.Codec.UnmarshalJSON(tx, &txRes) + s.Require().NoError(err) + s.Require().Contains(txRes.TxResponse.RawLog, tc.rawLog) + } else { + s.Require().Contains(broadcastTxResponse.TxResponse.RawLog, tc.rawLog) + } } } diff --git a/tests/e2e/asset/suite.go b/tests/e2e/asset/suite.go index 01ec9b3..bf435ab 100644 --- a/tests/e2e/asset/suite.go +++ b/tests/e2e/asset/suite.go @@ -134,9 +134,10 @@ func (s *E2ETestSuite) TestNotarizeAsset() { cid, signatureHex := sample.Asset(sk) testCases := []struct { - name string - args []string - rawLog string + name string + args []string + rawLog string + expectCheckTxErr bool }{ { "machine not found", @@ -149,6 +150,7 @@ func (s *E2ETestSuite) TestNotarizeAsset() { "--yes", }, "machine not found", + true, }, { "invalid signature hex string", @@ -173,6 +175,7 @@ func (s *E2ETestSuite) TestNotarizeAsset() { "--yes", }, "invalid signature", + false, }, { "valid notarization", @@ -185,6 +188,7 @@ func (s *E2ETestSuite) TestNotarizeAsset() { "--yes", }, "planetmintgo.asset.MsgNotarizeAsset", + false, }, } @@ -197,8 +201,12 @@ func (s *E2ETestSuite) TestNotarizeAsset() { s.Require().NoError(s.network.WaitForNextBlock()) rawLog, err := clitestutil.GetRawLogFromTxResponse(val, txResponse) - s.Require().NoError(err) - assert.Contains(s.T(), rawLog, tc.rawLog) + if !tc.expectCheckTxErr { + s.Require().NoError(err) + assert.Contains(s.T(), rawLog, tc.rawLog) + } else { + assert.Contains(s.T(), txResponse.RawLog, tc.rawLog) + } } } diff --git a/x/machine/types/errors.go b/x/machine/types/errors.go index dcec871..bd684f8 100644 --- a/x/machine/types/errors.go +++ b/x/machine/types/errors.go @@ -8,5 +8,7 @@ import ( // x/machine module sentinel errors var ( - ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") + ErrMachineNotFound = errorsmod.Register(ModuleName, 2, "machine not found") + ErrTrustAnchorNotFound = errorsmod.Register(ModuleName, 3, "trust anchor not found") + ErrTrustAnchorAlreadyInUse = errorsmod.Register(ModuleName, 4, "trust anchor already in use") )