diff --git a/e2e/README.md b/e2e/README.md index a0a7f3451a9..306b52cf1ac 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -161,9 +161,9 @@ chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) t.Run("broadcast multi message transaction", func(t *testing.T){ - payPacketFeeMsg := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), nil) - transferMsg := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), chainBWallet.Bech32Address(chainB.Config().Bech32Prefix), clienttypes.NewHeight(1, 1000), 0) - resp, err := s.BroadcastMessages(ctx, chainA, chainAWallet, payPacketFeeMsg, transferMsg) + msgPayPacketFee := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), nil) + msgTransfer := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), chainBWallet.Bech32Address(chainB.Config().Bech32Prefix), clienttypes.NewHeight(1, 1000), 0) + resp, err := s.BroadcastMessages(ctx, chainA, chainAWallet, msgPayPacketFee, msgTransfer) s.AssertValidTxResponse(resp) s.Require().NoError(err) diff --git a/e2e/tests/core/04-channel/upgrades_test.go b/e2e/tests/core/04-channel/upgrades_test.go index e2fd5cb4570..2e7a8a8f60c 100644 --- a/e2e/tests/core/04-channel/upgrades_test.go +++ b/e2e/tests/core/04-channel/upgrades_test.go @@ -10,6 +10,7 @@ import ( test "github.com/strangelove-ventures/interchaintest/v8/testutil" testifysuite "github.com/stretchr/testify/suite" + sdkmath "cosmossdk.io/math" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/ibc-go/e2e/testsuite" @@ -47,15 +48,35 @@ func (s *ChannelTestSuite) TestChannelUpgrade_WithFeeMiddleware_Succeeds() { chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) chainBAddress := chainBWallet.FormattedAddress() + var ( + chainARelayerWallet, chainBRelayerWallet ibc.Wallet + relayerAStartingBalance int64 + testFee = testvalues.DefaultFee(chainADenom) + ) + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") // trying to create some inflight packets, although they might get relayed before the upgrade starts t.Run("create inflight transfer packets between chain A and chain B", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") - s.AssertTxSuccess(transferTxResp) + chainBWalletAmount := ibc.WalletAmount{ + Address: chainBWallet.FormattedAddress(), // destination address + Denom: chainADenom, + Amount: sdkmath.NewInt(testvalues.IBCTransferAmount), + } + + transferTxResp, err := chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName(), chainBWalletAmount, ibc.TransferOptions{}) + s.Require().NoError(err) + s.Require().NoError(transferTxResp.Validate(), "chain-a ibc transfer tx is invalid") + + chainAwalletAmount := ibc.WalletAmount{ + Address: chainAWallet.FormattedAddress(), // destination address + Denom: chainBDenom, + Amount: sdkmath.NewInt(testvalues.IBCTransferAmount), + } - transferTxResp = s.Transfer(ctx, chainB, chainBWallet, channelB.PortID, channelB.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") - s.AssertTxSuccess(transferTxResp) + transferTxResp, err = chainB.SendIBCTransfer(ctx, channelB.ChannelID, chainBWallet.KeyName(), chainAwalletAmount, ibc.TransferOptions{}) + s.Require().NoError(err) + s.Require().NoError(transferTxResp.Validate(), "chain-b ibc transfer tx is invalid") }) t.Run("execute gov proposal to initiate channel upgrade", func(t *testing.T) { @@ -102,7 +123,7 @@ func (s *ChannelTestSuite) TestChannelUpgrade_WithFeeMiddleware_Succeeds() { s.Require().Equal(true, feeEnabled) }) - t.Run("verify channel B upgraded and has version with ics29", func(t *testing.T) { + t.Run("verify channel B upgraded and is fee enabled", func(t *testing.T) { channel, err := s.QueryChannel(ctx, chainB, channelB.PortID, channelB.ChannelID) s.Require().NoError(err) @@ -116,6 +137,101 @@ func (s *ChannelTestSuite) TestChannelUpgrade_WithFeeMiddleware_Succeeds() { s.Require().NoError(err) s.Require().Equal(true, feeEnabled) }) + + t.Run("prune packet acknowledgements", func(t *testing.T) { + // there should be one ack for the packet that we sent before the upgrade + acks, err := s.QueryPacketAcknowledgements(ctx, chainA, channelA.PortID, channelA.ChannelID, []uint64{}) + s.Require().NoError(err) + s.Require().Len(acks, 1) + s.Require().Equal(uint64(1), acks[0].Sequence) + + pruneAcksTxResponse := s.PruneAcknowledgements(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, uint64(1)) + s.AssertTxSuccess(pruneAcksTxResponse) + + // after pruning there should not be any acks + acks, err = s.QueryPacketAcknowledgements(ctx, chainA, channelA.PortID, channelA.ChannelID, []uint64{}) + s.Require().NoError(err) + s.Require().Empty(acks) + }) + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + + t.Run("recover relayer wallets", func(t *testing.T) { + err := s.RecoverRelayerWallets(ctx, relayer) + s.Require().NoError(err) + + chainARelayerWallet, chainBRelayerWallet, err = s.GetRelayerWallets(relayer) + s.Require().NoError(err) + + relayerAStartingBalance, err = s.GetChainANativeBalance(ctx, chainARelayerWallet) + s.Require().NoError(err) + t.Logf("relayer A user starting with balance: %d", relayerAStartingBalance) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("register and verify counterparty payee", func(t *testing.T) { + _, chainBRelayerUser := s.GetRelayerUsers(ctx) + resp := s.RegisterCounterPartyPayee(ctx, chainB, chainBRelayerUser, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, chainBRelayerWallet.FormattedAddress(), chainARelayerWallet.FormattedAddress()) + s.AssertTxSuccess(resp) + + address, err := s.QueryCounterPartyPayee(ctx, chainB, chainBRelayerWallet.FormattedAddress(), channelA.Counterparty.ChannelID) + s.Require().NoError(err) + s.Require().Equal(chainARelayerWallet.FormattedAddress(), address) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + t.Run("send incentivized transfer packet", func(t *testing.T) { + // before adding fees for the packet, there should not be incentivized packets + packets, err := s.QueryIncentivizedPacketsForChannel(ctx, chainA, channelA.PortID, channelA.ChannelID) + s.Require().NoError(err) + s.Require().Empty(packets) + + transferAmount := testvalues.DefaultTransferAmount(chainA.Config().Denom) + + msgPayPacketFee := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.FormattedAddress(), nil) + msgTransfer := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.FormattedAddress(), chainBWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainB), 0, "") + resp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgPayPacketFee, msgTransfer) + s.AssertTxSuccess(resp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + packets, err := s.QueryIncentivizedPacketsForChannel(ctx, chainA, channelA.PortID, channelA.ChannelID) + s.Require().NoError(err) + s.Require().Empty(packets) + }) + + t.Run("tokens are received by walletB", func(t *testing.T) { + actualBalance, err := s.QueryBalance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + // walletB has received two IBC transfers of value testvalues.IBCTransferAmount since the start of the test. + expected := 2 * testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("timeout fee is refunded", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + // once the relayer has relayed the packets, the timeout fee should be refunded. + // walletA has done two IBC transfers of value testvalues.IBCTransferAmount since the start of the test. + expected := testvalues.StartingTokenAmount - (2 * testvalues.IBCTransferAmount) - testFee.AckFee.AmountOf(chainADenom).Int64() - testFee.RecvFee.AmountOf(chainADenom).Int64() + s.Require().Equal(expected, actualBalance) + }) + + t.Run("relayerA is paid ack and recv fee", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainARelayerWallet) + s.Require().NoError(err) + + expected := relayerAStartingBalance + testFee.AckFee.AmountOf(chainADenom).Int64() + testFee.RecvFee.AmountOf(chainADenom).Int64() + s.Require().Equal(expected, actualBalance) + }) } // TestChannelUpgrade_WithFeeMiddleware_FailsWithTimeoutOnAck tests upgrading a transfer channel to wire up fee middleware but fails on ACK because of timeout diff --git a/e2e/tests/transfer/incentivized_test.go b/e2e/tests/transfer/incentivized_test.go index 92a8109924a..4bcfc14b1af 100644 --- a/e2e/tests/transfer/incentivized_test.go +++ b/e2e/tests/transfer/incentivized_test.go @@ -189,8 +189,8 @@ func (s *IncentivizedTransferTestSuite) TestMsgPayPacketFee_InvalidReceiverAccou transferAmount := testvalues.DefaultTransferAmount(chainADenom) t.Run("send IBC transfer", func(t *testing.T) { - transferMsg := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.FormattedAddress(), testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") - txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, transferMsg) + msgTransfer := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.FormattedAddress(), testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgTransfer) // this message should be successful, as receiver account is not validated on the sending chain. s.AssertTxSuccess(txResp) }) @@ -313,9 +313,9 @@ func (s *IncentivizedTransferTestSuite) TestMultiMsg_MsgPayPacketFeeSingleSender s.Require().Empty(packets) }) - payPacketFeeMsg := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.FormattedAddress(), nil) - transferMsg := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.FormattedAddress(), chainBWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainB), 0, "") - resp := s.BroadcastMessages(ctx, chainA, chainAWallet, payPacketFeeMsg, transferMsg) + msgPayPacketFee := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.FormattedAddress(), nil) + msgTransfer := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.FormattedAddress(), chainBWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainB), 0, "") + resp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgPayPacketFee, msgTransfer) s.AssertTxSuccess(resp) t.Run("there should be incentivized packets", func(t *testing.T) { diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go index 107306db7e0..1d9dc8b15ee 100644 --- a/e2e/testsuite/grpc_query.go +++ b/e2e/testsuite/grpc_query.go @@ -214,6 +214,20 @@ func (s *E2ETestSuite) QueryPacketCommitment(ctx context.Context, chain ibc.Chai return res.Commitment, nil } +// QueryPacketAcknowledgements queries the packet acknowledgements on the given chain for the provided channel (optional) list of packet commitment sequences. +func (s *E2ETestSuite) QueryPacketAcknowledgements(ctx context.Context, chain ibc.Chain, portID, channelID string, packetCommitmentSequences []uint64) ([]*channeltypes.PacketState, error) { + queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient + res, err := queryClient.PacketAcknowledgements(ctx, &channeltypes.QueryPacketAcknowledgementsRequest{ + PortId: portID, + ChannelId: channelID, + PacketCommitmentSequences: packetCommitmentSequences, + }) + if err != nil { + return nil, err + } + return res.Acknowledgements, nil +} + // QueryUpgradeError queries the upgrade error on the given chain for the provided channel. func (s *E2ETestSuite) QueryUpgradeError(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.ErrorReceipt, error) { queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient diff --git a/e2e/testsuite/tx.go b/e2e/testsuite/tx.go index 6f867b0e5c8..c3f39b8685c 100644 --- a/e2e/testsuite/tx.go +++ b/e2e/testsuite/tx.go @@ -285,3 +285,15 @@ func (s *E2ETestSuite) PayPacketFeeAsync( msg := feetypes.NewMsgPayPacketFeeAsync(packetID, packetFee) return s.BroadcastMessages(ctx, chain, user, msg) } + +// PruneAcknowledgements broadcasts a MsgPruneAcknowledgements message. +func (s *E2ETestSuite) PruneAcknowledgements( + ctx context.Context, + chain ibc.Chain, + user ibc.Wallet, + portID, channelID string, + limit uint64, +) sdk.TxResponse { + msg := channeltypes.NewMsgPruneAcknowledgements(portID, channelID, limit, user.FormattedAddress()) + return s.BroadcastMessages(ctx, chain, user, msg) +} diff --git a/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go b/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go index 568edb2b031..fb5d54bf740 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go @@ -863,6 +863,33 @@ func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeInit() { } } +// OnChanUpgradeTry callback returns error on controller chains +func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeTry() { + suite.SetupTest() // reset + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + // call application callback directly + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().NoError(err) + + app, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + cbs, ok := app.(porttypes.UpgradableModule) + suite.Require().True(ok) + + version, err := cbs.OnChanUpgradeTry( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.Version, + ) + suite.Require().Error(err) + suite.Require().ErrorIs(err, icatypes.ErrInvalidChannelFlow) + suite.Require().Equal("", version) +} + func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeAck() { var ( path *ibctesting.Path diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index f5fdde6c5c1..3491f47d52d 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -604,6 +604,33 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { } } +// OnChanUpgradeInit callback returns error on host chains +func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeInit() { + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + // call application callback directly + module, _, err := suite.chainB.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) + suite.Require().NoError(err) + + app, ok := suite.chainB.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + cbs, ok := app.(porttypes.UpgradableModule) + suite.Require().True(ok) + + version, err := cbs.OnChanUpgradeInit( + suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + path.EndpointB.ChannelConfig.Order, []string{path.EndpointB.ConnectionID}, path.EndpointB.ChannelConfig.Version, + ) + + suite.Require().Error(err) + suite.Require().ErrorIs(err, icatypes.ErrInvalidChannelFlow) + suite.Require().Equal("", version) +} + func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeTry() { testCases := []struct { name string @@ -672,6 +699,31 @@ func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeTry() { } } +// OnChanUpgradeAck callback returns error on host chains +func (suite *InterchainAccountsTestSuite) TestOnChanUpgradeAck() { + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + // call application callback directly + module, _, err := suite.chainB.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) + suite.Require().NoError(err) + + app, ok := suite.chainB.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + cbs, ok := app.(porttypes.UpgradableModule) + suite.Require().True(ok) + + err = cbs.OnChanUpgradeAck( + suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.Version, + ) + + suite.Require().Error(err) + suite.Require().ErrorIs(err, icatypes.ErrInvalidChannelFlow) +} + func (suite *InterchainAccountsTestSuite) fundICAWallet(ctx sdk.Context, portID string, amount sdk.Coins) { interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(ctx, ibctesting.FirstConnectionID, portID) suite.Require().True(found) diff --git a/modules/core/04-channel/types/msgs.go b/modules/core/04-channel/types/msgs.go index 24f4fcaecd1..facbc235d3f 100644 --- a/modules/core/04-channel/types/msgs.go +++ b/modules/core/04-channel/types/msgs.go @@ -701,7 +701,9 @@ func NewMsgChannelUpgradeCancel( } } -// ValidateBasic implements sdk.Msg +// ValidateBasic implements sdk.Msg. No checks are done for ErrorReceipt and ProofErrorReceipt +// since they are not required if the current channel state is not in FLUSHCOMPLETE and the signer +// is the designated authority (e.g. the governance module). func (msg MsgChannelUpgradeCancel) ValidateBasic() error { if err := host.PortIdentifierValidator(msg.PortId); err != nil { return errorsmod.Wrap(err, "invalid port ID")