Merge pull request #1620 from CosmWasm/channel_query

Start rework channel query
This commit is contained in:
Alexander Peters
2023-09-14 15:09:52 +02:00
committed by GitHub
5 changed files with 213 additions and 148 deletions

View File

@@ -9,6 +9,7 @@ import (
wasmvmtypes "github.com/CosmWasm/wasmvm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types"
"github.com/cosmos/gogoproto/proto" "github.com/cosmos/gogoproto/proto"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -642,6 +643,194 @@ func TestDistributionQuery(t *testing.T) {
} }
} }
func TestIBCListChannelsQuery(t *testing.T) {
cdc := MakeEncodingConfig(t).Codec
pCtx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins()))
keeper := keepers.WasmKeeper
nonIbcExample := InstantiateReflectExampleContract(t, pCtx, keepers)
ibcExample := InstantiateReflectExampleContract(t, pCtx, keepers)
// add an ibc port for testing
myIBCPortID := "myValidPortID"
cInfo := keeper.GetContractInfo(pCtx, ibcExample.Contract)
cInfo.IBCPortID = myIBCPortID
keeper.storeContractInfo(pCtx, ibcExample.Contract, cInfo)
// store a random channel to be ignored in queries
unusedChan := channeltypes.Channel{
State: channeltypes.OPEN,
Ordering: channeltypes.UNORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "counterPartyPortID",
ChannelId: "counterPartyChannelID",
},
ConnectionHops: []string{"any"},
Version: "any",
}
keepers.IBCKeeper.ChannelKeeper.SetChannel(pCtx, "nonContractPortID", "channel-99", unusedChan)
// mixed channel examples for testing
myExampleChannels := []channeltypes.Channel{
{
State: channeltypes.OPEN,
Ordering: channeltypes.ORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "counterPartyPortID",
ChannelId: "counterPartyChannelID",
},
ConnectionHops: []string{"one"},
Version: "v1",
},
{
State: channeltypes.INIT,
Ordering: channeltypes.UNORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "foobar",
},
ConnectionHops: []string{"one"},
Version: "initversion",
},
{
State: channeltypes.OPEN,
Ordering: channeltypes.UNORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "otherCounterPartyPortID",
ChannelId: "otherCounterPartyChannelID",
},
ConnectionHops: []string{"other", "second"},
Version: "otherVersion",
},
{
State: channeltypes.CLOSED,
Ordering: channeltypes.ORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "super",
ChannelId: "duper",
},
ConnectionHops: []string{"no-more"},
Version: "closedVersion",
},
}
withChannelsStored := func(portID string, channels ...channeltypes.Channel) func(t *testing.T, ctx sdk.Context) sdk.Context {
return func(t *testing.T, ctx sdk.Context) sdk.Context {
for i, v := range channels {
keepers.IBCKeeper.ChannelKeeper.SetChannel(ctx, portID, fmt.Sprintf("channel-%d", i), v)
}
return ctx
}
}
noopSetup := func(t *testing.T, ctx sdk.Context) sdk.Context { return ctx }
specs := map[string]struct {
setup func(t *testing.T, ctx sdk.Context) sdk.Context
contract sdk.AccAddress
query *wasmvmtypes.IBCQuery
expErr bool
assert func(t *testing.T, d []byte)
}{
"open channels - with query portID empty": {
contract: ibcExample.Contract,
setup: withChannelsStored(myIBCPortID, myExampleChannels...),
query: &wasmvmtypes.IBCQuery{ListChannels: &wasmvmtypes.ListChannelsQuery{}},
assert: func(t *testing.T, d []byte) {
rsp := unmarshalReflect[wasmvmtypes.ListChannelsResponse](t, d)
exp := wasmvmtypes.ListChannelsResponse{Channels: []wasmvmtypes.IBCChannel{
{
Endpoint: wasmvmtypes.IBCEndpoint{PortID: myIBCPortID, ChannelID: "channel-0"},
CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{
PortID: "counterPartyPortID",
ChannelID: "counterPartyChannelID",
},
Order: channeltypes.ORDERED.String(),
Version: "v1",
ConnectionID: "one",
}, {
Endpoint: wasmvmtypes.IBCEndpoint{PortID: myIBCPortID, ChannelID: "channel-2"},
CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{
PortID: "otherCounterPartyPortID",
ChannelID: "otherCounterPartyChannelID",
},
Order: channeltypes.UNORDERED.String(),
Version: "otherVersion",
ConnectionID: "other",
},
}}
assert.Equal(t, exp, rsp)
},
},
"open channels - with query portID passed": {
contract: ibcExample.Contract,
setup: withChannelsStored("OtherPortID", myExampleChannels...),
query: &wasmvmtypes.IBCQuery{ListChannels: &wasmvmtypes.ListChannelsQuery{PortID: "OtherPortID"}},
assert: func(t *testing.T, d []byte) {
rsp := unmarshalReflect[wasmvmtypes.ListChannelsResponse](t, d)
exp := wasmvmtypes.ListChannelsResponse{Channels: []wasmvmtypes.IBCChannel{
{
Endpoint: wasmvmtypes.IBCEndpoint{PortID: "OtherPortID", ChannelID: "channel-0"},
CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{
PortID: "counterPartyPortID",
ChannelID: "counterPartyChannelID",
},
Order: channeltypes.ORDERED.String(),
Version: "v1",
ConnectionID: "one",
}, {
Endpoint: wasmvmtypes.IBCEndpoint{PortID: "OtherPortID", ChannelID: "channel-2"},
CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{
PortID: "otherCounterPartyPortID",
ChannelID: "otherCounterPartyChannelID",
},
Order: channeltypes.UNORDERED.String(),
Version: "otherVersion",
ConnectionID: "other",
},
}}
assert.Equal(t, exp, rsp)
},
},
"non ibc contract - with query portID empty": {
contract: nonIbcExample.Contract,
setup: withChannelsStored(myIBCPortID, myExampleChannels...),
query: &wasmvmtypes.IBCQuery{ListChannels: &wasmvmtypes.ListChannelsQuery{}},
assert: func(t *testing.T, d []byte) {
rsp := unmarshalReflect[wasmvmtypes.ListChannelsResponse](t, d)
assert.Nil(t, rsp.Channels)
},
},
"no matching channels": {
contract: ibcExample.Contract,
setup: noopSetup,
query: &wasmvmtypes.IBCQuery{ListChannels: &wasmvmtypes.ListChannelsQuery{}},
assert: func(t *testing.T, d []byte) {
rsp := unmarshalReflect[wasmvmtypes.ListChannelsResponse](t, d)
assert.Empty(t, rsp.Channels)
},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := pCtx.CacheContext()
ctx = spec.setup(t, ctx)
// when
queryBz := mustMarshal(t, testdata.ReflectQueryMsg{
Chain: &testdata.ChainQuery{
Request: &wasmvmtypes.QueryRequest{IBC: spec.query},
},
})
simpleRes, gotErr := keeper.QuerySmart(ctx, spec.contract, queryBz)
if spec.expErr {
require.Error(t, gotErr)
return
}
// then
require.NoError(t, gotErr)
var rsp testdata.ChainResponse
mustUnmarshal(t, simpleRes, &rsp)
spec.assert(t, rsp.Data)
})
}
}
func unmarshalReflect[T any](t *testing.T, d []byte) T { func unmarshalReflect[T any](t *testing.T, d []byte) T {
var v T var v T
mustUnmarshal(t, d, &v) mustUnmarshal(t, d, &v)

View File

@@ -249,11 +249,18 @@ func IBCQuerier(wasm contractMetaDataSource, channelKeeper types.ChannelKeeper)
} }
if request.ListChannels != nil { if request.ListChannels != nil {
portID := request.ListChannels.PortID portID := request.ListChannels.PortID
channels := make(wasmvmtypes.IBCChannels, 0) if portID == "" { // then fallback to contract port address
channelKeeper.IterateChannels(ctx, func(ch channeltypes.IdentifiedChannel) bool { portID = wasm.GetContractInfo(ctx, caller).IBCPortID
// it must match the port and be in open state }
if (portID == "" || portID == ch.PortId) && ch.State == channeltypes.OPEN { var channels wasmvmtypes.IBCChannels
newChan := wasmvmtypes.IBCChannel{ if portID != "" { // then return empty list for non ibc contracts; no channels possible
gotChannels := channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID)
channels = make(wasmvmtypes.IBCChannels, 0, len(gotChannels))
for _, ch := range gotChannels {
if ch.State != channeltypes.OPEN {
continue
}
channels = append(channels, wasmvmtypes.IBCChannel{
Endpoint: wasmvmtypes.IBCEndpoint{ Endpoint: wasmvmtypes.IBCEndpoint{
PortID: ch.PortId, PortID: ch.PortId,
ChannelID: ch.ChannelId, ChannelID: ch.ChannelId,
@@ -265,11 +272,9 @@ func IBCQuerier(wasm contractMetaDataSource, channelKeeper types.ChannelKeeper)
Order: ch.Ordering.String(), Order: ch.Ordering.String(),
Version: ch.Version, Version: ch.Version,
ConnectionID: ch.ConnectionHops[0], ConnectionID: ch.ConnectionHops[0],
} })
channels = append(channels, newChan)
} }
return false }
})
res := wasmvmtypes.ListChannelsResponse{ res := wasmvmtypes.ListChannelsResponse{
Channels: channels, Channels: channels,
} }

View File

@@ -40,59 +40,6 @@ import (
) )
func TestIBCQuerier(t *testing.T) { func TestIBCQuerier(t *testing.T) {
myExampleChannels := []channeltypes.IdentifiedChannel{
// this is returned
{
State: channeltypes.OPEN,
Ordering: channeltypes.ORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "counterPartyPortID",
ChannelId: "counterPartyChannelID",
},
ConnectionHops: []string{"one"},
Version: "v1",
PortId: "myPortID",
ChannelId: "myChannelID",
},
// this is filtered out
{
State: channeltypes.INIT,
Ordering: channeltypes.UNORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "foobar",
},
ConnectionHops: []string{"one"},
Version: "initversion",
PortId: "initPortID",
ChannelId: "initChannelID",
},
// this is returned
{
State: channeltypes.OPEN,
Ordering: channeltypes.UNORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "otherCounterPartyPortID",
ChannelId: "otherCounterPartyChannelID",
},
ConnectionHops: []string{"other", "second"},
Version: "otherVersion",
PortId: "otherPortID",
ChannelId: "otherChannelID",
},
// this is filtered out
{
State: channeltypes.CLOSED,
Ordering: channeltypes.ORDERED,
Counterparty: channeltypes.Counterparty{
PortId: "super",
ChannelId: "duper",
},
ConnectionHops: []string{"no-more"},
Version: "closedVersion",
PortId: "closedPortID",
ChannelId: "closedChannelID",
},
}
specs := map[string]struct { specs := map[string]struct {
srcQuery *wasmvmtypes.IBCQuery srcQuery *wasmvmtypes.IBCQuery
wasmKeeper *mockWasmQueryKeeper wasmKeeper *mockWasmQueryKeeper
@@ -112,82 +59,6 @@ func TestIBCQuerier(t *testing.T) {
channelKeeper: &wasmtesting.MockChannelKeeper{}, channelKeeper: &wasmtesting.MockChannelKeeper{},
expJSONResult: `{"port_id":"myIBCPortID"}`, expJSONResult: `{"port_id":"myIBCPortID"}`,
}, },
"query list channels - all": {
srcQuery: &wasmvmtypes.IBCQuery{
ListChannels: &wasmvmtypes.ListChannelsQuery{},
},
channelKeeper: &wasmtesting.MockChannelKeeper{
IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels),
},
expJSONResult: `{
"channels": [
{
"endpoint": {
"port_id": "myPortID",
"channel_id": "myChannelID"
},
"counterparty_endpoint": {
"port_id": "counterPartyPortID",
"channel_id": "counterPartyChannelID"
},
"order": "ORDER_ORDERED",
"version": "v1",
"connection_id": "one"
},
{
"endpoint": {
"port_id": "otherPortID",
"channel_id": "otherChannelID"
},
"counterparty_endpoint": {
"port_id": "otherCounterPartyPortID",
"channel_id": "otherCounterPartyChannelID"
},
"order": "ORDER_UNORDERED",
"version": "otherVersion",
"connection_id": "other"
}
]
}`,
},
"query list channels - filtered": {
srcQuery: &wasmvmtypes.IBCQuery{
ListChannels: &wasmvmtypes.ListChannelsQuery{
PortID: "otherPortID",
},
},
channelKeeper: &wasmtesting.MockChannelKeeper{
IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels),
},
expJSONResult: `{
"channels": [
{
"endpoint": {
"port_id": "otherPortID",
"channel_id": "otherChannelID"
},
"counterparty_endpoint": {
"port_id": "otherCounterPartyPortID",
"channel_id": "otherCounterPartyChannelID"
},
"order": "ORDER_UNORDERED",
"version": "otherVersion",
"connection_id": "other"
}
]
}`,
},
"query list channels - filtered empty": {
srcQuery: &wasmvmtypes.IBCQuery{
ListChannels: &wasmvmtypes.ListChannelsQuery{
PortID: "none-existing",
},
},
channelKeeper: &wasmtesting.MockChannelKeeper{
IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels),
},
expJSONResult: `{"channels": []}`,
},
"query channel": { "query channel": {
srcQuery: &wasmvmtypes.IBCQuery{ srcQuery: &wasmvmtypes.IBCQuery{
Channel: &wasmvmtypes.ChannelQuery{ Channel: &wasmvmtypes.ChannelQuery{

View File

@@ -11,12 +11,12 @@ import (
) )
type MockChannelKeeper struct { type MockChannelKeeper struct {
GetChannelFn func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) GetChannelFn func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool)
GetNextSequenceSendFn func(ctx sdk.Context, portID, channelID string) (uint64, bool) GetNextSequenceSendFn func(ctx sdk.Context, portID, channelID string) (uint64, bool)
ChanCloseInitFn func(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error ChanCloseInitFn func(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error
GetAllChannelsFn func(ctx sdk.Context) []channeltypes.IdentifiedChannel GetAllChannelsFn func(ctx sdk.Context) []channeltypes.IdentifiedChannel
IterateChannelsFn func(ctx sdk.Context, cb func(channeltypes.IdentifiedChannel) bool) SetChannelFn func(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel)
SetChannelFn func(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel) GetAllChannelsWithPortPrefixFn func(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel
} }
func (m *MockChannelKeeper) GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { func (m *MockChannelKeeper) GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) {
@@ -47,11 +47,11 @@ func (m *MockChannelKeeper) ChanCloseInit(ctx sdk.Context, portID, channelID str
return m.ChanCloseInitFn(ctx, portID, channelID, chanCap) return m.ChanCloseInitFn(ctx, portID, channelID, chanCap)
} }
func (m *MockChannelKeeper) IterateChannels(ctx sdk.Context, cb func(channeltypes.IdentifiedChannel) bool) { func (m *MockChannelKeeper) GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel {
if m.IterateChannelsFn == nil { if m.GetAllChannelsWithPortPrefixFn == nil {
panic("not expected to be called") panic("not expected to be called")
} }
m.IterateChannelsFn(ctx, cb) return m.GetAllChannelsWithPortPrefixFn(ctx, portPrefix)
} }
func (m *MockChannelKeeper) SetChannel(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel) { func (m *MockChannelKeeper) SetChannel(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel) {

View File

@@ -82,8 +82,8 @@ type ChannelKeeper interface {
GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool)
ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error
GetAllChannels(ctx sdk.Context) (channels []channeltypes.IdentifiedChannel) GetAllChannels(ctx sdk.Context) (channels []channeltypes.IdentifiedChannel)
IterateChannels(ctx sdk.Context, cb func(channeltypes.IdentifiedChannel) bool)
SetChannel(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel) SetChannel(ctx sdk.Context, portID, channelID string, channel channeltypes.Channel)
GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel
} }
// ICS4Wrapper defines the method for an IBC data package to be submitted. // ICS4Wrapper defines the method for an IBC data package to be submitted.