From f560430b38e47a18c230e37c1a22a411de5d5c34 Mon Sep 17 00:00:00 2001 From: Damian Nolan Date: Mon, 8 Jan 2024 11:54:04 +0100 Subject: [PATCH] test: add mock middleware to block app upgrades and provide test coverage in core (#5406) --- modules/core/keeper/msg_server_test.go | 124 ++++++++++++++++ testing/mock/middleware.go | 197 +++++++++++++++++++++++++ testing/simapp/app.go | 8 + 3 files changed, 329 insertions(+) create mode 100644 testing/mock/middleware.go diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go index 25b0fb9729f..a737bb2ddcd 100644 --- a/modules/core/keeper/msg_server_test.go +++ b/modules/core/keeper/msg_server_test.go @@ -13,6 +13,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" @@ -903,6 +904,27 @@ func (suite *KeeperTestSuite) TestChannelUpgradeInit() { suite.Require().Nil(res) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg = channeltypes.NewMsgChannelUpgradeInit( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointA.GetProposedUpgrade().Fields, + path.EndpointA.Chain.GetSimApp().IBCKeeper.GetAuthority(), + ) + }, + func(res *channeltypes.MsgChannelUpgradeInitResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { @@ -982,6 +1004,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeTry() { suite.Require().Equal(uint64(99), errorReceipt.Sequence) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeTryResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { @@ -1150,6 +1189,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeAck() { suite.Require().False(store.Has([]byte("foo"))) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeAckResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { @@ -1355,6 +1411,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeConfirm() { suite.Require().Equal(uint64(1), errorReceipt.Sequence) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeConfirmResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { @@ -1455,6 +1528,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeOpen() { suite.Require().ErrorIs(err, channeltypes.ErrInvalidChannelState) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeOpenResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { @@ -1652,6 +1742,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeCancel() { suite.Require().Equal(uint64(1), channel.UpgradeSequence) }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { tc := tc @@ -1793,6 +1900,23 @@ func (suite *KeeperTestSuite) TestChannelUpgradeTimeout() { suite.Require().True(found, "channel upgrade should not be nil") }, }, + { + "ibc application does not implement the UpgradeableModule interface", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + path.EndpointB.ChannelConfig.PortID = ibcmock.MockBlockUpgrade + + suite.coordinator.Setup(path) + + msg.PortId = path.EndpointB.ChannelConfig.PortID + msg.ChannelId = path.EndpointB.ChannelID + }, + func(res *channeltypes.MsgChannelUpgradeTimeoutResponse, err error) { + suite.Require().ErrorIs(err, porttypes.ErrInvalidRoute) + suite.Require().Nil(res) + }, + }, } for _, tc := range cases { diff --git a/testing/mock/middleware.go b/testing/mock/middleware.go new file mode 100644 index 00000000000..954132d0cff --- /dev/null +++ b/testing/mock/middleware.go @@ -0,0 +1,197 @@ +package mock + +import ( + "bytes" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +const ( + MockBlockUpgrade = "mockblockupgrade" +) + +var _ porttypes.Middleware = (*BlockUpgradeMiddleware)(nil) + +// BlockUpgradeMiddleware does not implement the UpgradeableModule interface +type BlockUpgradeMiddleware struct { + appModule *AppModule + IBCApp *IBCApp // base application of an IBC middleware stack +} + +// NewIBCModule creates a new IBCModule given the underlying mock IBC application and scopedKeeper. +func NewBlockUpgradeMiddleware(appModule *AppModule, app *IBCApp) BlockUpgradeMiddleware { + appModule.ibcApps = append(appModule.ibcApps, app) + return BlockUpgradeMiddleware{ + appModule: appModule, + IBCApp: app, + } +} + +// OnChanOpenInit implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenInit( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string, +) (string, error) { + if strings.TrimSpace(version) == "" { + version = Version + } + + if im.IBCApp.OnChanOpenInit != nil { + return im.IBCApp.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) + } + + if chanCap != nil { + // Claim channel capability passed back by IBC module + if err := im.IBCApp.ScopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + } + + return version, nil +} + +// OnChanOpenTry implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenTry( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { + if im.IBCApp.OnChanOpenTry != nil { + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) + } + + if chanCap != nil { + // Claim channel capability passed back by IBC module + if err := im.IBCApp.ScopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + } + + return Version, nil +} + +// OnChanOpenAck implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenAck(ctx sdk.Context, portID string, channelID string, counterpartyChannelID string, counterpartyVersion string) error { + if im.IBCApp.OnChanOpenAck != nil { + return im.IBCApp.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanOpenConfirm != nil { + return im.IBCApp.OnChanOpenConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseInit implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseInit != nil { + return im.IBCApp.OnChanCloseInit(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseConfirm != nil { + return im.IBCApp.OnChanCloseConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnRecvPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + if im.IBCApp.OnRecvPacket != nil { + return im.IBCApp.OnRecvPacket(ctx, packet, relayer) + } + + // set state by claiming capability to check if revert happens return + capName := GetMockRecvCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + if bytes.Equal(MockPacketData, packet.GetData()) { + return MockAcknowledgement + } else if bytes.Equal(MockAsyncPacketData, packet.GetData()) { + return nil + } + + return MockFailAcknowledgement +} + +// OnAcknowledgementPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + if im.IBCApp.OnAcknowledgementPacket != nil { + return im.IBCApp.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + capName := GetMockAckCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { + if im.IBCApp.OnTimeoutPacket != nil { + return im.IBCApp.OnTimeoutPacket(ctx, packet, relayer) + } + + capName := GetMockTimeoutCanaryCapabilityName(packet) + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { + // application callback called twice on same packet sequence + // must never occur + panic(err) + } + + return nil +} + +// SendPacket implements the ICS4 Wrapper interface +func (BlockUpgradeMiddleware) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + return 0, nil +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (BlockUpgradeMiddleware) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + return nil +} + +// GetAppVersion returns the application version of the underlying application +func (BlockUpgradeMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return Version, true +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 7c63fc97340..dc44a54bd14 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -341,6 +341,7 @@ func NewSimApp( // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do // not replicate if you do not need to test core IBC or light clients. scopedIBCMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName) + scopedIBCMockBlockUpgradeKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.MockBlockUpgrade) scopedFeeMockKeeper := app.CapabilityKeeper.ScopeToModule(MockFeePort) scopedICAMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName + icacontrollertypes.SubModuleName) @@ -489,6 +490,13 @@ func NewSimApp( app.IBCMockModule = mockIBCModule ibcRouter.AddRoute(ibcmock.ModuleName, mockIBCModule) + // Mock IBC app wrapped with a middleware which does not implement the UpgradeableModule interface. + // NOTE: this is used to test integration with apps which do not yet fulfill the UpgradeableModule interface and error when + // an upgrade is tried on the channel. + mockBlockUpgradeIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.MockBlockUpgrade, scopedIBCMockBlockUpgradeKeeper)) + mockBlockUpgradeMw := ibcmock.NewBlockUpgradeMiddleware(&mockModule, mockBlockUpgradeIBCModule.IBCApp) + ibcRouter.AddRoute(ibcmock.MockBlockUpgrade, mockBlockUpgradeMw) + // Create Transfer Stack // SendPacket, since it is originating from the application to core IBC: // transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket