@@ -3,6 +3,10 @@ package integration
33import (
44 transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
55 channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
6+
7+ consumertypes "github.com/cosmos/interchain-security/v5/x/ccv/consumer/types"
8+ providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types"
9+ ccv "github.com/cosmos/interchain-security/v5/x/ccv/types"
610)
711
812func (suite * CCVTestSuite ) TestRecycleTransferChannel () {
@@ -53,3 +57,129 @@ func (suite *CCVTestSuite) TestRecycleTransferChannel() {
5357 channels := suite .consumerApp .GetIBCKeeper ().ChannelKeeper .GetAllChannels (suite .consumerCtx ())
5458 suite .Require ().Len (channels , 2 )
5559}
60+
61+ // TestChangeoverWithConnectionReuse tests the standalone-to-consumer changeover
62+ // when reusing an existing IBC connection (ICS1 feature).
63+ // This validates that a consumer chain can reuse an existing connection during changeover.
64+ func (suite * CCVTestSuite ) TestChangeoverWithConnectionReuse () {
65+ // Step 1: Create connection between consumer and provider (simulating existing connection)
66+ suite .coordinator .CreateConnections (suite .path )
67+
68+ consumerKeeper := suite .consumerApp .GetConsumerKeeper ()
69+ providerKeeper := suite .providerApp .GetProviderKeeper ()
70+
71+ // Get the connection IDs from the created connection
72+ providerConnectionID := suite .path .EndpointA .ConnectionID
73+ consumerConnectionID := suite .path .EndpointB .ConnectionID
74+
75+ suite .Require ().NotEmpty (providerConnectionID , "provider connection ID should be set" )
76+ suite .Require ().NotEmpty (consumerConnectionID , "consumer connection ID should be set" )
77+
78+ // Commit blocks on provider chain to ensure historical info is saved
79+ // MakeConsumerGenesis needs historical info at the current height
80+ suite .coordinator .CommitBlock (suite .providerChain )
81+
82+ // Step 2: Create a consumer addition proposal with connection_id set
83+ prop := providertypes.ConsumerAdditionProposal {
84+ ChainId : suite .consumerChain .ChainID ,
85+ UnbondingPeriod : ccv .DefaultConsumerUnbondingPeriod ,
86+ CcvTimeoutPeriod : ccv .DefaultCCVTimeoutPeriod ,
87+ TransferTimeoutPeriod : ccv .DefaultTransferTimeoutPeriod ,
88+ ConsumerRedistributionFraction : "0.75" ,
89+ BlocksPerDistributionTransmission : 1000 ,
90+ HistoricalEntries : 10000 ,
91+ ConnectionId : providerConnectionID , // ICS1: Reuse existing connection
92+ }
93+
94+ // Step 3: Generate consumer genesis with connection reuse
95+ consumerGenesis , _ , err := providerKeeper .MakeConsumerGenesis (suite .providerCtx (), & prop )
96+ suite .Require ().NoError (err , "MakeConsumerGenesis should succeed" )
97+
98+ // Step 4: Verify the generated genesis has connection reuse fields set correctly
99+ suite .Require ().Equal (consumerConnectionID , consumerGenesis .ConnectionId ,
100+ "consumer genesis should have connection_id set to consumer-side connection" )
101+ suite .Require ().True (consumerGenesis .PreCCV ,
102+ "consumer genesis should have preCCV=true for connection reuse" )
103+ suite .Require ().Nil (consumerGenesis .Provider .ClientState ,
104+ "client_state should be nil when reusing connection" )
105+ suite .Require ().Nil (consumerGenesis .Provider .ConsensusState ,
106+ "consensus_state should be nil when reusing connection" )
107+
108+ // For integration testing purposes, override preCCV to false since our test consumer
109+ // is not a real standalone chain with a standalone staking keeper.
110+ // In a real changeover scenario, preCCV would be true and the consumer would have
111+ // a standalone staking keeper. This test focuses on verifying the connection reuse
112+ // mechanism (provider genesis generation and consumer initialization with existing client).
113+ consumerGenesis .PreCCV = false
114+
115+ // Step 5: Get the existing client ID from the connection (before InitGenesis)
116+ consumerConn , found := suite .consumerApp .GetIBCKeeper ().ConnectionKeeper .GetConnection (
117+ suite .consumerCtx (), consumerConnectionID ,
118+ )
119+ suite .Require ().True (found , "consumer connection should exist" )
120+ existingClientID := consumerConn .ClientId
121+
122+ // Step 6: Initialize consumer with the generated genesis (with connection reuse)
123+ // Construct consumer GenesisState from the shared ConsumerGenesisState
124+ consumerGenesisState := & consumertypes.GenesisState {
125+ Params : consumerGenesis .Params ,
126+ NewChain : consumerGenesis .NewChain ,
127+ Provider : consumerGenesis .Provider ,
128+ PreCCV : consumerGenesis .PreCCV ,
129+ ConnectionId : consumerGenesis .ConnectionId ,
130+ }
131+ consumerKeeper .InitGenesis (suite .consumerCtx (), consumerGenesisState )
132+
133+ // Step 7: Verify consumer initialized correctly using existing connection's client
134+ providerClientID , found := consumerKeeper .GetProviderClientID (suite .consumerCtx ())
135+ suite .Require ().True (found , "provider client ID should be set" )
136+ suite .Require ().Equal (existingClientID , providerClientID ,
137+ "consumer should use existing client from connection" )
138+
139+ // Step 8: Complete CCV channel handshake on top of existing connection
140+ suite .ExecuteCCVChannelHandshake (suite .path )
141+
142+ // Step 9: Verify CCV channel was established on the existing connection
143+ // Note: In a real scenario, the provider channel ID would be set when the first VSC packet
144+ // is received. For this test, we verify the channel exists and manually set it since
145+ // we're testing connection reuse, not the full packet relay flow.
146+ channels := suite .consumerApp .GetIBCKeeper ().ChannelKeeper .GetAllChannels (suite .consumerCtx ())
147+ var ccvChannelID string
148+ for _ , ch := range channels {
149+ if ch .PortId == ccv .ConsumerPortID && ch .State == channeltypes .OPEN {
150+ ccvChannelID = ch .ChannelId
151+ break
152+ }
153+ }
154+ suite .Require ().NotEmpty (ccvChannelID , "CCV channel should exist" )
155+
156+ // Set the provider channel manually for testing purposes
157+ consumerKeeper .SetProviderChannel (suite .consumerCtx (), ccvChannelID )
158+
159+ // Verify we can now get the provider channel
160+ retrievedChannelID , found := consumerKeeper .GetProviderChannel (suite .consumerCtx ())
161+ suite .Require ().True (found , "provider channel should be set" )
162+ suite .Require ().Equal (ccvChannelID , retrievedChannelID , "provider channel ID should match" )
163+
164+ ccvChannel , found := suite .consumerApp .GetIBCKeeper ().ChannelKeeper .GetChannel (
165+ suite .consumerCtx (), ccv .ConsumerPortID , ccvChannelID ,
166+ )
167+ suite .Require ().True (found , "CCV channel should exist" )
168+ suite .Require ().Equal (consumerConnectionID , ccvChannel .ConnectionHops [0 ],
169+ "CCV channel should be on the existing connection" )
170+
171+ // Step 10: Verify the connection is shared between CCV and any other channels
172+ allChannels := suite .consumerApp .GetIBCKeeper ().ChannelKeeper .GetAllChannels (suite .consumerCtx ())
173+
174+ // Count how many channels use the existing connection
175+ channelsOnConnection := 0
176+ for _ , ch := range allChannels {
177+ if len (ch .ConnectionHops ) > 0 && ch .ConnectionHops [0 ] == consumerConnectionID {
178+ channelsOnConnection ++
179+ }
180+ }
181+
182+ // At minimum, the CCV channel should be on the existing connection
183+ suite .Require ().GreaterOrEqual (channelsOnConnection , 1 ,
184+ "at least CCV channel should use the existing connection" )
185+ }
0 commit comments