diff --git a/.github/workflows/e2e-upgrade.yaml b/.github/workflows/e2e-upgrade.yaml index adefcd0a264..9432b52a4b9 100644 --- a/.github/workflows/e2e-upgrade.yaml +++ b/.github/workflows/e2e-upgrade.yaml @@ -18,7 +18,13 @@ jobs: chain-binary: icad chain-a-tag: v0.3.5 chain-b-tag: v0.3.5 - chain-upgrade-tag: v0.4.2 + chain-upgrade-tag: v0.4.0 + - test: TestV6ToV7ChainUpgrade + chain-image: ghcr.io/cosmos/ibc-go-simd + chain-binary: simd + chain-a-tag: v6.0.0 + chain-b-tag: v6.0.0 + chain-upgrade-tag: v7.0.0 steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 diff --git a/e2e/tests/upgrades/upgrade_test.go b/e2e/tests/upgrades/upgrade_test.go index dc74d2047e4..faff471bb6c 100644 --- a/e2e/tests/upgrades/upgrade_test.go +++ b/e2e/tests/upgrades/upgrade_test.go @@ -24,8 +24,13 @@ import ( "github.com/cosmos/ibc-go/e2e/testvalues" 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" + v7migrations "github.com/cosmos/ibc-go/v6/modules/core/02-client/migrations/v7" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v6/modules/light-clients/06-solomachine" ibctesting "github.com/cosmos/ibc-go/v6/testing" simappupgrades "github.com/cosmos/ibc-go/v6/testing/simapp/upgrades" + v7upgrades "github.com/cosmos/ibc-go/v6/testing/simapp/upgrades/v7" ) const ( @@ -70,6 +75,10 @@ func (s *UpgradeTestSuite) UpgradeChain(ctx context.Context, chain *cosmos.Cosmo err = chain.StartAllNodes(ctx) s.Require().NoError(err, "error starting upgraded node(s)") + // we are reinitializing the clients because we need to update the hostGRPCAddress after + // the upgrade and subsequent restarting of nodes + s.InitGRPCClients(chain) + timeoutCtx, timeoutCtxCancel = context.WithTimeout(ctx, time.Minute*2) defer timeoutCtxCancel() @@ -386,6 +395,160 @@ func (s *UpgradeTestSuite) TestV5ToV6ChainUpgrade() { }) } +// TestV6ToV7ChainUpgrade will test that an upgrade from a v6 ibc-go binary to a v7 ibc-go binary is successful +// and that the automatic migrations associated with the 02-client module are performed. Namely that the solo machine +// proto definition is migrated in state from the v2 to v3 definition. This is checked by creating a solo machine client +// before the upgrade and asserting that its TypeURL has been changed after the upgrade. The test also ensure packets +// can be sent before and after the upgrade without issue +func (s *UpgradeTestSuite) TestV6ToV7ChainUpgrade() { + t := s.T() + testCfg := testconfig.FromEnv() + + ctx := context.Background() + relayer, channelA := s.SetupChainsRelayerAndChannel(ctx) + chainA, chainB := s.GetChains() + + var ( + chainADenom = chainA.Config().Denom + chainBIBCToken = testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) // IBC token sent to chainB + ) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.Bech32Address(chainA.Config().Bech32Prefix) + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.Bech32Address(chainB.Config().Bech32Prefix) + + // create second tendermint client + createClientOptions := ibc.CreateClientOptions{ + TrustingPeriod: ibctesting.TrustingPeriod.String(), + } + + s.SetupClients(ctx, relayer, createClientOptions) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("check that both tendermint clients are active", func(t *testing.T) { + status, err := s.QueryClientStatus(ctx, chainA, testvalues.TendermintClientID(0)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + status, err = s.QueryClientStatus(ctx, chainA, testvalues.TendermintClientID(1)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + }) + + // create solo machine client using the solomachine implementation from ibctesting + // TODO: the solomachine clientID should be updated when after fix of this issue: https://github.com/cosmos/ibc-go/issues/2907 + solo := ibctesting.NewSolomachine(t, testsuite.Codec(), "solomachine", "testing", 1) + + legacyConsensusState := &v7migrations.ConsensusState{ + PublicKey: solo.ConsensusState().PublicKey, + Diversifier: solo.ConsensusState().Diversifier, + Timestamp: solo.ConsensusState().Timestamp, + } + + legacyClientState := &v7migrations.ClientState{ + Sequence: solo.ClientState().Sequence, + IsFrozen: solo.ClientState().IsFrozen, + ConsensusState: legacyConsensusState, + AllowUpdateAfterProposal: true, + } + + msgCreateSoloMachineClient, err := clienttypes.NewMsgCreateClient(legacyClientState, legacyConsensusState, chainAAddress) + s.Require().NoError(err) + + resp, err := s.BroadcastMessages( + ctx, + chainA, + chainAWallet, + msgCreateSoloMachineClient, + ) + + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + + t.Run("check that the solomachine is now active and that the clientstate is a pre-upgrade v2 solomachine clientstate", func(t *testing.T) { + status, err := s.QueryClientStatus(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + res, err := s.ClientState(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(fmt.Sprint("/", proto.MessageName(&v7migrations.ClientState{})), res.ClientState.TypeUrl) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp, err := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.Require().NoError(err) + s.AssertValidTxResponse(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + // create separate user specifically for the upgrade proposal to more easily verify starting + // and end balances of the chainA users. + chainAUpgradeProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + t.Run("upgrade chainA", func(t *testing.T) { + s.UpgradeChain(ctx, chainA, chainAUpgradeProposalWallet, v7upgrades.UpgradeName, testCfg.ChainAConfig.Tag, testCfg.UpgradeTag) + }) + + t.Run("check that the tendermint clients are active again after upgrade", func(t *testing.T) { + status, err := s.QueryClientStatus(ctx, chainA, testvalues.TendermintClientID(0)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + status, err = s.QueryClientStatus(ctx, chainA, testvalues.TendermintClientID(1)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + }) + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp, err := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.Require().NoError(err) + s.AssertValidTxResponse(transferTxResp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance) + }) + + t.Run("check that the v2 solo machine clientstate has been updated to the v3 solo machine clientstate", func(t *testing.T) { + res, err := s.ClientState(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(fmt.Sprint("/", proto.MessageName(&solomachine.ClientState{})), res.ClientState.TypeUrl) + }) +} + // RegisterInterchainAccount will attempt to register an interchain account on the counterparty chain. func (s *UpgradeTestSuite) RegisterInterchainAccount(ctx context.Context, chain *cosmos.CosmosChain, user *ibc.Wallet, msgRegisterAccount *intertxtypes.MsgRegisterAccount) error { txResp, err := s.BroadcastMessages(ctx, chain, user, msgRegisterAccount) @@ -405,3 +568,16 @@ func getICAVersion(chainAVersion, chainBVersion string) string { // explicitly set the version string because the host chain might not yet support incentivized channels. return icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) } + +// ClientState queries the current ClientState by clientID +func (s *UpgradeTestSuite) ClientState(ctx context.Context, chain ibc.Chain, clientID string) (*clienttypes.QueryClientStateResponse, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientState(ctx, &clienttypes.QueryClientStateRequest{ + ClientId: clientID, + }) + if err != nil { + return res, err + } + + return res, nil +} diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go index 214dd355340..a671f46d58a 100644 --- a/e2e/testsuite/grpc_query.go +++ b/e2e/testsuite/grpc_query.go @@ -34,6 +34,19 @@ func (s *E2ETestSuite) QueryClientState(ctx context.Context, chain ibc.Chain, cl return clientState, nil } +// QueryClientStatus queries the status of the client by clientID +func (s *E2ETestSuite) QueryClientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ + ClientId: clientID, + }) + if err != nil { + return "", err + } + + return res.Status, nil +} + // QueryChannel queries the channel on a given chain for the provided portID and channelID func (s *E2ETestSuite) QueryChannel(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.Channel, error) { queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index 77c9482bf8a..b8c03f0e5f8 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -167,8 +167,8 @@ func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channel time.Sleep(time.Second * 10) } - s.initGRPCClients(chainA) - s.initGRPCClients(chainB) + s.InitGRPCClients(chainA) + s.InitGRPCClients(chainB) chainAChannels, err := r.GetChannels(ctx, eRep, chainA.Config().ChainID) s.Require().NoError(err) @@ -361,9 +361,9 @@ func (s *E2ETestSuite) GetChainGRCPClients(chain ibc.Chain) GRPCClients { return cs } -// initGRPCClients establishes GRPC clients with the given chain. +// InitGRPCClients establishes GRPC clients with the given chain. // The created GRPCClients can be retrieved with GetChainGRCPClients. -func (s *E2ETestSuite) initGRPCClients(chain *cosmos.CosmosChain) { +func (s *E2ETestSuite) InitGRPCClients(chain *cosmos.CosmosChain) { // Create a connection to the gRPC server. grpcConn, err := grpc.Dial( chain.GetHostGRPCAddress(), diff --git a/e2e/testvalues/values.go b/e2e/testvalues/values.go index 70ee43aa924..3e95f5cddf8 100644 --- a/e2e/testvalues/values.go +++ b/e2e/testvalues/values.go @@ -1,6 +1,7 @@ package testvalues import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -34,3 +35,11 @@ func DefaultFee(denom string) feetypes.Fee { func DefaultTransferAmount(denom string) sdk.Coin { return sdk.Coin{Denom: denom, Amount: sdk.NewInt(IBCTransferAmount)} } + +func TendermintClientID(id int) string { + return fmt.Sprintf("07-tendermint-%d", id) +} + +func SolomachineClientID(id int) string { + return fmt.Sprint("06-solomachine-%d", id) +}