Skip to content
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

Adding GetChannelsWithPortPrefix function to 04-channel (backport #2304) #2445

Merged
merged 4 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ 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.
* (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) [\#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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions modules/core/04-channel/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
83 changes: 83 additions & 0 deletions modules/core/04-channel/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions modules/core/04-channel/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}