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 19 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))
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,17 @@ test-integration-cov:
go test ./tests/integration/... -timeout 30m -coverpkg=./... -coverprofile=integration-profile.out -covermode=atomic

# run interchain tests
# we can use PROVIDER_IMAGE_TAG and PROVIDER_IMAGE_NAME to run tests with a desired docker image,
# including a locally built one that, for example, contains some of our changes that are not yet on the main branch.
# if not provided, default value for PROVIDER_IMAGE_TAG is "latest" and for PROVIDER_IMAGE_NAME "ghcr.io/cosmos/interchain-security"
# we can use PROVIDER_IMAGE_TAG, PROVIDER_IMAGE_NAME, SOUVEREIGN_IMAGE_TAG, and SOUVEREIGN_IMAGE_NAME to run tests with desired docker images,
# including locally built ones that, for example, contain some of our changes that are not yet on the main branch.
# if not provided, default value for PROVIDER_IMAGE_TAG and SOUVEREIGN_IMAGE_TAG is "latest" and for PROVIDER_IMAGE_NAME
# and SOUVEREIGN_IMAGE_NAME is "ghcr.io/cosmos/interchain-security"
test-interchain:
cd tests/interchain && PROVIDER_IMAGE_NAME=$(PROVIDER_IMAGE_NAME) PROVIDER_IMAGE_TAG=$(PROVIDER_IMAGE_TAG) go test ./... -timeout 30m
cd tests/interchain && \
PROVIDER_IMAGE_NAME=$(PROVIDER_IMAGE_NAME) \
PROVIDER_IMAGE_TAG=$(PROVIDER_IMAGE_TAG) \
SOUVEREIGN_IMAGE_NAME=$(SOUVEREIGN_IMAGE_NAME) \
SOUVEREIGN_IMAGE_TAG=$(SOUVEREIGN_IMAGE_TAG) \
go test ./... -timeout 30m

