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

feat!: enable standalone consumers to reuse existing clients for ICS #2400

Merged
merged 33 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
34409d7
add connection_id to init params
mpoke Nov 4, 2024
d496224
add message validation
mpoke Nov 4, 2024
29c8d9a
add preCCV and connection_id to ProviderInfo
mpoke Nov 4, 2024
85e9a53
move preccv and connId to ConsumerGenesisState
mpoke Nov 5, 2024
0540cbf
fix merge conflicts
mpoke Nov 7, 2024
049dd9e
add provider logic for non-empty connId
mpoke Nov 11, 2024
6c50702
Merge branch 'main' into marius/894-preccv
mpoke Nov 11, 2024
fa189f5
validate consumer genesis
mpoke Nov 13, 2024
81536d8
initiate CCV channel handshake
mpoke Nov 13, 2024
b07b3ab
fix UT
mpoke Nov 13, 2024
940596d
add changelog entries
mpoke Nov 13, 2024
5241586
fix merge conflicts
mpoke Nov 13, 2024
bc51f31
add TODO
mpoke Nov 13, 2024
1fa7866
fix indent
mpoke Nov 13, 2024
c3f1891
Merge branch 'main' into marius/894-preccv
stana-miric Dec 11, 2024
72e4064
changeover test
stana-miric Dec 12, 2024
4e034e1
Merge branch 'main' into marius/894-preccv
stana-miric Dec 23, 2024
d5c766a
remove setting preccv in app.go
mpoke Jan 2, 2025
57503fa
Merge branch 'main' into marius/894-preccv
mpoke Jan 2, 2025
226f710
Merge branch 'main' into marius/894-preccv
mpoke Jan 2, 2025
fad5f5b
add todos for genesis transformation
mpoke Jan 2, 2025
4d59eb2
interchain test desc added to testing.md
stana-miric Jan 6, 2025
7deec2d
update genesis transformation
stana-miric Jan 6, 2025
add1b5e
update changeover procedure docs
mpoke Jan 7, 2025
1dc3ea1
Update docs/docs/consumer-development/changeover-procedure.md
mpoke Jan 7, 2025
75a9826
Update docs/docs/consumer-development/changeover-procedure.md
mpoke Jan 7, 2025
c061b27
Update docs/docs/consumer-development/changeover-procedure.md
mpoke Jan 7, 2025
88f24ea
Update docs/docs/consumer-development/changeover-procedure.md
mpoke Jan 7, 2025
0c378eb
Update docs/docs/consumer-development/changeover-procedure.md
mpoke Jan 7, 2025
015037d
tests: remove unused e2e changeover [replaced by interchaintest]
MSalopek Jan 7, 2025
479421e
apply review suggestions
mpoke Jan 7, 2025
530b52d
Merge branch 'marius/894-preccv' of github.com:cosmos/interchain-secu…
mpoke Jan 7, 2025
36515a2
do not remove preCCV from consumer genesis state
mpoke Jan 7, 2025
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
12 changes: 12 additions & 0 deletions .changelog/unreleased/api-breaking/2400-preccv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- Enable existing (standalone) chains to use the existing client (and connection)
to the provider chain when becoming a consumer chain. This feature introduces
the following API-breaking changes.
([\#2400](https://github.com/cosmos/interchain-security/pull/2400))

- Add `connection_id` and `preCCV` to `ConsumerGenesisState`, the consumer
mpoke marked this conversation as resolved.
Show resolved Hide resolved
genesis state created by the provider chain. If the `connection_id` is not empty,
`preCCV` is set to true and both `provider.client_state` and `provider.consensus_state`
are set to nil (as the consumer doesn't need to create a new provider client).
As a result, for older versions of consumers, the `connection_id` in
`ConsumerInitializationParameters` must be empty and the resulting `ConsumerGenesisState`
needs to be adapted, i.e., both `connection_id` and `preCCV` need to be removed.
13 changes: 13 additions & 0 deletions .changelog/unreleased/features/2400-preccv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- Enable existing (standalone) chains to use the existing client (and connection)
to the provider chain when becoming a consumer chain. This feature introduces
the following changes.
([\#2400](https://github.com/cosmos/interchain-security/pull/2400))

- Add `connection_id` to `ConsumerInitializationParameters`, the ID of
the connection end _on the provider chain_ on top of which the CCV channel will
be established. Consumer chain owners can set `connection_id` to a valid ID in
order to reuse the underlying clients.
- Add `connection_id` to the consumer genesis state, the ID of the connection
end _on the consumer chain_ on top of which the CCV channel will be established.
If `connection_id` is a valid ID, then the consumer chain will use the underlying
client as the provider client and it will initiate the channel handshake.
3 changes: 3 additions & 0 deletions .changelog/unreleased/state-breaking/2400-preccv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Enable existing (standalone) chains to use the existing client (and connection)
to the provider chain when becoming a consumer chain.
([\#2400](https://github.com/cosmos/interchain-security/pull/2400))
1 change: 1 addition & 0 deletions app/consumer-democracy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ func New(
}
}

// TODO: remove this code
mpoke marked this conversation as resolved.
Show resolved Hide resolved
// For a new consumer chain, this code (together with the entire SetUpgradeHandler) is not needed at all,
// upgrade handler code is application specific. However, as an example, standalone to consumer
// changeover chains should utilize customized upgrade handler code similar to below.
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/build/modules/02-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,7 @@ init_params:
spawn_time: "2024-09-26T06:55:14.616054Z"
transfer_timeout_period: 3600s
unbonding_period: 1209600s
connection_id: ""
metadata:
description: description of your chain and all other relevant information
metadata: some metadata about your chain
Expand Down Expand Up @@ -1708,7 +1709,8 @@ where `update-consumer-msg.json` contains:
"consumer_redistribution_fraction": "0.75",
"blocks_per_distribution_transmission": "1500",
"historical_entries": "1000",
"distribution_transmission_channel": ""
"distribution_transmission_channel": "",
"connection_id": ""
},
"power_shaping_parameters":{
"top_N": 0,
Expand Down
7 changes: 6 additions & 1 deletion proto/interchain_security/ccv/consumer/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ message GenesisState {
// LastTransmissionBlockHeight nil on new chain, filled in on restart.
LastTransmissionBlockHeight last_transmission_block_height = 12
[ (gogoproto.nullable) = false ];
// flag indicating whether the consumer CCV module starts in pre-CCV state
// Flag indicating whether the consumer CCV module starts in pre-CCV state
bool preCCV = 13;
interchain_security.ccv.v1.ProviderInfo provider = 14
[ (gogoproto.nullable) = false ];
// The ID of the connection end on the consumer chain on top of which the
// CCV channel will be established. If connection_id == "", a new client of
// the provider chain and a new connection on top of this client are created.
// The new client is initialized using provider.client_state and provider.consensus_state.
string connection_id = 15;
}

// HeightValsetUpdateID represents a mapping internal to the consumer CCV module
Expand Down
10 changes: 8 additions & 2 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ message ConsumerAdditionProposal {
// sub-protocol. If DistributionTransmissionChannel == "", a new transfer
// channel is created on top of the same connection as the CCV channel.
// Note that transfer_channel_id is the ID of the channel end on the consumer
// chain. it is most relevant for chains performing a sovereign to consumer
// chain. It is most relevant for chains performing a standalone to consumer
// changeover in order to maintain the existing ibc transfer channel
string distribution_transmission_channel = 14;
// Corresponds to the percentage of validators that have to validate the chain under the Top N case.
Expand Down Expand Up @@ -466,9 +466,15 @@ message ConsumerInitializationParameters {
// sub-protocol. If DistributionTransmissionChannel == "", a new transfer
// channel is created on top of the same connection as the CCV channel.
// Note that transfer_channel_id is the ID of the channel end on the consumer
// chain. it is most relevant for chains performing a sovereign to consumer
// chain. It is most relevant for chains performing a standalone to consumer
// changeover in order to maintain the existing ibc transfer channel
string distribution_transmission_channel = 11;
// The ID of the connection end on the provider chain on top of which the CCV
// channel will be established. If connection_id == "", a new client of the
// consumer chain and a new connection on top of this client are created.
// Note that a standalone chain can transition to a consumer chain while
// maintaining existing IBC channels to other chains by providing a valid connection_id.
string connection_id = 12;
}

// PowerShapingParameters contains parameters that shape the validator set that we send to the consumer chain
Expand Down
19 changes: 15 additions & 4 deletions proto/interchain_security/ccv/v1/shared_consumer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,27 @@ message ConsumerParams {
message ConsumerGenesisState {
ConsumerParams params = 1 [ (gogoproto.nullable) = false ];
ProviderInfo provider = 2 [ (gogoproto.nullable) = false ];
// true for new chain, false for chain restart.
bool new_chain = 3; // TODO:Check if this is really needed
// True for new chain, false for chain restart.
// This is needed and always set to true; otherwise, new_chain in the consumer
// genesis state will default to false
bool new_chain = 3;
// Flag indicating whether the consumer CCV module starts in pre-CCV state
bool preCCV = 4;
mpoke marked this conversation as resolved.
Show resolved Hide resolved
// The ID of the connection end on the consumer chain on top of which the
// CCV channel will be established. If connection_id == "", a new client of
// the provider chain and a new connection on top of this client are created.
// The new client is initialized using client_state and consensus_state.
string connection_id = 5;
}

// ProviderInfo defines all information a consumer needs from a provider
// Shared data type between provider and consumer
message ProviderInfo {
// ProviderClientState filled in on new chain, nil on restart.
// The client state for the provider client filled in on new chain, nil on restart.
// If connection_id != "", then client_state is ignored.
ibc.lightclients.tendermint.v1.ClientState client_state = 1;
// ProviderConsensusState filled in on new chain, nil on restart.
// The consensus state for the provider client filled in on new chain, nil on restart.
// If connection_id != "", then consensus_state is ignored.
ibc.lightclients.tendermint.v1.ConsensusState consensus_state = 2;
// InitialValset filled in on new chain and on restart.
repeated .tendermint.abci.ValidatorUpdate initial_val_set = 3
Expand Down
53 changes: 48 additions & 5 deletions x/ccv/consumer/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package keeper
import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
conntypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"

abci "github.com/cometbft/cometbft/abci/types"

Expand Down Expand Up @@ -55,11 +58,31 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *types.GenesisState) []abci.V
// initialValSet is checked in NewChain case by ValidateGenesis
// start a new chain
if state.NewChain {
// create the provider client in InitGenesis for new consumer chain. CCV Handshake must be established with this client id.
clientID, err := k.clientKeeper.CreateClient(ctx, state.Provider.ClientState, state.Provider.ConsensusState)
if err != nil {
// If the client creation fails, the chain MUST NOT start
panic(err)
var clientID string
if state.ConnectionId == "" {
// create the provider client in InitGenesis for new consumer chain. CCV Handshake must be established with this client id.
cid, err := k.clientKeeper.CreateClient(ctx, state.Provider.ClientState, state.Provider.ConsensusState)
if err != nil {
// If the client creation fails, the chain MUST NOT start
panic(err)
}
clientID = cid

k.Logger(ctx).Info("create new provider chain client",
"client id", clientID,
)
} else {
// if connection id is provided, then the client is already created
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, state.ConnectionId)
if !found {
panic(errorsmod.Wrapf(conntypes.ErrConnectionNotFound, "could not find connection(%s)", state.ConnectionId))
}
clientID = connectionEnd.ClientId

k.Logger(ctx).Info("use existing client and connection to provider chain",
"client id", clientID,
"connection id", state.ConnectionId,
)
}

// set provider client id.
Expand All @@ -68,6 +91,26 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *types.GenesisState) []abci.V
// set default value for valset update ID
k.SetHeightValsetUpdateID(ctx, uint64(ctx.BlockHeight()), uint64(0))

if state.ConnectionId != "" {
// initiate CCV channel handshake
ccvChannelOpenInitMsg := channeltypes.NewMsgChannelOpenInit(
ccv.ConsumerPortID,
ccv.Version,
channeltypes.ORDERED,
[]string{state.ConnectionId},
ccv.ProviderPortID,
"", // signer unused
)
_, err := k.ChannelOpenInit(ctx, ccvChannelOpenInitMsg)
if err != nil {
panic(err)
}

// Note that if the connection ID is not provider, we cannot initiate
// the connection handshake as the counterparty client ID is unknown
// at this point. The connection handshake must be initiated by a relayer.
}

} else {
// chain restarts with the CCV channel established
if state.ProviderChannelId != "" {
Expand Down
57 changes: 41 additions & 16 deletions x/ccv/consumer/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ func NewInitialGenesisState(cs *ibctmtypes.ClientState, consState *ibctmtypes.Co
// expect the following optional and mandatory genesis states:
//
// 1. New chain starts:
// - Params, InitialValset, provider client state, provider consensus state // mandatory
// - Params, InitialValset // mandatory
//
// 1a. ConnectionId is empty
// - provider client state, provider consensus state // mandatory
//
// 1b. ConnectionId is not empty
// - provider client state, provider consensus state // nil
//
// 2. Chain restarts with CCV handshake still in progress:
// - Params, InitialValset, ProviderID, HeightToValidatorSetUpdateID // mandatory
Expand All @@ -73,8 +79,6 @@ func NewInitialGenesisState(cs *ibctmtypes.ClientState, consState *ibctmtypes.Co
// 3. Chain restarts with CCV handshake completed:
// - Params, InitialValset, ProviderID, channelID, HeightToValidatorSetUpdateID // mandatory
// - MaturingVSCPackets, OutstandingDowntime, PendingConsumerPacket, LastTransmissionBlockHeight // optional
//

func (gs GenesisState) Validate() error {
if !gs.Params.Enabled {
return nil
Expand All @@ -87,24 +91,45 @@ func (gs GenesisState) Validate() error {
}

if gs.NewChain {
if gs.Provider.ClientState == nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider client state cannot be nil for new chain")
}
if err := gs.Provider.ClientState.Validate(); err != nil {
return errorsmod.Wrapf(ccv.ErrInvalidGenesis, "provider client state invalid for new chain %s", err.Error())
}
if gs.Provider.ConsensusState == nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider consensus state cannot be nil for new chain")
}
if err := gs.Provider.ConsensusState.ValidateBasic(); err != nil {
return errorsmod.Wrapf(ccv.ErrInvalidGenesis, "provider consensus state invalid for new chain %s", err.Error())
if gs.ConnectionId == "" {
// connection ID is not provided
if gs.Provider.ClientState == nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider client state cannot be nil for new chain")
}
if err := gs.Provider.ClientState.Validate(); err != nil {
return errorsmod.Wrapf(ccv.ErrInvalidGenesis, "provider client state invalid for new chain %s", err.Error())
}
if gs.Provider.ConsensusState == nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider consensus state cannot be nil for new chain")
}
if err := gs.Provider.ConsensusState.ValidateBasic(); err != nil {
return errorsmod.Wrapf(ccv.ErrInvalidGenesis, "provider consensus state invalid for new chain %s", err.Error())
}
} else {
// connection ID is provided
if gs.Provider.ClientState != nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider client state must be nil when connection id is provided")
}
if gs.Provider.ConsensusState != nil {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider consensus state must be nil when connection id is provided")
}
if err := ccv.ValidateConnectionIdentifier(gs.ConnectionId); err != nil {
return errorsmod.Wrapf(ccv.ErrInvalidGenesis, "invalid connection id: %s", err.Error())
}
}

if gs.ProviderClientId != "" {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider client id cannot be set for new chain. It must be established on handshake")
}
if gs.ProviderChannelId != "" {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "provider channel id cannot be set for new chain. It must be established on handshake")
}
if len(gs.HeightToValsetUpdateId) != 0 {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "HeightToValsetUpdateId must be nil for new chain")
}
if len(gs.OutstandingDowntimeSlashing) != 0 {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "OutstandingDowntimeSlashing must be nil for new chain")
}
if len(gs.PendingConsumerPackets.List) != 0 {
return errorsmod.Wrap(ccv.ErrInvalidGenesis, "pending consumer packets must be empty for new chain")
}
Expand All @@ -121,11 +146,11 @@ func (gs GenesisState) Validate() error {
if handshakeInProgress {
if len(gs.OutstandingDowntimeSlashing) != 0 {
return errorsmod.Wrap(
ccv.ErrInvalidGenesis, "outstanding downtime must be empty when handshake isn't completed")
ccv.ErrInvalidGenesis, "outstanding downtime must be empty when handshake in progress")
}
if gs.LastTransmissionBlockHeight.Height != 0 {
return errorsmod.Wrap(
ccv.ErrInvalidGenesis, "last transmission block height must be zero when handshake isn't completed")
ccv.ErrInvalidGenesis, "last transmission block height must be zero when handshake in progress")
}
if len(gs.PendingConsumerPackets.List) != 0 {
for _, packet := range gs.PendingConsumerPackets.List {
Expand Down
Loading
Loading