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

test: e2e upgrade tests for v8 -> v9 #6791

Merged
merged 6 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions e2e/tests/upgrades/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type GenesisTestSuite struct {
func (s *GenesisTestSuite) TestIBCGenesis() {
t := s.T()

haltHeight := int64(100)

chainA, chainB := s.GetChains()

ctx := context.Background()
Expand Down
266 changes: 260 additions & 6 deletions e2e/tests/upgrades/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ import (
"github.com/cosmos/ibc-go/e2e/testsuite/query"
"github.com/cosmos/ibc-go/e2e/testvalues"
feetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types"
transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
v7migrations "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7"
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"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)

const (
haltHeight = int64(100)
haltHeightOffset = int64(30)
blocksAfterUpgrade = uint64(10)
)

Expand All @@ -62,6 +64,10 @@ func (s *UpgradeTestSuite) CreateUpgradeTestPath(testName string) (ibc.Relayer,
// UpgradeChain upgrades a chain to a specific version using the planName provided.
// The software upgrade proposal is broadcast by the provided wallet.
func (s *UpgradeTestSuite) UpgradeChain(ctx context.Context, chain *cosmos.CosmosChain, wallet ibc.Wallet, planName, currentVersion, upgradeVersion string) {
height, err := chain.GetNode().Height(ctx)
s.Require().NoError(err, "error fetching height before upgrade")

haltHeight := height + haltHeightOffset
plan := upgradetypes.Plan{
Name: planName,
Height: haltHeight,
Expand All @@ -80,14 +86,17 @@ func (s *UpgradeTestSuite) UpgradeChain(ctx context.Context, chain *cosmos.Cosmo
s.ExecuteAndPassGovV1Beta1Proposal(ctx, chain, wallet, upgradeProposal)
}

height, err := chain.Height(ctx)
s.Require().NoError(err, "error fetching height before upgrade")

timeoutCtx, timeoutCtxCancel := context.WithTimeout(ctx, time.Minute*2)
defer timeoutCtxCancel()

err = test.WaitForBlocks(timeoutCtx, int(haltHeight-height)+1, chain)
s.Require().Error(err, "chain did not halt at halt height")
err = test.WaitForCondition(time.Minute*2, time.Second*2, func() (bool, error) {
status, err := chain.GetNode().Client.Status(timeoutCtx)
gjermundgaraba marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return false, err
}
return status.SyncInfo.LatestBlockHeight >= haltHeight, nil
})
s.Require().NoError(err, "failed to wait for chain to halt")

var allNodes []test.ChainHeighter
for _, node := range chain.Nodes() {
Expand Down Expand Up @@ -1031,6 +1040,251 @@ func (s *UpgradeTestSuite) TestV8ToV8_1ChainUpgrade_ChannelUpgrades() {
})
}

func (s *UpgradeTestSuite) TestV8ToV9ChainUpgrade() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not something for today, but it seems like it could be a good idea to parameterize these upgrade tests. It looks like a lot of them are 90% the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that as well. Another thing I was thinking about was this notion of conformance test that exists in interhcaintest today. If we had a reasonable set of conformance tests we could potentially run them before and after an upgrade and ensure many things at once.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of the things I've been thinking about a bit is basically extracting re-usable fns from our existing E2Es and making the actual test cases super short, something like

   PerformTransferV1(chainA, chainB)  
   UpgradeChain(chainA, newVersion)
   PerformTransferV2(chainA, chainB)

Think it will be possible to reduce a huge amount of duplication in the E2Es.

t := s.T()
testCfg := testsuite.LoadConfig()
ctx := context.Background()

testName := t.Name()

relayer, channelA := s.CreateUpgradeTestPath(testName)

chainA, chainB := s.GetChains()
chainADenom := chainA.Config().Denom
chainBDenom := chainB.Config().Denom

chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
chainAAddress := chainAWallet.FormattedAddress()

chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
chainBAddress := chainBWallet.FormattedAddress()

chainAIBCToken := testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID)
chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID)

s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks")

t.Run("start relayer", func(t *testing.T) {
s.StartRelayer(relayer, testName)
})

t.Run("transfer native tokens from chainA to chainB", func(t *testing.T) {
transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "")
s.AssertTxSuccess(transferTxResp)

s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1)

actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom())
s.Require().NoError(err)

expected := testvalues.IBCTransferAmount
s.Require().Equal(expected, actualBalance.Int64())
})

t.Run("transfer native tokens from chainB to chainA", func(t *testing.T) {
transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferCoins(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA.(*cosmos.CosmosChain)), 0, "")
s.AssertTxSuccess(transferTxResp)

s.AssertPacketRelayed(ctx, chainA, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1)

actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom())
s.Require().NoError(err)

expected := testvalues.IBCTransferAmount
s.Require().Equal(expected, actualBalance.Int64())
})