# run mbt tests
test-mbt:
Expand Down
2 changes: 0 additions & 2 deletions app/consumer-democracy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,8 +689,6 @@ func New(

consumerGenesis := consumertypes.GenesisState{}
appCodec.MustUnmarshalJSON(appState[consumertypes.ModuleName], &consumerGenesis)

consumerGenesis.PreCCV = true
app.ConsumerKeeper.InitGenesis(sdkCtx, &consumerGenesis)

app.Logger().Info("start to run module migrations...")
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 @@ -1560,6 +1560,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 @@ -1715,7 +1716,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
121 changes: 117 additions & 4 deletions tests/interchain/chainsuite/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"path"
"strconv"
"strings"
"sync"
"time"

sdkmath "cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
Expand All @@ -16,6 +19,7 @@ import (
"github.com/strangelove-ventures/interchaintest/v8"
"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v8/ibc"
"github.com/tidwall/gjson"
"golang.org/x/sync/errgroup"
)

Expand Down Expand Up @@ -48,8 +52,8 @@ func chainFromCosmosChain(cosmos *cosmos.CosmosChain, relayerWallet ibc.Wallet,
return c, nil
}

// CreateProviderChain creates a single new chain with the given version and returns the chain object.
func CreateProviderChain(ctx context.Context, testName interchaintest.TestName, spec *interchaintest.ChainSpec) (*Chain, error) {
// CreateChain creates a single new chain with the given version and returns the chain object.
func CreateChain(ctx context.Context, testName interchaintest.TestName, spec *interchaintest.ChainSpec) (*Chain, error) {
cf := interchaintest.NewBuiltinChainFactory(
GetLogger(ctx),
[]*interchaintest.ChainSpec{spec},
Expand Down Expand Up @@ -155,6 +159,97 @@ func getValidatorWallets(ctx context.Context, chain *Chain) ([]ValidatorWallet,
return wallets, nil
}

// UpdateAndVerifyStakeChange updates the staking amount on the provider chain and verifies that the change is reflected on the consumer side
func (p *Chain) UpdateAndVerifyStakeChange(ctx context.Context, consumer *Chain, relayer *Relayer, amount, valIdx int) error {

providerAddress := p.ValidatorWallets[valIdx]

providerHex, err := p.GetValidatorHexAddress(ctx, valIdx)
if err != nil {
return err
}
consumerHex, err := consumer.GetValidatorHexAddress(ctx, valIdx)
if err != nil {
return err
}

providerPowerBefore, err := p.GetValidatorPower(ctx, providerHex)
if err != nil {
return err
}

// increase the stake for the given validator
_, err = p.Validators[valIdx].ExecTx(ctx, providerAddress.Moniker,
"staking", "delegate",
providerAddress.ValoperAddress, fmt.Sprintf("%d%s", amount, p.Config().Denom),
)
if err != nil {
return err
}

// check that the validator power is updated on both, provider and consumer chains
tCtx, tCancel := context.WithTimeout(ctx, 5*time.Minute)
defer tCancel()
var retErr error
for tCtx.Err() == nil {
retErr = nil
providerPower, err := p.GetValidatorPower(ctx, providerHex)
if err != nil {
return err
}
consumerPower, err := consumer.GetValidatorPower(ctx, consumerHex)
if err != nil {
return err
}
if providerPowerBefore >= providerPower {
retErr = fmt.Errorf("provider power did not increase after delegation")
} else if providerPower != consumerPower {
retErr = fmt.Errorf("consumer power did not update after provider delegation")
}
if retErr == nil {
break
}
time.Sleep(CommitTimeout)
}
return retErr
}

func (p *Chain) GetValidatorHexAddress(ctx context.Context, valIdx int) (string, error) {
json, err := p.Validators[valIdx].ReadFile(ctx, "config/priv_validator_key.json")
if err != nil {
return "", err
}
return gjson.GetBytes(json, "address").String(), nil
}

func (c *Chain) GetValidatorPower(ctx context.Context, hexaddr string) (int64, error) {
var power int64
err := checkEndpoint(c.GetHostRPCAddress()+"/validators", func(b []byte) error {
power = gjson.GetBytes(b, fmt.Sprintf("result.validators.#(address==\"%s\").voting_power", hexaddr)).Int()
if power == 0 {
return fmt.Errorf("validator %s power not found; validators are: %s", hexaddr, string(b))
}
return nil
})
if err != nil {
return 0, err
}
return power, nil
}

func checkEndpoint(url string, f func([]byte) error) error {
resp, err := http.Get(url) //nolint:gosec
if err != nil {
return err
}
defer resp.Body.Close()
bts, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
return f(bts)
}

func (c *Chain) WaitForProposalStatus(ctx context.Context, proposalID string, status govv1.ProposalStatus) error {
propID, err := strconv.ParseInt(proposalID, 10, 64)
if err != nil {
Expand Down Expand Up @@ -201,8 +296,8 @@ func (c *Chain) SubmitAndVoteForProposal(ctx context.Context, prop cosmos.TxProp
}

// builds proposal message, submits, votes and wait for proposal expected status
func (c *Chain) ExecuteProposalMsg(ctx context.Context, proposalMsg cosmos.ProtoMessage, proposer string, chainName string, vote string, expectedStatus govv1.ProposalStatus, expedited bool) error {
proposal, err := c.BuildProposal([]cosmos.ProtoMessage{proposalMsg}, chainName, "summary", "", GovMinDepositString, proposer, false)
func (c *Chain) ExecuteProposalMsg(ctx context.Context, proposalMsg cosmos.ProtoMessage, proposer string, title string, vote string, expectedStatus govv1.ProposalStatus, expedited bool) error {
proposal, err := c.BuildProposal([]cosmos.ProtoMessage{proposalMsg}, title, "summary", "", GovMinDepositString, proposer, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -439,6 +534,24 @@ func (c *Chain) GetValidatorKey(ctx context.Context, validatorIndex int) (string
return address, nil
}

func (c *Chain) GetCcvConsumerParams(ctx context.Context) (ConsumerParamsResponse, error) {
queryRes, _, err := c.GetNode().ExecQuery(
ctx,
"ccvconsumer", "params",
)
if err != nil {
return ConsumerParamsResponse{}, err
}

var queryResponse ConsumerParamsResponse
err = json.Unmarshal([]byte(queryRes), &queryResponse)
if err != nil {
return ConsumerParamsResponse{}, err
}

return queryResponse, nil
}

func getEvtAttribute(events []abci.Event, evtType string, key string) (string, bool) {
for _, evt := range events {
if evt.GetType() == evtType {
Expand Down
23 changes: 2 additions & 21 deletions tests/interchain/chainsuite/chain_spec_provider.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package chainsuite

import (
"strconv"

"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v8/ibc"

"github.com/strangelove-ventures/interchaintest/v8"
)

func GetProviderSpec(validatorCount int) *interchaintest.ChainSpec {
func GetProviderSpec(validatorCount int, modifiedGenesis []cosmos.GenesisKV) *interchaintest.ChainSpec {
fullNodes := FullNodeCount
validators := validatorCount

Expand All @@ -33,25 +31,8 @@ func GetProviderSpec(validatorCount int) *interchaintest.ChainSpec {
Repository: ProviderImageName(),
UIDGID: "1025:1025",
}},
ModifyGenesis: cosmos.ModifyGenesis(providerModifiedGenesis()),
ModifyGenesis: cosmos.ModifyGenesis(modifiedGenesis),
ModifyGenesisAmounts: DefaultGenesisAmounts(Stake),
},
}
}

func providerModifiedGenesis() []cosmos.GenesisKV {
return []cosmos.GenesisKV{
cosmos.NewGenesisKV("app_state.staking.params.unbonding_time", ProviderUnbondingTime.String()),
cosmos.NewGenesisKV("app_state.gov.params.voting_period", GovVotingPeriod.String()),
cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", GovDepositPeriod.String()),
cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", Stake),
cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", strconv.Itoa(GovMinDepositAmount)),
cosmos.NewGenesisKV("app_state.slashing.params.signed_blocks_window", strconv.Itoa(ProviderSlashingWindow)),
cosmos.NewGenesisKV("app_state.slashing.params.downtime_jail_duration", DowntimeJailDuration.String()),
cosmos.NewGenesisKV("app_state.slashing.params.slash_fraction_double_sign", SlashFractionDoubleSign),
cosmos.NewGenesisKV("app_state.provider.params.slash_meter_replenish_period", ProviderReplenishPeriod),
cosmos.NewGenesisKV("app_state.provider.params.slash_meter_replenish_fraction", ProviderReplenishFraction),
cosmos.NewGenesisKV("app_state.provider.params.blocks_per_epoch", "1"),
cosmos.NewGenesisKV("app_state.staking.params.max_validators", "1"),
}
}
Loading
Loading