diff --git a/types/errors/errors.go b/types/errors/errors.go index 759db2075d03..c59f6ba552eb 100644 --- a/types/errors/errors.go +++ b/types/errors/errors.go @@ -110,6 +110,9 @@ var ( // explicitly set timeout height. ErrTxTimeoutHeight = Register(RootCodespace, 30, "tx timeout height") + // ErrUnknownExtensionOptions defines an error for unknown extension options. + ErrUnknownExtensionOptions = Register(RootCodespace, 31, "unknown extension options") + // ErrPanic is only set when we recover from a panic, so we know to // redact potentially sensitive system info ErrPanic = Register(UndefinedCodespace, 111222, "panic") diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go index ee436ac7d426..c966d1c7eb75 100644 --- a/x/auth/ante/ante.go +++ b/x/auth/ante/ante.go @@ -16,6 +16,7 @@ func NewAnteHandler( ) sdk.AnteHandler { return sdk.ChainAnteDecorators( NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewRejectExtensionOptionsDecorator(), NewMempoolFeeDecorator(), NewValidateBasicDecorator(), TxTimeoutHeightDecorator{}, diff --git a/x/auth/ante/ext.go b/x/auth/ante/ext.go new file mode 100644 index 000000000000..362b8d32a971 --- /dev/null +++ b/x/auth/ante/ext.go @@ -0,0 +1,36 @@ +package ante + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type HasExtensionOptionsTx interface { + GetExtensionOptions() []*codectypes.Any + GetNonCriticalExtensionOptions() []*codectypes.Any +} + +// RejectExtensionOptionsDecorator is an AnteDecorator that rejects all extension +// options which can optionally be included in protobuf transactions. Users that +// need extension options should create a custom AnteHandler chain that handles +// needed extension options properly and rejects unknown ones. +type RejectExtensionOptionsDecorator struct{} + +// NewRejectExtensionOptionsDecorator creates a new RejectExtensionOptionsDecorator +func NewRejectExtensionOptionsDecorator() RejectExtensionOptionsDecorator { + return RejectExtensionOptionsDecorator{} +} + +var _ types.AnteDecorator = RejectExtensionOptionsDecorator{} + +// AnteHandle implements the AnteDecorator.AnteHandle method +func (r RejectExtensionOptionsDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) { + if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { + if len(hasExtOptsTx.GetExtensionOptions()) != 0 { + return ctx, sdkerrors.ErrUnknownExtensionOptions + } + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/ext_test.go b/x/auth/ante/ext_test.go new file mode 100644 index 000000000000..89ce6a7d649f --- /dev/null +++ b/x/auth/ante/ext_test.go @@ -0,0 +1,36 @@ +package ante_test + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +func (suite *AnteTestSuite) TestRejectExtensionOptionsDecorator() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + reod := ante.NewRejectExtensionOptionsDecorator() + antehandler := sdk.ChainAnteDecorators(reod) + + // no extension options should not trigger an error + theTx := suite.txBuilder.GetTx() + _, err := antehandler(suite.ctx, theTx, false) + suite.Require().NoError(err) + + extOptsTxBldr, ok := suite.txBuilder.(tx.ExtensionOptionsTxBuilder) + if !ok { + // if we can't set extension options, this decorator doesn't apply and we're done + return + } + + // setting any extension option should cause an error + any, err := types.NewAnyWithValue(testdata.NewTestMsg()) + suite.Require().NoError(err) + extOptsTxBldr.SetExtensionOptions(any) + theTx = suite.txBuilder.GetTx() + _, err = antehandler(suite.ctx, theTx, false) + suite.Require().EqualError(err, "unknown extension options") +} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 31763c1fcc76..26b273d05494 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -14,6 +14,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) @@ -38,10 +39,19 @@ type builder struct { } var ( - _ authsigning.Tx = &builder{} - _ client.TxBuilder = &builder{} + _ authsigning.Tx = &builder{} + _ client.TxBuilder = &builder{} + _ ante.HasExtensionOptionsTx = &builder{} + _ ExtensionOptionsTxBuilder = &builder{} ) +type ExtensionOptionsTxBuilder interface { + client.TxBuilder + + SetExtensionOptions(...*codectypes.Any) + SetNonCriticalExtensionOptions(...*codectypes.Any) +} + func newBuilder(pubkeyCodec types.PublicKeyCodec) *builder { return &builder{ tx: &tx.Tx{ @@ -391,3 +401,21 @@ func (t *builder) setSignatures(sigs [][]byte) { func (t *builder) GetTx() authsigning.Tx { return t } + +func (t *builder) GetExtensionOptions() []*codectypes.Any { + return t.tx.Body.ExtensionOptions +} + +func (t *builder) GetNonCriticalExtensionOptions() []*codectypes.Any { + return t.tx.Body.NonCriticalExtensionOptions +} + +func (t *builder) SetExtensionOptions(extOpts ...*codectypes.Any) { + t.tx.Body.ExtensionOptions = extOpts + t.bodyBz = nil +} + +func (t *builder) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { + t.tx.Body.NonCriticalExtensionOptions = extOpts + t.bodyBz = nil +} diff --git a/x/auth/tx/builder_test.go b/x/auth/tx/builder_test.go index a0dea1041a06..2205f4cb5f9c 100644 --- a/x/auth/tx/builder_test.go +++ b/x/auth/tx/builder_test.go @@ -109,6 +109,13 @@ func TestTxBuilder(t *testing.T) { require.Equal(t, 1, len(txBuilder.GetPubKeys())) require.Equal(t, legacy.Cdc.MustMarshalBinaryBare(pubkey), legacy.Cdc.MustMarshalBinaryBare(txBuilder.GetPubKeys()[0])) + any, err := codectypes.NewAnyWithValue(testdata.NewTestMsg()) + require.NoError(t, err) + txBuilder.SetExtensionOptions(any) + require.Equal(t, []*codectypes.Any{any}, txBuilder.GetExtensionOptions()) + txBuilder.SetNonCriticalExtensionOptions(any) + require.Equal(t, []*codectypes.Any{any}, txBuilder.GetNonCriticalExtensionOptions()) + txBuilder = &builder{} require.NotPanics(t, func() { _ = txBuilder.GetMsgs()