s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks")

t.Run("upgrade chain", func(t *testing.T) {
govProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), govProposalWallet, testCfg.UpgradeConfig.PlanName, testCfg.ChainConfigs[0].Tag, testCfg.UpgradeConfig.Tag)
})

t.Run("query denoms after upgrade", func(t *testing.T) {
resp, err := query.GRPCQueryWithMethod[transfertypes.QueryDenomsResponse](ctx, chainA, &transfertypes.QueryDenomsRequest{}, "/ibc.applications.transfer.v2.QueryV2/Denoms")
gjermundgaraba marked this conversation as resolved.
Show resolved Hide resolved
s.Require().NoError(err)
s.Require().Len(resp.Denoms, 1)
s.Require().Equal(chainAIBCToken, resp.Denoms[0])
})

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 := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "")
s.AssertTxSuccess(transferTxResp)

s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 2)

actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom())
s.Require().NoError(err)

expected := testvalues.IBCTransferAmount * 2
s.Require().Equal(expected, actualBalance.Int64())
})
}

func (s *UpgradeTestSuite) TestV8ToV9ChainUpgrade_Localhost() {
t := s.T()
testCfg := testsuite.LoadConfig()
ctx := context.Background()

testName := t.Name()

_, channelA := s.CreateUpgradeTestPath(testName)
channelVersion := channelA.Version
gjermundgaraba marked this conversation as resolved.
Show resolved Hide resolved

chainA, chainB := s.GetChains()
chainADenom := chainA.Config().Denom

rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)

var (
srcChannelID string
dstChannelID string
)

s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks")

t.Run("open localhost channel", func(t *testing.T) {
var (
msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse
msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse
)

msgChanOpenInit := channeltypes.NewMsgChannelOpenInit(
transfertypes.PortID, channelVersion,
channeltypes.UNORDERED, []string{exported.LocalhostConnectionID},
transfertypes.PortID, rlyWallet.FormattedAddress(),
)
txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenInit)
s.AssertTxSuccess(txResp)
s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes))
srcChannelID = msgChanOpenInitRes.ChannelId

msgChanOpenTry := channeltypes.NewMsgChannelOpenTry(
transfertypes.PortID, channelVersion,
channeltypes.UNORDERED, []string{exported.LocalhostConnectionID},
transfertypes.PortID, srcChannelID,
channelVersion, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(),
)
txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry)
s.AssertTxSuccess(txResp)
s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes))
dstChannelID = msgChanOpenTryRes.ChannelId

msgChanOpenAck := channeltypes.NewMsgChannelOpenAck(
transfertypes.PortID, srcChannelID,
dstChannelID, channelVersion,
localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(),
)
txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck)
s.AssertTxSuccess(txResp)

msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm(
transfertypes.PortID, dstChannelID,
localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(),
)
txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm)
s.AssertTxSuccess(txResp)
})

t.Run("ibc transfer over localhost", func(t *testing.T) {
txResp := s.Transfer(ctx, chainA, userAWallet, transfertypes.PortID, srcChannelID, testvalues.DefaultTransferCoins(chainADenom), userAWallet.FormattedAddress(), userBWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
s.AssertTxSuccess(txResp)

packet, err := ibctesting.ParsePacketFromEvents(txResp.Events)
s.Require().NoError(err)
s.Require().NotNil(packet)

msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress())

txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket)
s.AssertTxSuccess(txResp)

ack, err := ibctesting.ParseAckFromEvents(txResp.Events)
s.Require().NoError(err)
s.Require().NotNil(ack)

msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress())
txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement)
s.AssertTxSuccess(txResp)

s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, srcChannelID, 1)
ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, dstChannelID)
actualBalance, err := query.Balance(ctx, chainA, userBWallet.FormattedAddress(), ibcToken.IBCDenom())
s.Require().NoError(err)
s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64())
})

t.Run("localhost exists in state before upgrade", func(t *testing.T) {
status, err := query.ClientStatus(ctx, chainA, exported.LocalhostClientID)
s.Require().NoError(err)
s.Require().Equal(exported.Active.String(), status)

state, err := s.ClientState(ctx, chainA, exported.LocalhostClientID)
s.Require().NoError(err)
s.Require().NotNil(state)
})

t.Run("upgrade chain", func(t *testing.T) {
govProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), govProposalWallet, testCfg.UpgradeConfig.PlanName, testCfg.ChainConfigs[0].Tag, testCfg.UpgradeConfig.Tag)
})

t.Run("localhost does not exist in state after upgrade", func(t *testing.T) {
status, err := query.ClientStatus(ctx, chainA, exported.LocalhostClientID)
s.Require().NoError(err)
s.Require().Equal(exported.Active.String(), status)

state, err := s.ClientState(ctx, chainA, exported.LocalhostClientID)
s.Require().Error(err)
s.Require().Nil(state)
})

