diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3fb917e26..cf02604c063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,11 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (apps/27-interchain-accounts) [\#2146](https://github.com/cosmos/ibc-go/pull/2146) ICS27 controller now claims the channel capability passed via ibc core, and passes `nil` to the underlying app callback. The channel capability arg in `SendTx` is now ignored and looked up internally. * (apps/27-interchain-accounts) [\#2177](https://github.com/cosmos/ibc-go/pull/2177) Adding `IsMiddlewareEnabled` flag to interchain accounts `ActiveChannel` genesis type. * (apps/27-interchain-accounts) [\#2140](https://github.com/cosmos/ibc-go/pull/2140) Adding migration handler to ICS27 `controller` submodule to assert ownership of channel capabilities and set middleware enabled flag for existing channels. The ICS27 module consensus version has been bumped from 1 to 2. +<<<<<<< HEAD +======= +* (core/04-channel) [\#2304](https://github.com/cosmos/ibc-go/pull/2304) Adding `GetAllChannelsWithPortPrefix` function which filters channels based on a provided port prefix. +* (apps/27-interchain-accounts) [\#2290](https://github.com/cosmos/ibc-go/pull/2290) Changed `DefaultParams` function in `host` submodule to allow all messages by default. Defined a constant named `AllowAllHostMsgs` for `host` module to keep wildcard "*" string which allows all messages. +>>>>>>> ce5aba8 (Adding GetChannelsWithPortPrefix function to 04-channel (#2304)) * (apps/27-interchain-accounts) [\#2248](https://github.com/cosmos/ibc-go/pull/2248) Adding call to underlying app in `OnChanCloseConfirm` callback of the controller submodule and adding relevant unit tests. * (apps/27-interchain-accounts) [\#2251](https://github.com/cosmos/ibc-go/pull/2251) Adding `msgServer` struct to controller submodule that embeds the `Keeper` struct. * (apps/27-interchain-accounts) [\#2290](https://github.com/cosmos/ibc-go/pull/2290) Changed `DefaultParams` function in `host` submodule to allow all messages by default. Defined a constant named `AllowAllHostMsgs` for `host` module to keep wildcard "*" string which allows all messages. diff --git a/modules/apps/27-interchain-accounts/controller/keeper/migrations.go b/modules/apps/27-interchain-accounts/controller/keeper/migrations.go index 798a6ea389e..818c469f4ce 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/migrations.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/migrations.go @@ -5,7 +5,8 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - "github.com/cosmos/ibc-go/v6/modules/core/23-commitment/types" + controllertypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" ) @@ -23,19 +24,20 @@ func NewMigrator(keeper *Keeper) Migrator { // are owned by the controller submodule and ibc. func (m Migrator) AssertChannelCapabilityMigrations(ctx sdk.Context) error { if m.keeper != nil { - for _, ch := range m.keeper.GetAllActiveChannels(ctx) { + filteredChannels := m.keeper.channelKeeper.GetAllChannelsWithPortPrefix(ctx, icatypes.PortPrefix) + for _, ch := range filteredChannels { name := host.ChannelCapabilityPath(ch.PortId, ch.ChannelId) - capacity, found := m.keeper.scopedKeeper.GetCapability(ctx, name) + capability, found := m.keeper.scopedKeeper.GetCapability(ctx, name) if !found { return sdkerrors.Wrapf(capabilitytypes.ErrCapabilityNotFound, "failed to find capability: %s", name) } - isAuthenticated := m.keeper.scopedKeeper.AuthenticateCapability(ctx, capacity, name) + isAuthenticated := m.keeper.scopedKeeper.AuthenticateCapability(ctx, capability, name) if !isAuthenticated { - return sdkerrors.Wrapf(capabilitytypes.ErrCapabilityNotOwned, "expected capability owner: %s", types.SubModuleName) + return sdkerrors.Wrapf(capabilitytypes.ErrCapabilityNotOwned, "expected capability owner: %s", controllertypes.SubModuleName) } - m.keeper.SetMiddlewareEnabled(ctx, ch.PortId, ch.ConnectionId) + m.keeper.SetMiddlewareEnabled(ctx, ch.PortId, ch.ConnectionHops[0]) } } return nil diff --git a/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go b/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go index 0f2d99fd375..b2387e16a6e 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go @@ -1,7 +1,11 @@ package keeper_test import ( + "fmt" + "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/keeper" + icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" ibctesting "github.com/cosmos/ibc-go/v6/testing" ) @@ -16,15 +20,23 @@ func (suite *KeeperTestSuite) TestAssertChannelCapabilityMigrations() { func() {}, true, }, + { + "channel with different port is filtered out", + func() { + portIDWithOutPrefix := ibctesting.MockPort + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), portIDWithOutPrefix, ibctesting.FirstChannelID, channeltypes.Channel{ + ConnectionHops: []string{ibctesting.FirstConnectionID}, + }) + }, + true, + }, { "capability not found", func() { - suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID( - suite.chainA.GetContext(), - ibctesting.FirstConnectionID, - ibctesting.MockPort, - ibctesting.FirstChannelID, - ) + portIDWithPrefix := fmt.Sprintf("%s%s", icatypes.PortPrefix, "port-without-capability") + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), portIDWithPrefix, ibctesting.FirstChannelID, channeltypes.Channel{ + ConnectionHops: []string{ibctesting.FirstConnectionID}, + }) }, false, }, diff --git a/modules/apps/27-interchain-accounts/types/expected_keepers.go b/modules/apps/27-interchain-accounts/types/expected_keepers.go index 5af37a0f2ed..6d9aeb9b31f 100644 --- a/modules/apps/27-interchain-accounts/types/expected_keepers.go +++ b/modules/apps/27-interchain-accounts/types/expected_keepers.go @@ -29,6 +29,7 @@ type ChannelKeeper interface { GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) GetConnection(ctx sdk.Context, connectionID string) (ibcexported.ConnectionI, error) + GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel } // PortKeeper defines the expected IBC port keeper diff --git a/modules/core/04-channel/keeper/keeper.go b/modules/core/04-channel/keeper/keeper.go index cda779add42..26bf2c02587 100644 --- a/modules/core/04-channel/keeper/keeper.go +++ b/modules/core/04-channel/keeper/keeper.go @@ -384,6 +384,28 @@ func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.IdentifiedChannel } } +// GetAllChannelsWithPortPrefix returns all channels with the specified port prefix. If an empty prefix is provided +// all channels will be returned. +func (k Keeper) GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []types.IdentifiedChannel { + if strings.TrimSpace(portPrefix) == "" { + return k.GetAllChannels(ctx) + } + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.FilteredPortPrefix(portPrefix)) + defer iterator.Close() + + var filteredChannels []types.IdentifiedChannel + for ; iterator.Valid(); iterator.Next() { + var channel types.Channel + k.cdc.MustUnmarshal(iterator.Value(), &channel) + + portID, channelID := host.MustParseChannelPath(string(iterator.Key())) + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, channel) + filteredChannels = append(filteredChannels, identifiedChannel) + } + return filteredChannels +} + // GetAllChannels returns all stored Channel objects. func (k Keeper) GetAllChannels(ctx sdk.Context) (channels []types.IdentifiedChannel) { k.IterateChannels(ctx, func(channel types.IdentifiedChannel) bool { diff --git a/modules/core/04-channel/keeper/keeper_test.go b/modules/core/04-channel/keeper/keeper_test.go index 091f968e193..ac97d72e7c3 100644 --- a/modules/core/04-channel/keeper/keeper_test.go +++ b/modules/core/04-channel/keeper/keeper_test.go @@ -1,10 +1,12 @@ package keeper_test import ( + "reflect" "testing" "github.com/stretchr/testify/suite" + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" ibctesting "github.com/cosmos/ibc-go/v6/testing" ibcmock "github.com/cosmos/ibc-go/v6/testing/mock" @@ -81,6 +83,87 @@ func (suite *KeeperTestSuite) TestGetAppVersion() { suite.Require().Equal(ibcmock.Version, channelVersion) } +// TestGetAllChannelsWithPortPrefix verifies ports are filtered correctly using a port prefix. +func (suite *KeeperTestSuite) TestGetAllChannelsWithPortPrefix() { + + const ( + secondChannelID = "channel-1" + differentChannelPortID = "different-portid" + ) + + allChannels := []types.IdentifiedChannel{ + types.NewIdentifiedChannel(transfertypes.PortID, ibctesting.FirstChannelID, types.Channel{}), + types.NewIdentifiedChannel(differentChannelPortID, secondChannelID, types.Channel{}), + } + + tests := []struct { + name string + prefix string + allChannels []types.IdentifiedChannel + expectedChannels []types.IdentifiedChannel + }{ + { + name: "transfer channel is retrieved with prefix", + prefix: "tra", + allChannels: allChannels, + expectedChannels: []types.IdentifiedChannel{types.NewIdentifiedChannel(transfertypes.TypeMsgTransfer, ibctesting.FirstChannelID, types.Channel{})}, + }, + { + name: "matches port with full name as prefix", + prefix: transfertypes.TypeMsgTransfer, + allChannels: allChannels, + expectedChannels: []types.IdentifiedChannel{types.NewIdentifiedChannel(transfertypes.TypeMsgTransfer, ibctesting.FirstChannelID, types.Channel{})}, + }, + { + name: "no ports match prefix", + prefix: "wont-match-anything", + allChannels: allChannels, + expectedChannels: nil, + }, + { + name: "empty prefix matches everything", + prefix: "", + allChannels: allChannels, + expectedChannels: allChannels, + }, + } + + for _, tc := range tests { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + for _, ch := range tc.allChannels { + suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), ch.PortId, ch.ChannelId, types.Channel{}) + } + + ctxA := suite.chainA.GetContext() + + actualChannels := suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.GetAllChannelsWithPortPrefix(ctxA, tc.prefix) + + suite.Require().True(containsAll(tc.expectedChannels, actualChannels)) + }) + } +} + +// containsAll verifies if all elements in the expected slice exist in the actual slice +// independent of order. +func containsAll(expected, actual []types.IdentifiedChannel) bool { + for _, expectedChannel := range expected { + foundMatch := false + for _, actualChannel := range actual { + if reflect.DeepEqual(actualChannel, expectedChannel) { + foundMatch = true + break + } + } + if !foundMatch { + return false + } + } + return true +} + // TestGetAllChannels creates multiple channels on chain A through various connections // and tests their retrieval. 2 channels are on connA0 and 1 channel is on connA1 func (suite KeeperTestSuite) TestGetAllChannels() { diff --git a/modules/core/04-channel/types/keys.go b/modules/core/04-channel/types/keys.go index 33cfcac5758..c2135bce5da 100644 --- a/modules/core/04-channel/types/keys.go +++ b/modules/core/04-channel/types/keys.go @@ -60,3 +60,8 @@ func ParseChannelSequence(channelID string) (uint64, error) { return sequence, nil } + +// FilteredPortPrefix returns the prefix key for the given port prefix. +func FilteredPortPrefix(portPrefix string) []byte { + return []byte(fmt.Sprintf("%s/%s/%s", host.KeyChannelEndPrefix, host.KeyPortPrefix, portPrefix)) +}