-
Notifications
You must be signed in to change notification settings - Fork 26
test: cover ante routing and upgrade handlers v5-v10 #128
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,51 +5,56 @@ import ( | |
|
|
||
| errorsmod "cosmossdk.io/errors" | ||
|
|
||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| errortypes "github.com/cosmos/cosmos-sdk/types/errors" | ||
| "github.com/cosmos/cosmos-sdk/x/auth/ante" | ||
| ) | ||
|
|
||
| type anteHandlerFactory func(sdk.Context, baseevmante.HandlerOptions) sdk.AnteHandler | ||
|
|
||
| // 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 baseevmante.HandlerOptions) sdk.AnteHandler { | ||
| return newAnteHandler(options, newCosmosAnteHandler, newMonoEVMAnteHandler) | ||
| } | ||
|
|
||
| func newAnteHandler( | ||
| options baseevmante.HandlerOptions, | ||
| cosmosHandler anteHandlerFactory, | ||
| evmHandler anteHandlerFactory, | ||
| ) sdk.AnteHandler { | ||
| return func( | ||
| ctx sdk.Context, tx sdk.Tx, sim bool, | ||
| ) (newCtx sdk.Context, err error) { | ||
| var anteHandler sdk.AnteHandler | ||
| if tx == nil { | ||
| return ctx, errorsmod.Wrap(errortypes.ErrUnknownRequest, "tx is nil") | ||
| } | ||
|
|
||
| txWithExtensions, ok := tx.(ante.HasExtensionOptionsTx) | ||
| if ok { | ||
| opts := txWithExtensions.GetExtensionOptions() | ||
| if len(opts) > 0 { | ||
| switch typeURL := opts[0].GetTypeUrl(); typeURL { | ||
| case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx": | ||
| // handle as *evmtypes.MsgEthereumTx | ||
| anteHandler = newMonoEVMAnteHandler(ctx, options) | ||
| case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx": | ||
| // cosmos-sdk tx with dynamic fee extension | ||
| anteHandler = newCosmosAnteHandler(ctx, options) | ||
| default: | ||
| return ctx, errorsmod.Wrapf( | ||
| errortypes.ErrUnknownExtensionOptions, | ||
| "rejecting tx with unsupported extension option: %s", typeURL, | ||
| ) | ||
| } | ||
|
|
||
| return anteHandler(ctx, tx, sim) | ||
| } | ||
| var opts []*codectypes.Any | ||
| if txExt, ok := tx.(ante.HasExtensionOptionsTx); ok { | ||
| opts = txExt.GetExtensionOptions() | ||
| } | ||
| if len(opts) == 0 { | ||
| return cosmosHandler(ctx, options)(ctx, tx, sim) | ||
| } | ||
|
|
||
| // handle as totally normal Cosmos SDK tx | ||
| switch tx.(type) { | ||
| case sdk.Tx: | ||
| anteHandler = newCosmosAnteHandler(ctx, options) | ||
| var anteHandler sdk.AnteHandler | ||
| switch typeURL := opts[0].GetTypeUrl(); typeURL { | ||
| case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx": | ||
| // handle as *evmtypes.MsgEthereumTx | ||
| anteHandler = evmHandler(ctx, options) | ||
| case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx": | ||
| // cosmos-sdk tx with dynamic fee extension | ||
| anteHandler = cosmosHandler(ctx, options) | ||
| default: | ||
| return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx) | ||
| return ctx, errorsmod.Wrapf( | ||
| errortypes.ErrUnknownExtensionOptions, | ||
| "rejecting tx with unsupported extension option: %s", typeURL, | ||
| ) | ||
|
Comment on lines
+45
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, a transaction can include multiple TxBody.extension_options. The TxBody protobuf message defines extension_options as a repeated google.protobuf.Any extension_options = 1023;, allowing zero or more elements. For validation of unknown options: extension_options (field 1023) are critical (bit 11 not set, as 1023 & 2048 == 0). Unknown fields in critical ranges, including unknown Any types packed into extension_options, cause the transaction to be rejected during decoding via the TxDecoder, which uses unknown field rejection. The comment in the proto states: "If any of these are present and can’t be handled, the transaction will be rejected". non_critical_extension_options (field 2047) are for options that can be safely ignored if unknown (bit 11 set). The query specifies extension_options, which are the critical ones requiring handling or rejection. This behavior is consistent in v0.53.x, as the proto definition remains the same from v0.50 to v0.53, and unknown field rejection is enforced in TxDecoder and ante handlers. Citations:
🏁 Script executed: # First, let's see the full function context around lines 45-56
cat -n app/ante/ante.go | head -100Repository: xrplevm/node Length of output: 2412 🏁 Script executed: # Also check if there are any guards or length checks before line 45
cat -n app/ante/ante.go | grep -A 50 "opts\[0\]" | head -60Repository: xrplevm/node Length of output: 709 🏁 Script executed: # Check test file for coverage patterns
cat -n app/ante/ante_test.go | head -150Repository: xrplevm/node Length of output: 4323 🏁 Script executed: # Search for how evmHandler and cosmosHandler handle extension options
rg -A 20 "func newMonoEVMAnteHandler|func newCosmosAnteHandler" app/ante/ante.goRepository: xrplevm/node Length of output: 38 🏁 Script executed: # Check if there's any validation of extension options in handlers
rg "GetExtensionOptions|extension_options" app/ante/ -A 3Repository: xrplevm/node Length of output: 424 🏁 Script executed: # Look for any nil pointer protection patterns in the codebase
rg "opts\[0\].*nil|== nil" app/ante/Repository: xrplevm/node Length of output: 90 🏁 Script executed: # Check what files exist in app/ante
ls -la app/ante/Repository: xrplevm/node Length of output: 418 🏁 Script executed: # Search for handler definitions in the entire codebase
find . -name "*.go" -type f -exec grep -l "newMonoEVMAnteHandler\|newCosmosAnteHandler" {} \;Repository: xrplevm/node Length of output: 130 🏁 Script executed: # Check if these handlers are external (imported from baseevmante)
rg "newMonoEVMAnteHandler|newCosmosAnteHandler" --type goRepository: xrplevm/node Length of output: 622 🏁 Script executed: # Check the evm_handler to see if it validates extension options
cat -n app/ante/evm_handler.goRepository: xrplevm/node Length of output: 1025 🏁 Script executed: # Check the cosmos_handler to see if it validates extension options
cat -n app/ante/cosmos_handler.goRepository: xrplevm/node Length of output: 2943 🏁 Script executed: # Check what ExtensionOptionsDecorator does - search for its usage pattern
rg "ExtensionOptionsDecorator" --type go -B 2 -A 5Repository: xrplevm/node Length of output: 630 🏁 Script executed: # Verify the imports to understand where ExtensionOptionChecker comes from
rg "ExtensionOptionChecker" --type go -B 2 -A 2Repository: xrplevm/node Length of output: 1034 🏁 Script executed: # Check what HasDynamicFeeExtensionOption does
rg "HasDynamicFeeExtensionOption" --type go -B 3 -A 10Repository: xrplevm/node Length of output: 1750 🏁 Script executed: # Look for the antetypes package
find . -name "*.go" -type go -exec grep -l "HasDynamicFeeExtensionOption" {} \;Repository: xrplevm/node Length of output: 91 🏁 Script executed: # Check the protobuf definition to understand if pointers in a slice can be nil after len check
rg "codectypes.Any" --type go | head -20Repository: xrplevm/node Length of output: 770 🏁 Script executed: # Check if there's a way opts[0] can be nil after len check
# Understand the structure better - is it possible for a pointer in a slice to be nil?
python3 << 'EOF'
# In Go, []*T is a slice of pointers. len() checks slice length, not pointer validity.
# Example: make([]*int, 1) creates a slice with one element that is nil by default
print("In Go, a slice like []*codectypes.Any after len(opts) == 0 check")
print("can still have nil pointers at individual indices.")
print("")
print("So opts[0] CAN be nil even when len(opts) > 0")
print("This would cause a panic on opts[0].GetTypeUrl()")
EOFRepository: xrplevm/node Length of output: 268 🏁 Script executed: # Check if there are actual scenarios with multiple extension options
rg "extension_options.*=" --type go -B 2 -A 2 | head -40Repository: xrplevm/node Length of output: 38 🏁 Script executed: # Final verification: check if EVM handler validates extension options via any decorator
cat -n app/ante/evm_handler.goRepository: xrplevm/node Length of output: 1025 Validate all extension options before routing and guard nil pointer dereference. On Line 45, Suggested fix var anteHandler sdk.AnteHandler
+ if opts[0] == nil {
+ return ctx, errorsmod.Wrap(
+ errortypes.ErrUnknownExtensionOptions,
+ "rejecting tx with nil extension option",
+ )
+ }
+
+ // Validate all extension options before routing.
+ for _, opt := range opts {
+ switch opt.GetTypeUrl() {
+ case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx", "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx":
+ // supported
+ default:
+ return ctx, errorsmod.Wrapf(
+ errortypes.ErrUnknownExtensionOptions,
+ "rejecting tx with unsupported extension option: %s", opt.GetTypeUrl(),
+ )
+ }
+ }
+
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx
anteHandler = evmHandler(ctx, options)
case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx":
// cosmos-sdk tx with dynamic fee extension
anteHandler = cosmosHandler(ctx, options)
- default:
- return ctx, errorsmod.Wrapf(
- errortypes.ErrUnknownExtensionOptions,
- "rejecting tx with unsupported extension option: %s", typeURL,
- )
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| return anteHandler(ctx, tx, sim) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| package ante | ||
|
|
||
| import ( | ||
| "errors" | ||
| "testing" | ||
|
|
||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| errortypes "github.com/cosmos/cosmos-sdk/types/errors" | ||
| authante "github.com/cosmos/cosmos-sdk/x/auth/ante" | ||
| baseevmante "github.com/cosmos/evm/ante" | ||
| antetypes "github.com/cosmos/evm/ante/types" | ||
| evmtypes "github.com/cosmos/evm/x/vm/types" | ||
| "github.com/stretchr/testify/require" | ||
| protov2 "google.golang.org/protobuf/proto" | ||
| ) | ||
|
|
||
| // Sentinel errors returned by the stub ante handlers to signal which branch | ||
| // the router selected, so tests can assert routing without a real ante chain. | ||
| var ( | ||
| errRoutedToCosmosAnte = errors.New("routed to cosmos ante") | ||
| errRoutedToEVMAnte = errors.New("routed to evm ante") | ||
| ) | ||
|
|
||
| type routingTx struct { | ||
| msgs []sdk.Msg | ||
| extensionOptions []*codectypes.Any | ||
| } | ||
|
|
||
| var ( | ||
| _ sdk.Tx = routingTx{} | ||
| _ authante.HasExtensionOptionsTx = routingTx{} | ||
| ) | ||
|
|
||
| func (tx routingTx) GetMsgs() []sdk.Msg { | ||
| return tx.msgs | ||
| } | ||
|
|
||
| func (tx routingTx) GetMsgsV2() ([]protov2.Message, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (tx routingTx) GetExtensionOptions() []*codectypes.Any { | ||
| return tx.extensionOptions | ||
| } | ||
|
|
||
| func (tx routingTx) GetNonCriticalExtensionOptions() []*codectypes.Any { | ||
| return nil | ||
| } | ||
|
|
||
| func stubAnteHandlerFactory(routeErr error) anteHandlerFactory { | ||
| return func(sdk.Context, baseevmante.HandlerOptions) sdk.AnteHandler { | ||
| return func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { | ||
| return ctx, routeErr | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestNewAnteHandlerRouting(t *testing.T) { | ||
| ethExtension, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) | ||
| require.NoError(t, err) | ||
| dynamicFeeExtension, err := codectypes.NewAnyWithValue(&antetypes.ExtensionOptionDynamicFeeTx{}) | ||
| require.NoError(t, err) | ||
|
|
||
| cases := []struct { | ||
| name string | ||
| tx sdk.Tx | ||
| wantErr error | ||
| wantErrContains string | ||
| }{ | ||
| { | ||
| name: "ethereum extension routes to evm ante", | ||
| tx: routingTx{ | ||
| msgs: []sdk.Msg{&evmtypes.MsgEthereumTx{}}, | ||
| extensionOptions: []*codectypes.Any{ethExtension}, | ||
| }, | ||
| wantErr: errRoutedToEVMAnte, | ||
| }, | ||
| { | ||
| name: "dynamic fee extension routes to cosmos ante", | ||
| tx: routingTx{extensionOptions: []*codectypes.Any{dynamicFeeExtension}}, | ||
| wantErr: errRoutedToCosmosAnte, | ||
| }, | ||
| { | ||
| name: "no extension routes to cosmos ante", | ||
| tx: routingTx{}, | ||
| wantErr: errRoutedToCosmosAnte, | ||
| }, | ||
| { | ||
| name: "unknown extension is rejected", | ||
| tx: routingTx{ | ||
| extensionOptions: []*codectypes.Any{{ | ||
| TypeUrl: "/cosmos.evm.test.v1.UnknownExtensionOption", | ||
| }}, | ||
| }, | ||
| wantErr: errortypes.ErrUnknownExtensionOptions, | ||
| wantErrContains: "rejecting tx with unsupported extension option: /cosmos.evm.test.v1.UnknownExtensionOption", | ||
| }, | ||
| { | ||
| name: "nil tx is rejected", | ||
| tx: nil, | ||
| wantErr: errortypes.ErrUnknownRequest, | ||
| wantErrContains: "tx is nil", | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| handler := newAnteHandler( | ||
| baseevmante.HandlerOptions{}, | ||
| stubAnteHandlerFactory(errRoutedToCosmosAnte), | ||
| stubAnteHandlerFactory(errRoutedToEVMAnte), | ||
| ) | ||
|
|
||
| _, err := handler(sdk.Context{}, tc.tx, false) | ||
|
|
||
| require.ErrorIs(t, err, tc.wantErr) | ||
| if tc.wantErrContains != "" { | ||
| require.ErrorContains(t, err, tc.wantErrContains) | ||
| } | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package v10 | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "cosmossdk.io/log" | ||
| upgradetypes "cosmossdk.io/x/upgrade/types" | ||
| cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
| "github.com/cosmos/cosmos-sdk/codec" | ||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| "github.com/cosmos/cosmos-sdk/types/module" | ||
| evmtypes "github.com/cosmos/evm/x/vm/types" | ||
| "github.com/stretchr/testify/require" | ||
| "google.golang.org/grpc" | ||
| ) | ||
|
|
||
| var _ EvmKeeper = (*mockEvmKeeper)(nil) | ||
|
|
||
| type mockEvmKeeper struct { | ||
| initErr error | ||
| initCalled bool | ||
| } | ||
|
|
||
| func (m *mockEvmKeeper) GetParams(sdk.Context) evmtypes.Params { return evmtypes.Params{} } | ||
| func (m *mockEvmKeeper) SetParams(sdk.Context, evmtypes.Params) error { return nil } | ||
| func (m *mockEvmKeeper) SetCodeHash(sdk.Context, []byte, []byte) {} | ||
| func (m *mockEvmKeeper) InitEvmCoinInfo(sdk.Context) error { | ||
| m.initCalled = true | ||
| return m.initErr | ||
| } | ||
|
|
||
| func TestCreateUpgradeHandlerInitializesEvmCoinInfo(t *testing.T) { | ||
| ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) | ||
| moduleManager := module.NewManager() | ||
| configurator := module.NewConfigurator( | ||
| codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), | ||
| grpc.NewServer(), | ||
| grpc.NewServer(), | ||
| ) | ||
| keeper := &mockEvmKeeper{} | ||
| versions := module.VersionMap{"bank": 1} | ||
|
|
||
| updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator, keeper)( | ||
| ctx, | ||
| upgradetypes.Plan{Name: UpgradeName}, | ||
| versions, | ||
| ) | ||
|
|
||
| require.NoError(t, err) | ||
| require.True(t, keeper.initCalled) | ||
| require.Empty(t, updatedVersions) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package v5 | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "cosmossdk.io/log" | ||
| upgradetypes "cosmossdk.io/x/upgrade/types" | ||
| cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
| "github.com/cosmos/cosmos-sdk/codec" | ||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| "github.com/cosmos/cosmos-sdk/types/module" | ||
| "github.com/stretchr/testify/require" | ||
| "google.golang.org/grpc" | ||
| ) | ||
|
|
||
| func TestCreateUpgradeHandlerCompletes(t *testing.T) { | ||
| ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) | ||
| moduleManager := module.NewManager() | ||
| configurator := module.NewConfigurator( | ||
| codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), | ||
| grpc.NewServer(), | ||
| grpc.NewServer(), | ||
| ) | ||
| versions := module.VersionMap{"bank": 1} | ||
|
|
||
| updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( | ||
| ctx, | ||
| upgradetypes.Plan{Name: UpgradeName}, | ||
| versions, | ||
| ) | ||
|
|
||
| require.NoError(t, err) | ||
| require.Empty(t, updatedVersions) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package v6 | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "cosmossdk.io/log" | ||
| upgradetypes "cosmossdk.io/x/upgrade/types" | ||
| cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
| "github.com/cosmos/cosmos-sdk/codec" | ||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| "github.com/cosmos/cosmos-sdk/types/module" | ||
| "github.com/stretchr/testify/require" | ||
| "google.golang.org/grpc" | ||
| ) | ||
|
|
||
| func TestCreateUpgradeHandlerCompletes(t *testing.T) { | ||
| ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) | ||
| moduleManager := module.NewManager() | ||
| configurator := module.NewConfigurator( | ||
| codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), | ||
| grpc.NewServer(), | ||
| grpc.NewServer(), | ||
| ) | ||
| versions := module.VersionMap{"bank": 1} | ||
|
|
||
| updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( | ||
| ctx, | ||
| upgradetypes.Plan{Name: UpgradeName}, | ||
| versions, | ||
| ) | ||
|
|
||
| require.NoError(t, err) | ||
| require.Empty(t, updatedVersions) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package v7 | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "cosmossdk.io/log" | ||
| upgradetypes "cosmossdk.io/x/upgrade/types" | ||
| cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
| "github.com/cosmos/cosmos-sdk/codec" | ||
| codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| "github.com/cosmos/cosmos-sdk/types/module" | ||
| "github.com/stretchr/testify/require" | ||
| "google.golang.org/grpc" | ||
| ) | ||
|
|
||
| func TestCreateUpgradeHandlerCompletes(t *testing.T) { | ||
| ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) | ||
| moduleManager := module.NewManager() | ||
| configurator := module.NewConfigurator( | ||
| codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), | ||
| grpc.NewServer(), | ||
| grpc.NewServer(), | ||
| ) | ||
| versions := module.VersionMap{"bank": 1} | ||
|
|
||
| updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( | ||
| ctx, | ||
| upgradetypes.Plan{Name: UpgradeName}, | ||
| versions, | ||
| ) | ||
|
|
||
| require.NoError(t, err) | ||
| require.Empty(t, updatedVersions) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not modify the ante handler since it is copy-pasted from cosmos/evm