t.Run("query localhost transfer channel ends after upgrade", func(t *testing.T) {
channelEndA, err := query.Channel(ctx, chainA, transfertypes.PortID, srcChannelID)
s.Require().NoError(err)
s.Require().NotNil(channelEndA)

channelEndB, err := query.Channel(ctx, chainA, transfertypes.PortID, dstChannelID)
s.Require().NoError(err)
s.Require().NotNil(channelEndB)

s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops)
})

t.Run("ibc transfer back over localhost after upgrade", func(t *testing.T) {
ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, dstChannelID)
transferCoins := testvalues.DefaultTransferCoins(ibcToken.IBCDenom())
txResp := s.Transfer(ctx, chainA, userBWallet, transfertypes.PortID, dstChannelID, transferCoins, userBWallet.FormattedAddress(), userAWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
s.AssertTxSuccess(txResp)

packet, err := ibctesting.ParsePacketFromEvents(txResp.Events)
s.Require().NoError(err)
s.Require().NotNil(packet)

msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress())

txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket)
s.AssertTxSuccess(txResp)

ack, err := ibctesting.ParseAckFromEvents(txResp.Events)
s.Require().NoError(err)
s.Require().NotNil(ack)

msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress())
txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement)
s.AssertTxSuccess(txResp)

s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, dstChannelID, 1)

actualBalance, err := query.Balance(ctx, chainA, userAWallet.FormattedAddress(), chainADenom)
s.Require().NoError(err)
s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64())
})
}

// ClientState queries the current ClientState by clientID
func (*UpgradeTestSuite) ClientState(ctx context.Context, chain ibc.Chain, clientID string) (*clienttypes.QueryClientStateResponse, error) {
res, err := query.GRPCQuery[clienttypes.QueryClientStateResponse](ctx, chain, &clienttypes.QueryClientStateRequest{ClientId: clientID})
Expand Down
7 changes: 6 additions & 1 deletion e2e/testsuite/query/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func GRPCQuery[T any](ctx context.Context, chain ibc.Chain, req proto.Message, o
return nil, err
}

return GRPCQueryWithMethod[T](ctx, chain, req, path, opts...)
}

// GRPCQueryWithMethod queries the chain with a query request with a specific method (grpc path) and deserializes the response to T
func GRPCQueryWithMethod[T any](ctx context.Context, chain ibc.Chain, req proto.Message, method string, opts ...grpc.CallOption) (*T, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to add this, because the v2 query path is not standard and I think it is better to send in the path rather than have a bunch of ifs here.

// Create a connection to the gRPC server.
grpcConn, err := grpc.Dial(
chain.GetHostGRPCAddress(),
Expand All @@ -30,7 +35,7 @@ func GRPCQuery[T any](ctx context.Context, chain ibc.Chain, req proto.Message, o
defer grpcConn.Close()

resp := new(T)
err = grpcConn.Invoke(ctx, path, req, resp, opts...)
err = grpcConn.Invoke(ctx, method, req, resp, opts...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/testsuite/testconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ type DebugConfig struct {
GenesisDebug GenesisDebugConfig `yaml:"genesis"`
}

// LoadConfig attempts to load a atest configuration from the default file path.
// LoadConfig attempts to load a test configuration from the default file path.
// if any environment variables are specified, they will take precedence over the individual configuration
// options.
func LoadConfig() TestConfig {
Expand Down
8 changes: 6 additions & 2 deletions modules/apps/transfer/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod

// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-transfer module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx))
if err != nil {
if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
panic(err)
}

if err := types.RegisterQueryV2HandlerClient(context.Background(), mux, types.NewQueryV2Client(clientCtx)); err != nil {
panic(err)
}
}
Expand Down Expand Up @@ -119,6 +122,7 @@ func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), am.keeper)
types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
types.RegisterQueryV2Server(cfg.QueryServer(), am.keeper)

m := keeper.NewMigrator(am.keeper)
if err := cfg.RegisterMigration(types.ModuleName, 2, m.MigrateTotalEscrowForDenom); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions simapp/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ func (app *SimApp) registerUpgradeHandlers() {
),
)

app.UpgradeKeeper.SetUpgradeHandler(
upgrades.V9,
upgrades.CreateDefaultUpgradeHandler(
app.ModuleManager,
app.configurator,
),
)

upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(err)
Expand Down
2 changes: 2 additions & 0 deletions simapp/upgrades/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
V8 = "v8"
// V8_1 defines the upgrade name for the ibc-go/v8.1 upgrade handler.
V8_1 = "v8.1"
// V9 defines the upgrade name for the ibc-go/v9 upgrade handler.
V9 = "v9"
)

// CreateDefaultUpgradeHandler creates an upgrade handler which can be used for regular upgrade tests
Expand Down
Loading