diff --git a/LICENSE b/LICENSE index 261eeb9e9..0058c90f2 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2024 Strangelove Crypto, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/chain/cosmos/chain_node.go b/chain/cosmos/chain_node.go index 9371dfc37..658e3bd90 100644 --- a/chain/cosmos/chain_node.go +++ b/chain/cosmos/chain_node.go @@ -11,6 +11,7 @@ import ( "math/rand" "os" "path" + "path/filepath" "strconv" "strings" "sync" @@ -35,11 +36,13 @@ import ( dockerclient "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "go.uber.org/zap" + "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" + ccvclient "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" "github.com/strangelove-ventures/interchaintest/v8/ibc" @@ -249,6 +252,25 @@ func (tn *ChainNode) OverwriteGenesisFile(ctx context.Context, content []byte) e return nil } +func (tn *ChainNode) PrivValFileContent(ctx context.Context) ([]byte, error) { + fr := dockerutil.NewFileRetriever(tn.logger(), tn.DockerClient, tn.TestName) + gen, err := fr.SingleFileContent(ctx, tn.VolumeName, "config/priv_validator_key.json") + if err != nil { + return nil, fmt.Errorf("getting priv_validator_key.json content: %w", err) + } + + return gen, nil +} + +func (tn *ChainNode) OverwritePrivValFile(ctx context.Context, content []byte) error { + fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) + if err := fw.WriteFile(ctx, tn.VolumeName, "config/priv_validator_key.json", content); err != nil { + return fmt.Errorf("overwriting priv_validator_key.json: %w", err) + } + + return nil +} + func (tn *ChainNode) copyGentx(ctx context.Context, destVal *ChainNode) error { nid, err := tn.NodeID(ctx) if err != nil { @@ -713,6 +735,24 @@ func (tn *ChainNode) IsAboveSDK47(ctx context.Context) bool { return tn.HasCommand(ctx, "genesis") } +// ICSVersion returns the version of interchain-security the binary was built with. +// If it doesn't depend on interchain-security, it returns an empty string. +func (tn *ChainNode) ICSVersion(ctx context.Context) string { + if strings.HasPrefix(tn.Chain.Config().Bin, "interchain-security") { + // This isn't super pretty, but it's the best we can do for an interchain-security binary. + // It doesn't depend on itself, and the version command doesn't actually output a version. + // Ideally if you have a binary called something like "v3.3.0-my-fix" you can use it as a version, since the v3.3.0 part is in it. + return semver.Canonical(tn.Image.Version) + } + info := tn.GetBuildInformation(ctx) + for _, dep := range info.BuildDeps { + if strings.HasPrefix(dep.Parent, "github.com/cosmos/interchain-security") { + return semver.Canonical(dep.Version) + } + } + return "" +} + // AddGenesisAccount adds a genesis account for each key func (tn *ChainNode) AddGenesisAccount(ctx context.Context, address string, genesisAmount []sdk.Coin) error { amount := "" @@ -815,6 +855,27 @@ func (tn *ChainNode) SendIBCTransfer( return tn.ExecTx(ctx, keyName, command...) } +func (tn *ChainNode) ConsumerAdditionProposal(ctx context.Context, keyName string, prop ccvclient.ConsumerAdditionProposalJSON) (string, error) { + propBz, err := json.Marshal(prop) + if err != nil { + return "", err + } + + fileName := "proposal_" + dockerutil.RandLowerCaseLetterString(4) + ".json" + + fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) + if err := fw.WriteFile(ctx, tn.VolumeName, fileName, propBz); err != nil { + return "", fmt.Errorf("failure writing proposal json: %w", err) + } + + filePath := filepath.Join(tn.HomeDir(), fileName) + + return tn.ExecTx(ctx, keyName, + "gov", "submit-legacy-proposal", "consumer-addition", filePath, + "--gas", "auto", + ) +} + func (tn *ChainNode) GetTransaction(clientCtx client.Context, txHash string) (*sdk.TxResponse, error) { // Retry because sometimes the tx is not committed to state yet. var txResp *sdk.TxResponse diff --git a/chain/cosmos/codec.go b/chain/cosmos/codec.go index 83c0185f7..b297d2729 100644 --- a/chain/cosmos/codec.go +++ b/chain/cosmos/codec.go @@ -25,6 +25,7 @@ import ( transfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" ibccore "github.com/cosmos/ibc-go/v8/modules/core" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ccvprovider "github.com/cosmos/interchain-security/v5/x/ccv/provider" ibcwasm "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos/08-wasm-types" ) @@ -50,6 +51,7 @@ func DefaultEncoding() testutil.TestEncodingConfig { ibccore.AppModuleBasic{}, ibctm.AppModuleBasic{}, ibcwasm.AppModuleBasic{}, + ccvprovider.AppModuleBasic{}, ) } diff --git a/chain/cosmos/cosmos_chain.go b/chain/cosmos/cosmos_chain.go index 9ca09faef..4c82ae709 100644 --- a/chain/cosmos/cosmos_chain.go +++ b/chain/cosmos/cosmos_chain.go @@ -5,13 +5,16 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" "io" "math" "os" + "path" "strconv" "strings" "sync" + "time" sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" @@ -23,12 +26,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" paramsutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" // nolint:staticcheck chanTypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ccvclient "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" dockertypes "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" + "github.com/icza/dyno" "github.com/strangelove-ventures/interchaintest/v8/blockdb" wasmtypes "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos/08-wasm-types" "github.com/strangelove-ventures/interchaintest/v8/chain/internal/tendermint" @@ -36,18 +42,25 @@ import ( "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/testutil" "go.uber.org/zap" + "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" ) +var ( + DefaultProviderUnbondingPeriod = 336 * time.Hour +) + // CosmosChain is a local docker testnet for a Cosmos SDK chain. // Implements the ibc.Chain interface. type CosmosChain struct { testName string cfg ibc.ChainConfig - numValidators int + NumValidators int numFullNodes int Validators ChainNodes FullNodes ChainNodes + Provider *CosmosChain + Consumers []*CosmosChain // Additional processes that need to be run on a per-chain basis. Sidecars SidecarProcesses @@ -108,7 +121,7 @@ func NewCosmosChain(testName string, chainConfig ibc.ChainConfig, numValidators return &CosmosChain{ testName: testName, cfg: chainConfig, - numValidators: numValidators, + NumValidators: numValidators, numFullNodes: numFullNodes, log: log, cdc: cdc, @@ -492,6 +505,15 @@ func (c *CosmosChain) QueryBankMetadata(ctx context.Context, denom string) (*Ban return c.getFullNode().QueryBankMetadata(ctx, denom) } +// ConsumerAdditionProposal submits a legacy governance proposal to add a consumer to the chain. +func (c *CosmosChain) ConsumerAdditionProposal(ctx context.Context, keyName string, prop ccvclient.ConsumerAdditionProposalJSON) (tx TxProposal, _ error) { + txHash, err := c.getFullNode().ConsumerAdditionProposal(ctx, keyName, prop) + if err != nil { + return tx, fmt.Errorf("failed to submit consumer addition proposal: %w", err) + } + return c.txProposal(txHash) +} + func (c *CosmosChain) txProposal(txHash string) (tx TxProposal, _ error) { txResp, err := c.GetTransaction(txHash) if err != nil { @@ -709,13 +731,13 @@ func (c *CosmosChain) initializeChainNodes( c.pullImages(ctx, cli) image := chainCfg.Images[0] - newVals := make(ChainNodes, c.numValidators) + newVals := make(ChainNodes, c.NumValidators) copy(newVals, c.Validators) newFullNodes := make(ChainNodes, c.numFullNodes) copy(newFullNodes, c.FullNodes) eg, egCtx := errgroup.WithContext(ctx) - for i := len(c.Validators); i < c.numValidators; i++ { + for i := len(c.Validators); i < c.NumValidators; i++ { i := i eg.Go(func() error { val, err := c.NewChainNode(egCtx, testName, cli, networkID, image, true, i) @@ -1022,6 +1044,323 @@ func (c *CosmosChain) Start(testName string, ctx context.Context, additionalGene return testutil.WaitForBlocks(ctx, 2, c.getFullNode()) } +// Bootstraps the provider chain and starts it from genesis +func (c *CosmosChain) StartProvider(testName string, ctx context.Context, additionalGenesisWallets ...ibc.WalletAmount) error { + existingFunc := c.cfg.ModifyGenesis + c.cfg.ModifyGenesis = func(cc ibc.ChainConfig, b []byte) ([]byte, error) { + var err error + b, err = ModifyGenesis([]GenesisKV{ + NewGenesisKV("app_state.gov.params.voting_period", "10s"), + NewGenesisKV("app_state.gov.params.max_deposit_period", "10s"), + NewGenesisKV("app_state.gov.params.min_deposit.0.denom", c.cfg.Denom), + })(cc, b) + if err != nil { + return nil, err + } + if existingFunc != nil { + return existingFunc(cc, b) + } + return b, nil + } + + const proposerKeyName = "proposer" + if err := c.CreateKey(ctx, proposerKeyName); err != nil { + return fmt.Errorf("failed to add proposer key: %s", err) + } + + proposerAddr, err := c.getFullNode().AccountKeyBech32(ctx, proposerKeyName) + if err != nil { + return fmt.Errorf("failed to get proposer key: %s", err) + } + + proposer := ibc.WalletAmount{ + Address: proposerAddr, + Denom: c.cfg.Denom, + Amount: sdkmath.NewInt(10_000_000_000_000), + } + + additionalGenesisWallets = append(additionalGenesisWallets, proposer) + + if err := c.Start(testName, ctx, additionalGenesisWallets...); err != nil { + return err + } + + trustingPeriod, err := time.ParseDuration(c.cfg.TrustingPeriod) + if err != nil { + return fmt.Errorf("failed to parse trusting period in 'StartProvider': %w", err) + } + + for _, consumer := range c.Consumers { + prop := ccvclient.ConsumerAdditionProposalJSON{ + Title: fmt.Sprintf("Addition of %s consumer chain", consumer.cfg.Name), + Summary: "Proposal to add new consumer chain", + ChainId: consumer.cfg.ChainID, + InitialHeight: clienttypes.Height{RevisionNumber: clienttypes.ParseChainID(consumer.cfg.ChainID), RevisionHeight: 1}, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: time.Now(), // Client on provider tracking consumer will be created as soon as proposal passes + + // TODO fetch or default variables + BlocksPerDistributionTransmission: 1000, + CcvTimeoutPeriod: 2419200000000000, + TransferTimeoutPeriod: 3600000000000, + ConsumerRedistributionFraction: "0.75", + HistoricalEntries: 10000, + UnbondingPeriod: trustingPeriod, + Deposit: "100000000" + c.cfg.Denom, + } + + height, err := c.Height(ctx) + if err != nil { + return fmt.Errorf("failed to query provider height before consumer addition proposal: %w", err) + } + + propTx, err := c.ConsumerAdditionProposal(ctx, proposerKeyName, prop) + if err != nil { + return err + } + + propID, err := strconv.ParseUint(propTx.ProposalID, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse proposal id: %w", err) + } + + if err := c.VoteOnProposalAllValidators(ctx, propID, ProposalVoteYes); err != nil { + return err + } + + _, err = PollForProposalStatus(ctx, c, height, height+10, propID, govv1beta1.StatusPassed) + if err != nil { + return fmt.Errorf("proposal status did not change to passed in expected number of blocks: %w", err) + } + } + + return nil +} + +const ( + icsVer330 = "v3.3.0" + icsVer400 = "v4.0.0" +) + +func (c *CosmosChain) transformCCVState(ctx context.Context, ccvState []byte, consumerVersion, providerVersion string, icsCfg ibc.ICSConfig) ([]byte, error) { + // If they're both under 3.3.0, or if they're the same version, we don't need to transform the state. + if semver.MajorMinor(providerVersion) == semver.MajorMinor(consumerVersion) || + (semver.Compare(providerVersion, icsVer330) < 0 && semver.Compare(consumerVersion, icsVer330) < 0) { + return ccvState, nil + } + var imageVersion, toVersion string + // The trick here is that when we convert the state to a consumer < 3.3.0, we need a converter that knows about that version; those are >= 4.0.0, and need a --to flag. + // Other than that, this is a question of using whichever version is newer. If it's the provider's, we need a --to flag to tell it the consumer version. + // If it's the consumer's, we don't need a --to flag cause it'll assume the consumer version. + if semver.Compare(providerVersion, icsVer330) >= 0 && semver.Compare(providerVersion, consumerVersion) > 0 { + imageVersion = icsVer400 + if semver.Compare(providerVersion, icsVer400) > 0 { + imageVersion = providerVersion + } + toVersion = semver.Major(consumerVersion) + if toVersion == "v3" { + toVersion = semver.MajorMinor(consumerVersion) + } + } else { + imageVersion = consumerVersion + } + + if icsCfg.ProviderVerOverride != "" { + imageVersion = icsCfg.ProviderVerOverride + } + if icsCfg.ConsumerVerOverride != "" { + toVersion = icsCfg.ConsumerVerOverride + } + + c.log.Info("Transforming CCV state", zap.String("provider", providerVersion), zap.String("consumer", consumerVersion), zap.String("imageVersion", imageVersion), zap.String("toVersion", toVersion)) + + err := c.GetNode().WriteFile(ctx, ccvState, "ccvconsumer.json") + if err != nil { + return nil, fmt.Errorf("failed to write ccv state to file: %w", err) + } + job := dockerutil.NewImage(c.log, c.GetNode().DockerClient, c.GetNode().NetworkID, + c.GetNode().TestName, "ghcr.io/strangelove-ventures/heighliner/ics", imageVersion, + ) + cmd := []string{"interchain-security-cd", "genesis", "transform"} + if toVersion != "" { + cmd = append(cmd, "--to", toVersion+".x") + } + cmd = append(cmd, path.Join(c.GetNode().HomeDir(), "ccvconsumer.json")) + res := job.Run(ctx, cmd, dockerutil.ContainerOptions{Binds: c.GetNode().Bind()}) + if res.Err != nil { + return nil, fmt.Errorf("failed to transform ccv state: %w", res.Err) + } + return res.Stdout, nil +} + +// Bootstraps the consumer chain and starts it from genesis +func (c *CosmosChain) StartConsumer(testName string, ctx context.Context, additionalGenesisWallets ...ibc.WalletAmount) error { + chainCfg := c.Config() + + configFileOverrides := chainCfg.ConfigFileOverrides + + eg := new(errgroup.Group) + // Initialize validators and fullnodes. + for _, v := range c.Nodes() { + v := v + eg.Go(func() error { + if err := v.InitFullNodeFiles(ctx); err != nil { + return err + } + for configFile, modifiedConfig := range configFileOverrides { + modifiedToml, ok := modifiedConfig.(testutil.Toml) + if !ok { + return fmt.Errorf("provided toml override for file %s is of type (%T). Expected (DecodedToml)", configFile, modifiedConfig) + } + if err := testutil.ModifyTomlConfigFile( + ctx, + v.logger(), + v.DockerClient, + v.TestName, + v.VolumeName, + configFile, + modifiedToml, + ); err != nil { + return err + } + } + return nil + }) + } + + // wait for this to finish + if err := eg.Wait(); err != nil { + return err + } + + // Copy provider priv val keys to these nodes + for i, val := range c.Provider.Validators { + privVal, err := val.PrivValFileContent(ctx) + if err != nil { + return err + } + if err := c.Validators[i].OverwritePrivValFile(ctx, privVal); err != nil { + return err + } + } + + if c.cfg.PreGenesis != nil { + err := c.cfg.PreGenesis(chainCfg) + if err != nil { + return err + } + } + + validator0 := c.Validators[0] + + for _, wallet := range additionalGenesisWallets { + if err := validator0.AddGenesisAccount(ctx, wallet.Address, []types.Coin{{Denom: wallet.Denom, Amount: sdkmath.NewInt(wallet.Amount.Int64())}}); err != nil { + return err + } + } + + genbz, err := validator0.GenesisFileContent(ctx) + if err != nil { + return err + } + + ccvStateMarshaled, _, err := c.Provider.GetNode().ExecQuery(ctx, "provider", "consumer-genesis", c.cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to query provider for ccv state: %w", err) + } + c.log.Info("BEFORE MIGRATION!", zap.String("GEN", string(ccvStateMarshaled))) + + consumerICS := c.GetNode().ICSVersion(ctx) + providerICS := c.Provider.GetNode().ICSVersion(ctx) + ccvStateMarshaled, err = c.transformCCVState(ctx, ccvStateMarshaled, consumerICS, providerICS, chainCfg.InterchainSecurityConfig) + c.log.Info("HERE STATE!", zap.String("GEN", string(ccvStateMarshaled))) + if err != nil { + return fmt.Errorf("failed to marshal ccv state to json: %w", err) + } + + var ccvStateUnmarshaled interface{} + if err := json.Unmarshal(ccvStateMarshaled, &ccvStateUnmarshaled); err != nil { + return fmt.Errorf("failed to unmarshal ccv state json: %w", err) + } + + var genesisJson interface{} + if err := json.Unmarshal(genbz, &genesisJson); err != nil { + return fmt.Errorf("failed to unmarshal genesis json: %w", err) + } + + if err := dyno.Set(genesisJson, ccvStateUnmarshaled, "app_state", "ccvconsumer"); err != nil { + return fmt.Errorf("failed to populate ccvconsumer genesis state: %w", err) + } + + if genbz, err = json.Marshal(genesisJson); err != nil { + return fmt.Errorf("failed to marshal genesis bytes to json: %w", err) + } + + genbz = bytes.ReplaceAll(genbz, []byte(`"stake"`), []byte(fmt.Sprintf(`"%s"`, chainCfg.Denom))) + + if c.cfg.ModifyGenesis != nil { + genbz, err = c.cfg.ModifyGenesis(chainCfg, genbz) + if err != nil { + return err + } + } + + // Provide EXPORT_GENESIS_FILE_PATH and EXPORT_GENESIS_CHAIN to help debug genesis file + exportGenesis := os.Getenv("EXPORT_GENESIS_FILE_PATH") + exportGenesisChain := os.Getenv("EXPORT_GENESIS_CHAIN") + if exportGenesis != "" && exportGenesisChain == c.cfg.Name { + c.log.Debug("Exporting genesis file", + zap.String("chain", exportGenesisChain), + zap.String("path", exportGenesis), + ) + _ = os.WriteFile(exportGenesis, genbz, 0600) + } + + chainNodes := c.Nodes() + + for _, cn := range chainNodes { + if err := cn.OverwriteGenesisFile(ctx, genbz); err != nil { + return err + } + } + + if err := chainNodes.LogGenesisHashes(ctx); err != nil { + return err + } + + eg, egCtx := errgroup.WithContext(ctx) + for _, n := range chainNodes { + n := n + eg.Go(func() error { + return n.CreateNodeContainer(egCtx) + }) + } + if err := eg.Wait(); err != nil { + return err + } + + peers := chainNodes.PeerString(ctx) + + eg, egCtx = errgroup.WithContext(ctx) + for _, n := range chainNodes { + n := n + c.log.Info("Starting container", zap.String("container", n.Name())) + eg.Go(func() error { + if err := n.SetPeers(egCtx, peers); err != nil { + return err + } + return n.StartContainer(egCtx) + }) + } + if err := eg.Wait(); err != nil { + return err + } + + // Wait for 5 blocks before considering the chains "started" + return testutil.WaitForBlocks(ctx, 5, c.getFullNode()) +} + // Height implements ibc.Chain func (c *CosmosChain) Height(ctx context.Context) (int64, error) { return c.getFullNode().Height(ctx) @@ -1203,18 +1542,13 @@ func (c *CosmosChain) StartAllValSidecars(ctx context.Context) error { return eg.Wait() } -func (c *CosmosChain) VoteOnProposalAllValidators(ctx context.Context, proposalID string, vote string) error { - propID, err := strconv.ParseUint(proposalID, 10, 64) - if err != nil { - return fmt.Errorf("failed to parse proposalID %s: %w", proposalID, err) - } - +func (c *CosmosChain) VoteOnProposalAllValidators(ctx context.Context, proposalID uint64, vote string) error { var eg errgroup.Group for _, n := range c.Nodes() { if n.Validator { n := n eg.Go(func() error { - return n.VoteOnProposal(ctx, valKey, propID, vote) + return n.VoteOnProposal(ctx, valKey, proposalID, vote) }) } } diff --git a/chainset.go b/chainset.go index 9294302d2..d5fd79bb3 100644 --- a/chainset.go +++ b/chainset.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/client" "github.com/strangelove-ventures/interchaintest/v8/blockdb" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v8/ibc" "go.uber.org/multierr" "go.uber.org/zap" @@ -106,15 +107,50 @@ func (cs *chainSet) Start(ctx context.Context, testName string, additionalGenesi for c := range cs.chains { c := c + if cosmosChain, ok := c.(*cosmos.CosmosChain); ok && cosmosChain.Provider != nil { + // wait for provider chains to be started up first + continue + } eg.Go(func() error { + chainCfg := c.Config() + if cosmosChain, ok := c.(*cosmos.CosmosChain); ok { + if len(cosmosChain.Consumers) > 0 { + // this is a provider chain + if err := cosmosChain.StartProvider(testName, egCtx, additionalGenesisWallets[c]...); err != nil { + return fmt.Errorf("failed to start provider chain %s: %w", chainCfg.Name, err) + } + return nil + } + } + + // standard chain startup if err := c.Start(testName, egCtx, additionalGenesisWallets[c]...); err != nil { - return fmt.Errorf("failed to start chain %s: %w", c.Config().Name, err) + return fmt.Errorf("failed to start chain %s: %w", chainCfg.Name, err) } return nil }) } + if err := eg.Wait(); err != nil { + return err + } + + eg, egCtx = errgroup.WithContext(ctx) + // Now startup any consumer chains + for c := range cs.chains { + c := c + if cosmosChain, ok := c.(*cosmos.CosmosChain); ok && cosmosChain.Provider != nil { + eg.Go(func() error { + // this is a consumer chain + if err := cosmosChain.StartConsumer(testName, egCtx, additionalGenesisWallets[c]...); err != nil { + return fmt.Errorf("failed to start consumer chain %s: %w", c.Config().Name, err) + } + + return nil + }) + } + } return eg.Wait() } diff --git a/configuredChains.yaml b/configuredChains.yaml index bf2997cd9..ef3aa1745 100644 --- a/configuredChains.yaml +++ b/configuredChains.yaml @@ -9,7 +9,7 @@ agoric: bech32-prefix: agoric denom: urun gas-prices: 0.01urun - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 564 trusting-period: 672h images: @@ -24,7 +24,7 @@ akash: bech32-prefix: akash denom: uakt gas-prices: 0.01uakt - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/akash @@ -38,7 +38,7 @@ arkeo: bech32-prefix: arkeo denom: uarkeo gas-prices: 0.01uarkeo - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/arkeo @@ -52,7 +52,7 @@ axelar: bech32-prefix: axelar denom: uaxl gas-prices: 0.01uaxl - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 168h images: - repository: ghcr.io/strangelove-ventures/heighliner/axelar @@ -66,7 +66,7 @@ bitcanna: bech32-prefix: bcna denom: ubcna gas-prices: 0.01ubcna - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/bitcanna @@ -80,7 +80,7 @@ bitsong: bech32-prefix: bitsong denom: ubtsg gas-prices: 0.01ubtsg - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 639 trusting-period: 504h images: @@ -95,7 +95,7 @@ bostrom: bech32-prefix: bostrom denom: boot gas-prices: 0.01boot - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 192h images: - repository: ghcr.io/strangelove-ventures/heighliner/bostrom @@ -109,7 +109,7 @@ carbon: bech32-prefix: swth denom: swth gas-prices: 0.01swth - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 720h images: - repository: ghcr.io/strangelove-ventures/heighliner/carbon @@ -123,7 +123,7 @@ cerberus: bech32-prefix: cerberus denom: ucrbrus gas-prices: 0.01ucrbrus - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336.667h images: - repository: ghcr.io/strangelove-ventures/heighliner/cerberus @@ -137,7 +137,7 @@ cheqd: bech32-prefix: cheqd denom: ncheq gas-prices: 0.01ncheq - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336.111h images: - repository: ghcr.io/strangelove-ventures/heighliner/cheqd @@ -151,7 +151,7 @@ chihuahua: bech32-prefix: chihuahua denom: uhuahua gas-prices: 0.01uhuahua - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/chihuahua @@ -165,7 +165,7 @@ comdex: bech32-prefix: comdex denom: ucmdx gas-prices: 0.01ucmdx - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/comdex @@ -194,7 +194,7 @@ crescent: bech32-prefix: cre denom: ucre gas-prices: 0.01ucre - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/crescent @@ -208,7 +208,7 @@ cronos: bech32-prefix: crc denom: stake gas-prices: 0.01stake - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 60 trusting-period: 672h images: @@ -223,7 +223,7 @@ cryptoorgchain: bech32-prefix: cro denom: basecro gas-prices: 0.01basecro - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 394 trusting-period: 672h images: @@ -238,7 +238,7 @@ decentr: bech32-prefix: decentr denom: udec gas-prices: 0.01udec - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/decentr @@ -252,7 +252,7 @@ desmos: bech32-prefix: desmos denom: udsm gas-prices: 0.01udsm - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 852 trusting-period: 336h images: @@ -267,7 +267,7 @@ dig: bech32-prefix: dig denom: udig gas-prices: 0.01udig - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/dig @@ -281,7 +281,7 @@ emoney: bech32-prefix: emoney denom: ungm gas-prices: 0.01ungm - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/emoney @@ -295,7 +295,7 @@ evmos: bech32-prefix: evmos denom: aevmos gas-prices: 0.01aevmos - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 60 trusting-period: 336h images: @@ -310,7 +310,7 @@ fetchhub: bech32-prefix: fetch denom: afet gas-prices: 0.01afet - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/fetchhub @@ -324,7 +324,7 @@ firmachain: bech32-prefix: firma denom: ufct gas-prices: 0.01ufct - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 7777777 trusting-period: 504h images: @@ -339,7 +339,7 @@ gaia: bech32-prefix: cosmos denom: uatom gas-prices: 0.01uatom - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/gaia @@ -353,7 +353,7 @@ gravitybridge: bech32-prefix: gravity denom: ugraviton gas-prices: 0.01ugraviton - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/gravitybridge @@ -402,6 +402,20 @@ icad: uid-gid: 1025:1025 no-host-mount: false +ics-consumer: + name: ics-consumer + type: cosmos + bin: interchain-security-cd + bech32-prefix: cosmos + denom: stake + gas-prices: 0.0stake + gas-adjustment: 1.1 + trusting-period: 96h + images: + - repository: ghcr.io/strangelove-ventures/heighliner/ics + uid-gid: 1025:1025 + no-host-mount: false + ics-provider: name: ics-provider type: cosmos @@ -423,7 +437,7 @@ impacthub: bech32-prefix: ixo denom: uixo gas-prices: 0.01uixo - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/impacthub @@ -437,7 +451,7 @@ injective: bech32-prefix: inj denom: inj gas-prices: 0.01inj - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 60 trusting-period: 504h images: @@ -452,7 +466,7 @@ irisnet: bech32-prefix: iaa denom: uiris gas-prices: 0.01uiris - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/irisnet @@ -466,7 +480,7 @@ juno: bech32-prefix: juno denom: ujuno gas-prices: 0.0025ujuno - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 672h images: - repository: ghcr.io/strangelove-ventures/heighliner/juno @@ -480,7 +494,7 @@ kichain: bech32-prefix: ki denom: uxki gas-prices: 0.01uxki - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 672h images: - repository: ghcr.io/strangelove-ventures/heighliner/kichain @@ -494,7 +508,7 @@ konstellation: bech32-prefix: darc denom: udarc gas-prices: 0.01udarc - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 672h images: - repository: ghcr.io/strangelove-ventures/heighliner/konstellation @@ -508,7 +522,7 @@ kujira: bech32-prefix: kujira denom: ukuji gas-prices: 0.01ukuji - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/kujira @@ -522,7 +536,7 @@ likecoin: bech32-prefix: like denom: nanolike gas-prices: 0.01nanolike - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/likecoin @@ -536,7 +550,7 @@ lumnetwork: bech32-prefix: lum denom: ulum gas-prices: 0.01ulum - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 504h images: - repository: ghcr.io/strangelove-ventures/heighliner/lum @@ -550,7 +564,7 @@ neutron: bech32-prefix: neutron denom: untrn gas-prices: 0.01untrn - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/neutron @@ -564,7 +578,7 @@ omniflixhub: bech32-prefix: omniflix denom: uflix gas-prices: 0.01uflix - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/omniflix @@ -578,7 +592,7 @@ osmosis: bech32-prefix: osmo denom: uosmo gas-prices: 0.0025uosmo - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 336h images: - repository: ghcr.io/strangelove-ventures/heighliner/osmosis @@ -592,7 +606,7 @@ panacea: bech32-prefix: panacea denom: umed gas-prices: 0.01umed - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 371 trusting-period: "504h" images: @@ -607,7 +621,7 @@ penumbra: bech32-prefix: penumbrav2t denom: upenumbra gas-prices: 0.0upenumbra - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: 672h images: - repository: ghcr.io/strangelove-ventures/heighliner/tendermint @@ -622,7 +636,7 @@ persistence: bech32-prefix: persistence denom: uxprt gas-prices: 0.01uxprt - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 750 trusting-period: "504h" images: @@ -637,7 +651,7 @@ provenance: bech32-prefix: pb denom: nhash gas-prices: 0.01nhash - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 505 trusting-period: "504h" images: @@ -652,7 +666,7 @@ regen: bech32-prefix: regen denom: uregen gas-prices: 0.01uregen - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/regen @@ -666,7 +680,7 @@ rizon: bech32-prefix: rizon denom: uatolo gas-prices: 0.01uatolo - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/rizon @@ -680,7 +694,7 @@ secretnetwork: bech32-prefix: secret denom: uscrt gas-prices: 0.01uscrt - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 529 trusting-period: "504h" images: @@ -695,7 +709,7 @@ sentinel: bech32-prefix: sent denom: udvpn gas-prices: 0.01udvpn - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "672h" images: - repository: ghcr.io/strangelove-ventures/heighliner/sentinel @@ -709,7 +723,7 @@ shentu: bech32-prefix: certik denom: uctk gas-prices: 0.01uctk - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/shentu @@ -723,7 +737,7 @@ sifchain: bech32-prefix: sif denom: rowan gas-prices: 0.01rowan - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/sifchain @@ -737,7 +751,7 @@ sommelier: bech32-prefix: somm denom: usomm gas-prices: 0.01usomm - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "672h" images: - repository: ghcr.io/strangelove-ventures/heighliner/sommelier @@ -751,7 +765,7 @@ stargaze: bech32-prefix: stars denom: ustars gas-prices: 0.01ustars - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "336h" images: - repository: ghcr.io/strangelove-ventures/heighliner/stargaze @@ -765,7 +779,7 @@ starname: bech32-prefix: star denom: uiov gas-prices: 0.01uiov - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 234 trusting-period: "504h" images: @@ -780,7 +794,7 @@ stride: bech32-prefix: stride denom: ustrd gas-prices: 0.01ustrd - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "336h" images: - repository: ghcr.io/strangelove-ventures/heighliner/stride @@ -794,7 +808,7 @@ terpnetwork: bech32-prefix: terp denom: uterp gas-prices: 0.01uterp - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "504h" images: - repository: ghcr.io/strangelove-ventures/heighliner/terpnetwork @@ -808,7 +822,7 @@ terra: bech32-prefix: terra denom: uluna gas-prices: 0.01uluna - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 330 trusting-period: "336h" images: @@ -823,7 +837,7 @@ umee: bech32-prefix: umee denom: uumee gas-prices: 0.01uumee - gas-adjustment: 1.3 + gas-adjustment: 2.0 trusting-period: "336h" images: - repository: ghcr.io/strangelove-ventures/heighliner/umee @@ -837,7 +851,7 @@ vidulum: bech32-prefix: vdl denom: uvdl gas-prices: 0.01uvdl - gas-adjustment: 1.3 + gas-adjustment: 2.0 coin-type: 370 trusting-period: "504h" images: diff --git a/examples/cosmos/chain_core_test.go b/examples/cosmos/chain_core_test.go index 3cb60f134..795ba8078 100644 --- a/examples/cosmos/chain_core_test.go +++ b/examples/cosmos/chain_core_test.go @@ -558,7 +558,7 @@ func testGov(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users require.EqualValues(t, v.Options[0].Option, govv1.VoteOption_VOTE_OPTION_YES) // pass vote with all validators - err = chain.VoteOnProposalAllValidators(ctx, "1", "yes") + err = chain.VoteOnProposalAllValidators(ctx, 1, "yes") require.NoError(t, err) // GovQueryProposalsV1 diff --git a/examples/cosmos/chain_param_change_test.go b/examples/cosmos/chain_param_change_test.go index d49713f4b..65a6c9222 100644 --- a/examples/cosmos/chain_param_change_test.go +++ b/examples/cosmos/chain_param_change_test.go @@ -74,14 +74,14 @@ func CosmosChainParamChangeTest(t *testing.T, name, version string) { paramTx, err := chain.ParamChangeProposal(ctx, chainUser.KeyName(), ¶m_change) require.NoError(t, err, "error submitting param change proposal tx") - err = chain.VoteOnProposalAllValidators(ctx, paramTx.ProposalID, cosmos.ProposalVoteYes) + propId, err := strconv.ParseUint(paramTx.ProposalID, 10, 64) + require.NoError(t, err, "failed to convert proposal ID to uint64") + + err = chain.VoteOnProposalAllValidators(ctx, propId, cosmos.ProposalVoteYes) require.NoError(t, err, "failed to submit votes") height, _ := chain.Height(ctx) - propId, err := strconv.ParseUint(paramTx.ProposalID, 10, 64) - require.NoError(t, err, "failed to convert proposal ID to uint64") - _, err = cosmos.PollForProposalStatus(ctx, chain, height, height+10, propId, govv1beta1.StatusPassed) require.NoError(t, err, "proposal status did not change to passed in expected number of blocks") diff --git a/examples/cosmos/chain_upgrade_ibc_test.go b/examples/cosmos/chain_upgrade_ibc_test.go index fc62f0d77..8d2ba6ec3 100644 --- a/examples/cosmos/chain_upgrade_ibc_test.go +++ b/examples/cosmos/chain_upgrade_ibc_test.go @@ -131,12 +131,12 @@ func CosmosChainUpgradeIBCTest(t *testing.T, chainName, initialVersion, upgradeC upgradeTx, err := chain.UpgradeProposal(ctx, chainUser.KeyName(), proposal) require.NoError(t, err, "error submitting software upgrade proposal tx") - err = chain.VoteOnProposalAllValidators(ctx, upgradeTx.ProposalID, cosmos.ProposalVoteYes) - require.NoError(t, err, "failed to submit votes") - propId, err := strconv.ParseUint(upgradeTx.ProposalID, 10, 64) require.NoError(t, err, "failed to convert proposal ID to uint64") + err = chain.VoteOnProposalAllValidators(ctx, propId, cosmos.ProposalVoteYes) + require.NoError(t, err, "failed to submit votes") + _, err = cosmos.PollForProposalStatus(ctx, chain, height, height+haltHeightDelta, propId, govv1beta1.StatusPassed) require.NoError(t, err, "proposal status did not change to passed in expected number of blocks") diff --git a/examples/hyperspace/hyperspace_test.go b/examples/hyperspace/hyperspace_test.go index 0dd738aad..615f80245 100644 --- a/examples/hyperspace/hyperspace_test.go +++ b/examples/hyperspace/hyperspace_test.go @@ -360,12 +360,12 @@ func pushWasmContractViaGov(t *testing.T, ctx context.Context, cosmosChain *cosm height, err := cosmosChain.Height(ctx) require.NoError(t, err, "error fetching height before submit upgrade proposal") - err = cosmosChain.VoteOnProposalAllValidators(ctx, proposalTx.ProposalID, cosmos.ProposalVoteYes) - require.NoError(t, err, "failed to submit votes") - propId, err := strconv.ParseUint(proposalTx.ProposalID, 10, 64) require.NoError(t, err, "failed to convert proposal ID to uint64") + err = cosmosChain.VoteOnProposalAllValidators(ctx, propId, cosmos.ProposalVoteYes) + require.NoError(t, err, "failed to submit votes") + _, err = cosmos.PollForProposalStatus(ctx, cosmosChain, height, height+heightDelta, propId, govv1beta1.StatusPassed) require.NoError(t, err, "proposal status did not change to passed in expected number of blocks") diff --git a/examples/ibc/ics_test.go b/examples/ibc/ics_test.go new file mode 100644 index 000000000..f7cc04c5c --- /dev/null +++ b/examples/ibc/ics_test.go @@ -0,0 +1,87 @@ +package ibc_test + +import ( + "context" + "fmt" + "testing" + "time" + + interchaintest "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testreporter" + "github.com/strangelove-ventures/interchaintest/v8/testutil" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +// This tests Cosmos Interchain Security, spinning up a provider and a single consumer chain. +func TestICS(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + t.Parallel() + + ctx := context.Background() + + vals := 1 + fNodes := 0 + + // Chain Factory + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ + { + Name: "ics-provider", Version: "v3.1.0", + NumValidators: &vals, NumFullNodes: &fNodes, + ChainConfig: ibc.ChainConfig{GasAdjustment: 1.5}}, + { + Name: "ics-consumer", Version: "v3.1.0", + NumValidators: &vals, NumFullNodes: &fNodes, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + provider, consumer := chains[0], chains[1] + + // Relayer Factory + client, network := interchaintest.DockerSetup(t) + + r := interchaintest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t), + ).Build(t, client, network) + + // Prep Interchain + const ibcPath = "ics-path" + ic := interchaintest.NewInterchain(). + AddChain(provider). + AddChain(consumer). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(interchaintest.ProviderConsumerLink{ + Provider: provider, + Consumer: consumer, + Relayer: r, + Path: ibcPath, + }) + + // Log location + f, err := interchaintest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + err = ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: interchaintest.DefaultBlockDatabaseFilepath(), + + SkipPathCreation: false, + }) + require.NoError(t, err, "failed to build interchain") + + err = testutil.WaitForBlocks(ctx, 10, provider, consumer) + require.NoError(t, err, "failed to wait for blocks") +} diff --git a/examples/polkadot/push_wasm_client_code_test.go b/examples/polkadot/push_wasm_client_code_test.go index f7ce8265d..2233b1f8b 100644 --- a/examples/polkadot/push_wasm_client_code_test.go +++ b/examples/polkadot/push_wasm_client_code_test.go @@ -138,12 +138,12 @@ func TestPushWasmClientCode(t *testing.T) { height, err := simdChain.Height(ctx) require.NoError(t, err, "error fetching height before submit upgrade proposal") - err = simdChain.VoteOnProposalAllValidators(ctx, proposalTx.ProposalID, cosmos.ProposalVoteYes) - require.NoError(t, err, "failed to submit votes") - propId, err := strconv.ParseUint(proposalTx.ProposalID, 10, 64) require.NoError(t, err, "failed to convert proposal ID to uint64") + err = simdChain.VoteOnProposalAllValidators(ctx, propId, cosmos.ProposalVoteYes) + require.NoError(t, err, "failed to submit votes") + _, err = cosmos.PollForProposalStatus(ctx, simdChain, height, height+heightDelta, propId, govv1beta1.StatusPassed) require.NoError(t, err, "proposal status did not change to passed in expected number of blocks") diff --git a/go.mod b/go.mod index 3c22e264a..1656f7446 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/strangelove-ventures/interchaintest/v8 -go 1.21 - -toolchain go1.21.0 +go 1.22.2 replace ( github.com/ChainSafe/go-schnorrkel => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d @@ -29,6 +27,7 @@ require ( github.com/cosmos/gogoproto v1.4.12 github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.2.0 + github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.1 github.com/docker/docker v24.0.9+incompatible @@ -52,6 +51,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.22.0 + golang.org/x/mod v0.17.0 golang.org/x/sync v0.7.0 golang.org/x/tools v0.20.0 google.golang.org/grpc v1.63.2 @@ -71,6 +71,7 @@ require ( cosmossdk.io/depinject v1.0.0-alpha.4 // indirect cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/log v1.3.1 // indirect + cosmossdk.io/x/evidence v0.1.0 // indirect cosmossdk.io/x/tx v0.13.2 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -92,12 +93,13 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect + github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 // indirect github.com/cockroachdb/errors v1.11.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v1.1.0 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft-db v0.9.1 // indirect + github.com/cometbft/cometbft-db v0.10.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.2 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect @@ -243,7 +245,6 @@ require ( go.opentelemetry.io/otel/metric v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect - golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.19.0 // indirect diff --git a/go.sum b/go.sum index e38ca099c..5283849d1 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= @@ -363,8 +363,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft v0.38.6 h1:QSgpCzrGWJ2KUq1qpw+FCfASRpE27T6LQbfEHscdyOk= github.com/cometbft/cometbft v0.38.6/go.mod h1:8rSPxzUJYquCN8uuBgbUHOMg2KAwvr7CyUw+6ukO4nw= -github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= -github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/cometbft/cometbft-db v0.10.0 h1:VMBQh88zXn64jXVvj39tlu/IgsGR84T7ImjS523DCiU= +github.com/cometbft/cometbft-db v0.10.0/go.mod h1:7RR7NRv99j7keWJ5IkE9iZibUTKYdtepXTp7Ra0FxKk= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -397,6 +397,8 @@ github.com/cosmos/ibc-go/v8 v8.2.0 h1:7oCzyy1sZCcgpeQLnHxC56brsSz3KWwQGKXalXwXFz github.com/cosmos/ibc-go/v8 v8.2.0/go.mod h1:wj3qx75iC/XNnsMqbPDCIGs0G6Y3E/lo3bdqCyoCy+8= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= +github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74 h1:6atU/xizTL10q6EprP7oRuvfgUP2F6puvutnVoE+FRc= +github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74/go.mod h1:h/RkwOppo5AJj+1pkQyfjqU1MPdpohD/S6oEeAXpGZY= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -968,6 +970,8 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/oxyno-zeta/gomock-extra-matcher v1.2.0 h1:WPEclU0y0PMwUzdDcaKZvld4aXpa3fkzjiUMQdcBEHg= +github.com/oxyno-zeta/gomock-extra-matcher v1.2.0/go.mod h1:S0r7HmKeCGsHmvIVFMjKWwswb4+30nCNWbXRMBVPkaU= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1201,6 +1205,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= diff --git a/go.work b/go.work index e9cc53592..17e01f5e5 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.21 +go 1.22.2 use ( . diff --git a/ibc/relayer.go b/ibc/relayer.go index 5f49229c0..081490a2b 100644 --- a/ibc/relayer.go +++ b/ibc/relayer.go @@ -41,8 +41,8 @@ type Relayer interface { // setup channels, connections, and clients LinkPath(ctx context.Context, rep RelayerExecReporter, pathName string, channelOpts CreateChannelOptions, clientOptions CreateClientOptions) error - // update path channel filter - UpdatePath(ctx context.Context, rep RelayerExecReporter, pathName string, filter ChannelFilter) error + // update path channel filter or src/dst clients, conns, or chain IDs + UpdatePath(ctx context.Context, rep RelayerExecReporter, pathName string, opts PathUpdateOptions) error // update clients, such as after new genesis UpdateClients(ctx context.Context, rep RelayerExecReporter, pathName string) error @@ -80,7 +80,7 @@ type Relayer interface { // CreateClient performs the client handshake steps necessary for creating a light client // on src that tracks the state of dst. // Unlike CreateClients, this only creates the client on the destination chain - CreateClient(ctx context.Context, rep RelayerExecReporter, srcChainID, dstChainID, pathName string, opts CreateClientOptions) error + CreateClient(ctx context.Context, rep RelayerExecReporter, srcChainID string, dstChainID string, pathName string, opts CreateClientOptions) error // CreateConnections performs the connection handshake steps necessary for creating a connection // between the src and dst chains. diff --git a/ibc/types.go b/ibc/types.go index cf1941d9a..af7c662e0 100644 --- a/ibc/types.go +++ b/ibc/types.go @@ -62,6 +62,8 @@ type ChainConfig struct { UsingChainIDFlagCLI bool `yaml:"using-chain-id-flag-cli"` // Configuration describing additional sidecar processes. SidecarConfigs []SidecarConfig + // Configuration describing additional interchain security options. + InterchainSecurityConfig ICSConfig // CoinDecimals for the chains base micro/nano/atto token configuration. CoinDecimals *int64 // HostPortOverride exposes ports to the host. @@ -217,6 +219,10 @@ func (c ChainConfig) MergeChainSpecConfig(other ChainConfig) ChainConfig { c.ExposeAdditionalPorts = append(c.ExposeAdditionalPorts, other.ExposeAdditionalPorts...) } + if other.InterchainSecurityConfig != (ICSConfig{}) { + c.InterchainSecurityConfig = other.InterchainSecurityConfig + } + return c } @@ -392,3 +398,18 @@ type ChannelFilter struct { Rule string ChannelList []string } + +type PathUpdateOptions struct { + ChannelFilter *ChannelFilter + SrcClientID *string + SrcConnID *string + SrcChainID *string + DstClientID *string + DstConnID *string + DstChainID *string +} + +type ICSConfig struct { + ProviderVerOverride string + ConsumerVerOverride string +} diff --git a/interchain.go b/interchain.go index 33fa5ee57..87112e02d 100644 --- a/interchain.go +++ b/interchain.go @@ -6,6 +6,7 @@ import ( "cosmossdk.io/math" "github.com/docker/docker/client" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/testreporter" "go.uber.org/zap" @@ -26,6 +27,9 @@ type Interchain struct { // Key: relayer and path name; Value: the two chains being linked. links map[relayerPath]interchainLink + // Key: relayer and path name; Value: the provider and consumer chain link. + providerConsumerLinks map[relayerPath]providerConsumerLink + // Set to true after Build is called once. built bool @@ -53,6 +57,20 @@ type interchainLink struct { createChannelOpts ibc.CreateChannelOptions } +type providerConsumerLink struct { + provider, consumer ibc.Chain + + // If set, these options will be used when creating the client in the path link step. + // If a zero value initialization is used, e.g. CreateClientOptions{}, + // then the default values will be used via ibc.DefaultClientOpts. + createClientOpts ibc.CreateClientOptions + + // If set, these options will be used when creating the channel in the path link step. + // If a zero value initialization is used, e.g. CreateChannelOptions{}, + // then the default values will be used via ibc.DefaultChannelOpts. + createChannelOpts ibc.CreateChannelOptions +} + // NewInterchain returns a new Interchain. // // Typical usage involves multiple calls to AddChain, one or more calls to AddRelayer, @@ -64,7 +82,8 @@ func NewInterchain() *Interchain { chains: make(map[ibc.Chain]string), relayers: make(map[ibc.Relayer]string), - links: make(map[relayerPath]interchainLink), + links: make(map[relayerPath]interchainLink), + providerConsumerLinks: make(map[relayerPath]providerConsumerLink), } } @@ -131,6 +150,43 @@ func (ic *Interchain) AddRelayer(relayer ibc.Relayer, name string) *Interchain { return ic } +// AddLink adds the given link to the Interchain. +// If any validation fails, AddLink panics. +func (ic *Interchain) AddProviderConsumerLink(link ProviderConsumerLink) *Interchain { + if _, exists := ic.chains[link.Provider]; !exists { + cfg := link.Provider.Config() + panic(fmt.Errorf("chain with name=%s and id=%s was never added to Interchain", cfg.Name, cfg.ChainID)) + } + if _, exists := ic.chains[link.Consumer]; !exists { + cfg := link.Consumer.Config() + panic(fmt.Errorf("chain with name=%s and id=%s was never added to Interchain", cfg.Name, cfg.ChainID)) + } + if _, exists := ic.relayers[link.Relayer]; !exists { + panic(fmt.Errorf("relayer %v was never added to Interchain", link.Relayer)) + } + + if link.Provider == link.Consumer { + panic(fmt.Errorf("chains must be different (both were %v)", link.Provider)) + } + + key := relayerPath{ + Relayer: link.Relayer, + Path: link.Path, + } + + if _, exists := ic.providerConsumerLinks[key]; exists { + panic(fmt.Errorf("relayer %q already has a path named %q", key.Relayer, key.Path)) + } + + ic.providerConsumerLinks[key] = providerConsumerLink{ + provider: link.Provider, + consumer: link.Consumer, + createChannelOpts: link.CreateChannelOpts, + createClientOpts: link.CreateClientOpts, + } + return ic +} + // InterchainLink describes a link between two chains, // by specifying the chain names, the relayer name, // and the name of the path to create. @@ -155,6 +211,26 @@ type InterchainLink struct { CreateChannelOpts ibc.CreateChannelOptions } +type ProviderConsumerLink struct { + Provider, Consumer ibc.Chain + + // Relayer to use for link. + Relayer ibc.Relayer + + // Name of path to create. + Path string + + // If set, these options will be used when creating the client in the path link step. + // If a zero value initialization is used, e.g. CreateClientOptions{}, + // then the default values will be used via ibc.DefaultClientOpts. + CreateClientOpts ibc.CreateClientOptions + + // If set, these options will be used when creating the channel in the path link step. + // If a zero value initialization is used, e.g. CreateChannelOptions{}, + // then the default values will be used via ibc.DefaultChannelOpts. + CreateChannelOpts ibc.CreateChannelOptions +} + // AddLink adds the given link to the Interchain. // If any validation fails, AddLink panics. func (ic *Interchain) AddLink(link InterchainLink) *Interchain { @@ -227,6 +303,15 @@ func (ic *Interchain) Build(ctx context.Context, rep *testreporter.RelayerExecRe } ic.cs = newChainSet(ic.log, chains) + // Consumer chains need to have the same number of validators as their provider. + // Consumer also needs reference to its provider chain. + for _, providerConsumerLink := range ic.providerConsumerLinks { + provider, consumer := providerConsumerLink.provider.(*cosmos.CosmosChain), providerConsumerLink.consumer.(*cosmos.CosmosChain) + consumer.NumValidators = provider.NumValidators + consumer.Provider = provider + provider.Consumers = append(provider.Consumers, consumer) + } + // Initialize the chains (pull docker images, etc.). if err := ic.cs.Initialize(ctx, opts.TestName, opts.Client, opts.NetworkID); err != nil { return fmt.Errorf("failed to initialize chains: %w", err) @@ -286,9 +371,135 @@ func (ic *Interchain) Build(ctx context.Context, rep *testreporter.RelayerExecRe } } + // For every provider consumer link, teach the relayer about the link and create the link. + for rp, link := range ic.providerConsumerLinks { + rp := rp + link := link + p := link.provider + c := link.consumer + + if err := rp.Relayer.GeneratePath(ctx, rep, c.Config().ChainID, p.Config().ChainID, rp.Path); err != nil { + return fmt.Errorf( + "failed to generate path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + } + + var eg errgroup.Group + + // Now link the paths in parallel + // Creates clients, connections, and channels for each link/path. + for rp, link := range ic.providerConsumerLinks { + rp := rp + link := link + p := link.provider + c := link.consumer + eg.Go(func() error { + // If the user specifies a zero value CreateClientOptions struct then we fall back to the default + // client options. + if link.createClientOpts == (ibc.CreateClientOptions{}) { + link.createClientOpts = ibc.DefaultClientOpts() + } + + // Check that the client creation options are valid and fully specified. + if err := link.createClientOpts.Validate(); err != nil { + return err + } + + // If the user specifies a zero value CreateChannelOptions struct then we fall back to the default + // channel options for an ics20 fungible token transfer channel. + if link.createChannelOpts == (ibc.CreateChannelOptions{}) { + link.createChannelOpts = ibc.DefaultChannelOpts() + } + + // Check that the channel creation options are valid and fully specified. + if err := link.createChannelOpts.Validate(); err != nil { + return err + } + + consumerClients, err := rp.Relayer.GetClients(ctx, rep, c.Config().ChainID) + if err != nil { + return fmt.Errorf( + "failed to fetch consumer clients while linking path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + var consumerClient *ibc.ClientOutput + for _, client := range consumerClients { + if client.ClientState.ChainID == p.Config().ChainID { + consumerClient = client + break + } + } + if consumerClient == nil { + return fmt.Errorf( + "consumer chain %s does not have a client tracking the provider chain %s for path %s on relayer %s", + ic.chains[c], ic.chains[p], rp.Path, rp.Relayer, + ) + } + consumerClientID := consumerClients[0].ClientID + + providerClients, err := rp.Relayer.GetClients(ctx, rep, p.Config().ChainID) + if err != nil { + return fmt.Errorf( + "failed to fetch provider clients while linking path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + var providerClient *ibc.ClientOutput + for _, client := range providerClients { + if client.ClientState.ChainID == c.Config().ChainID { + providerClient = client + break + } + } + if providerClient == nil { + return fmt.Errorf( + "provider chain %s does not have a client tracking the consumer chain %s for path %s on relayer %s", + ic.chains[p], ic.chains[c], rp.Path, rp.Relayer, + ) + } + providerClientID := providerClients[0].ClientID + + // Update relayer config with client IDs + if err := rp.Relayer.UpdatePath(ctx, rep, rp.Path, ibc.PathUpdateOptions{ + SrcClientID: &consumerClientID, + DstClientID: &providerClientID, + }); err != nil { + return fmt.Errorf( + "failed to update path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + + // Connection handshake + if err := rp.Relayer.CreateConnections(ctx, rep, rp.Path); err != nil { + return fmt.Errorf( + "failed to create connections on path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + + // Create the provider/consumer channel for relaying val set updates + if err := rp.Relayer.CreateChannel(ctx, rep, rp.Path, ibc.CreateChannelOptions{ + SourcePortName: "consumer", + DestPortName: "provider", + Order: ibc.Ordered, + Version: "1", + }); err != nil { + return fmt.Errorf( + "failed to create ccv channels on path %s on relayer %s between chains %s and %s: %w", + rp.Path, rp.Relayer, ic.chains[p], ic.chains[c], err, + ) + } + + return nil + }) + } + // Now link the paths in parallel // Creates clients, connections, and channels for each link/path. - var eg errgroup.Group for rp, link := range ic.links { rp := rp link := link @@ -463,6 +674,15 @@ func (ic *Interchain) relayerChains() map[ibc.Relayer][]ibc.Chain { uniq[r][link.chains[1]] = struct{}{} } + for rp, link := range ic.providerConsumerLinks { + r := rp.Relayer + if uniq[r] == nil { + uniq[r] = make(map[ibc.Chain]struct{}, 2) // Adding at least 2 chains per relayer. + } + uniq[r][link.provider] = struct{}{} + uniq[r][link.consumer] = struct{}{} + } + // Then convert the sets to slices. out := make(map[ibc.Relayer][]ibc.Chain, len(uniq)) for r, chainSet := range uniq { diff --git a/local-interchain/chains/interchainsecurity.json b/local-interchain/chains/interchainsecurity.json new file mode 100644 index 000000000..73400bff6 --- /dev/null +++ b/local-interchain/chains/interchainsecurity.json @@ -0,0 +1,148 @@ +{ + "chains": [ + { + "name": "gaia", + "chain_id": "localcosmos-1", + "denom": "uatom", + "binary": "gaiad", + "bech32_prefix": "cosmos", + "docker_image": { + "version": "v15.0.0-rc2" + }, + "gas_prices": "0%DENOM%", + "chain_type": "cosmos", + "coin_type": 118, + "trusting_period": "336h", + "gas_adjustment": 1.3, + "number_vals": 1, + "number_node": 0, + "debugging": true, + "block_time": "1s", + "genesis": { + "modify": [ + { + "key": "app_state.gov.params.voting_period", + "value": "3s" + }, + { + "key": "app_state.interchainaccounts.host_genesis_state.params.allow_messages", + "value": [ + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgRedeemTokensforShares", + "/cosmos.staking.v1beta1.MsgTokenizeShares", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/ibc.applications.transfer.v1.MsgTransfer" + ] + } + ], + "accounts": [ + { + "name": "acc0", + "address": "cosmos1hj5fveer5cjtn4wd6wstzugjfdxzl0xpxvjjvr", + "amount": "10000000000%DENOM%", + "mnemonic": "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" + } + ] + } + }, + { + "name": "ics-consumer", + "chain_id": "localneutron-1", + "denom": "untrn", + "binary": "neutrond", + "bech32_prefix": "neutron", + "docker_image": { + "version": "v3.0.4", + "repository": "ghcr.io/strangelove-ventures/heighliner/neutron" + }, + "gas_prices": "0.0untrn,0.0uatom", + "chain_type": "cosmos", + "coin_type": 118, + "trusting_period": "336h", + "gas_adjustment": 1.3, + "number_vals": 1, + "number_node": 0, + "ics_consumer_link": "localcosmos-1", + "debugging": true, + "block_time": "1s", + "genesis": { + "modify": [ + { + "key": "consensus_params.block.max_gas", + "value": "100000000" + }, + { + "key": "app_state.ccvconsumer.params.soft_opt_out_threshold", + "value": "0.05" + }, + { + "key": "app_state.ccvconsumer.params.reward_denoms", + "value": [ + "untrn" + ] + }, + { + "key": "app_state.ccvconsumer.params.provider_reward_denoms", + "value": [ + "uatom" + ] + }, + { + "key": "consensus_params.block.max_gas", + "value": "1000000000" + }, + { + "key": "app_state.globalfee.params.minimum_gas_prices", + "value": [ + { + "denom": "untrn", + "amount": "0" + } + ] + }, + { + "key": "app_state.feeburner.params.treasury_address", + "value": "neutron1hj5fveer5cjtn4wd6wstzugjfdxzl0xpznmsky" + }, + { + "key": "app_state.tokenfactory.params.fee_collector_address", + "value": "neutron1hj5fveer5cjtn4wd6wstzugjfdxzl0xpznmsky" + }, + { + "key": "app_state.interchainaccounts.host_genesis_state.params.allow_messages", + "value": [ + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgRedeemTokensforShares", + "/cosmos.staking.v1beta1.MsgTokenizeShares", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/ibc.applications.transfer.v1.MsgTransfer", + "/ibc.lightclients.localhost.v2.ClientState", + "/ibc.core.client.v1.MsgCreateClient", + "/ibc.core.client.v1.Query/ClientState", + "/ibc.core.client.v1.Query/ConsensusState", + "/ibc.core.connection.v1.Query/Connection" + ] + } + ], + "accounts": [ + { + "name": "acc0", + "address": "neutron1hj5fveer5cjtn4wd6wstzugjfdxzl0xpznmsky", + "amount": "10000000000%DENOM%", + "mnemonic": "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/local-interchain/go.mod b/local-interchain/go.mod index 5d99cf1fe..01dc60831 100644 --- a/local-interchain/go.mod +++ b/local-interchain/go.mod @@ -1,8 +1,6 @@ module github.com/strangelove-ventures/localinterchain -go 1.21 - -toolchain go1.21.0 +go 1.22.2 replace ( github.com/ChainSafe/go-schnorrkel => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d @@ -39,6 +37,7 @@ require ( cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/log v1.3.1 // indirect cosmossdk.io/store v1.1.0 // indirect + cosmossdk.io/x/evidence v0.1.0 // indirect cosmossdk.io/x/feegrant v0.1.0 // indirect cosmossdk.io/x/tx v0.13.2 // indirect cosmossdk.io/x/upgrade v0.1.1 // indirect @@ -72,7 +71,7 @@ require ( github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/cometbft/cometbft v0.38.6 // indirect - github.com/cometbft/cometbft-db v0.9.1 // indirect + github.com/cometbft/cometbft-db v0.10.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.2 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect @@ -83,6 +82,7 @@ require ( github.com/cosmos/ibc-go/modules/capability v1.0.0 // indirect github.com/cosmos/ibc-go/v8 v8.2.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect + github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74 // indirect github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/local-interchain/go.sum b/local-interchain/go.sum index c61b74aac..5d7d86e5e 100644 --- a/local-interchain/go.sum +++ b/local-interchain/go.sum @@ -342,8 +342,8 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= @@ -357,8 +357,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft v0.38.6 h1:QSgpCzrGWJ2KUq1qpw+FCfASRpE27T6LQbfEHscdyOk= github.com/cometbft/cometbft v0.38.6/go.mod h1:8rSPxzUJYquCN8uuBgbUHOMg2KAwvr7CyUw+6ukO4nw= -github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= -github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/cometbft/cometbft-db v0.10.0 h1:VMBQh88zXn64jXVvj39tlu/IgsGR84T7ImjS523DCiU= +github.com/cometbft/cometbft-db v0.10.0/go.mod h1:7RR7NRv99j7keWJ5IkE9iZibUTKYdtepXTp7Ra0FxKk= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -391,6 +391,8 @@ github.com/cosmos/ibc-go/v8 v8.2.0 h1:7oCzyy1sZCcgpeQLnHxC56brsSz3KWwQGKXalXwXFz github.com/cosmos/ibc-go/v8 v8.2.0/go.mod h1:wj3qx75iC/XNnsMqbPDCIGs0G6Y3E/lo3bdqCyoCy+8= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= +github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74 h1:6atU/xizTL10q6EprP7oRuvfgUP2F6puvutnVoE+FRc= +github.com/cosmos/interchain-security/v5 v5.0.0-alpha1.0.20240424193412-7cd900ad2a74/go.mod h1:h/RkwOppo5AJj+1pkQyfjqU1MPdpohD/S6oEeAXpGZY= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -954,6 +956,8 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/oxyno-zeta/gomock-extra-matcher v1.2.0 h1:WPEclU0y0PMwUzdDcaKZvld4aXpa3fkzjiUMQdcBEHg= +github.com/oxyno-zeta/gomock-extra-matcher v1.2.0/go.mod h1:S0r7HmKeCGsHmvIVFMjKWwswb4+30nCNWbXRMBVPkaU= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1182,6 +1186,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= diff --git a/local-interchain/interchain/start.go b/local-interchain/interchain/start.go index 99ecb4b60..5ddc106f7 100644 --- a/local-interchain/interchain/start.go +++ b/local-interchain/interchain/start.go @@ -58,6 +58,9 @@ func StartChain(installDir, chainCfgFile string, ac *types.AppStartConfig) { // ibc-path-name -> index of []cosmos.CosmosChain ibcpaths := make(map[string][]int) + // providerChainId -> []consumerChainIds + icsPair := make(map[string][]string) + chainSpecs := []*interchaintest.ChainSpec{} for idx, cfg := range config.Chains { @@ -69,6 +72,10 @@ func StartChain(installDir, chainCfgFile string, ac *types.AppStartConfig) { ibcpaths[path] = append(ibcpaths[path], idx) } } + + if cfg.ICSConsumerLink != "" { + icsPair[cfg.ICSConsumerLink] = append(icsPair[cfg.ICSConsumerLink], cfg.ChainID) + } } if err := VerifyIBCPaths(ibcpaths); err != nil { @@ -101,7 +108,7 @@ func StartChain(installDir, chainCfgFile string, ac *types.AppStartConfig) { client, network := interchaintest.DockerSetup(fakeT) // setup a relayer if we have IBC paths to use. - if len(ibcpaths) > 0 { + if len(ibcpaths) > 0 || len(icsPair) > 0 { rlyCfg := config.Relayer relayerType, relayerName := ibc.CosmosRly, "relay" @@ -124,6 +131,36 @@ func StartChain(installDir, chainCfgFile string, ac *types.AppStartConfig) { LinkIBCPaths(ibcpaths, chains, ic, relayer) } + // Add Interchain Security chain pairs together + if len(icsPair) > 0 { + for provider, consumers := range icsPair { + var p, c ibc.Chain + + // a provider can have multiple consumers + for _, consumer := range consumers { + for _, chain := range chains { + if chain.Config().ChainID == provider { + p = chain + } + if chain.Config().ChainID == consumer { + c = chain + } + } + } + + pathName := fmt.Sprintf("%s-%s", p.Config().ChainID, c.Config().ChainID) + + logger.Info("Adding ICS pair", zap.String("provider", p.Config().ChainID), zap.String("consumer", c.Config().ChainID), zap.String("path", pathName)) + + ic = ic.AddProviderConsumerLink(interchaintest.ProviderConsumerLink{ + Provider: p, + Consumer: c, + Relayer: relayer, + Path: pathName, + }) + } + } + // Build all chains & begin. err = ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ TestName: testName, diff --git a/local-interchain/interchain/types/chain.go b/local-interchain/interchain/types/chain.go index 785182d9e..95a799f6d 100644 --- a/local-interchain/interchain/types/chain.go +++ b/local-interchain/interchain/types/chain.go @@ -23,6 +23,7 @@ type Chain struct { Debugging bool `json:"debugging" yaml:"debugging"` BlockTime string `json:"block_time" yaml:"block_time"` HostPortOverride map[string]string `json:"host_port_override" yaml:"host_port_override"` + ICSConsumerLink string `json:"ics_consumer_link"` // a consumer sets this to ex: "provider-chain-id" to connect to them // Required Name string `json:"name" yaml:"name" validate:"min=1"` diff --git a/relayer/docker.go b/relayer/docker.go index 0425945f2..39eb8270e 100644 --- a/relayer/docker.go +++ b/relayer/docker.go @@ -228,7 +228,7 @@ func (r *DockerRelayer) CreateClients(ctx context.Context, rep ibc.RelayerExecRe return res.Err } -func (r *DockerRelayer) CreateClient(ctx context.Context, rep ibc.RelayerExecReporter, srcChainID, dstChainID, pathName string, opts ibc.CreateClientOptions) error { +func (r *DockerRelayer) CreateClient(ctx context.Context, rep ibc.RelayerExecReporter, srcChainID string, dstChainID string, pathName string, opts ibc.CreateClientOptions) error { cmd := r.c.CreateClient(srcChainID, dstChainID, pathName, opts, r.HomeDir()) res := r.Exec(ctx, rep, cmd, nil) return res.Err @@ -252,8 +252,8 @@ func (r *DockerRelayer) GeneratePath(ctx context.Context, rep ibc.RelayerExecRep return res.Err } -func (r *DockerRelayer) UpdatePath(ctx context.Context, rep ibc.RelayerExecReporter, pathName string, filter ibc.ChannelFilter) error { - cmd := r.c.UpdatePath(pathName, r.HomeDir(), filter) +func (r *DockerRelayer) UpdatePath(ctx context.Context, rep ibc.RelayerExecReporter, pathName string, opts ibc.PathUpdateOptions) error { + cmd := r.c.UpdatePath(pathName, r.HomeDir(), opts) res := r.Exec(ctx, rep, cmd, nil) return res.Err } @@ -564,7 +564,7 @@ type RelayerCommander interface { CreateConnections(pathName, homeDir string) []string Flush(pathName, channelID, homeDir string) []string GeneratePath(srcChainID, dstChainID, pathName, homeDir string) []string - UpdatePath(pathName, homeDir string, filter ibc.ChannelFilter) []string + UpdatePath(pathName, homeDir string, opts ibc.PathUpdateOptions) []string GetChannels(chainID, homeDir string) []string GetConnections(chainID, homeDir string) []string GetClients(chainID, homeDir string) []string diff --git a/relayer/hermes/hermes_commander.go b/relayer/hermes/hermes_commander.go index 9f3950c7e..cfc7142a8 100644 --- a/relayer/hermes/hermes_commander.go +++ b/relayer/hermes/hermes_commander.go @@ -145,7 +145,7 @@ func (c commander) CreateWallet(keyName, address, mnemonic string) ibc.Wallet { return NewWallet(keyName, address, mnemonic) } -func (c commander) UpdatePath(pathName, homeDir string, filter ibc.ChannelFilter) []string { +func (c commander) UpdatePath(pathName, homeDir string, opts ibc.PathUpdateOptions) []string { // TODO: figure out how to implement this. panic("implement me") } diff --git a/relayer/hyperspace/hyperspace_commander.go b/relayer/hyperspace/hyperspace_commander.go index 100ad7df1..6ce5b3f02 100644 --- a/relayer/hyperspace/hyperspace_commander.go +++ b/relayer/hyperspace/hyperspace_commander.go @@ -153,7 +153,7 @@ func (c *hyperspaceCommander) GeneratePath(srcChainID, dstChainID, pathName, hom } // Hyperspace does not have paths, just two configs -func (hyperspaceCommander) UpdatePath(pathName, homeDir string, filter ibc.ChannelFilter) []string { +func (hyperspaceCommander) UpdatePath(pathName, homeDir string, opts ibc.PathUpdateOptions) []string { panic("[UpdatePath] Do not call me") } diff --git a/relayer/rly/cosmos_relayer.go b/relayer/rly/cosmos_relayer.go index 4761040f7..fb8268125 100644 --- a/relayer/rly/cosmos_relayer.go +++ b/relayer/rly/cosmos_relayer.go @@ -63,7 +63,7 @@ type CosmosRelayerChainConfig struct { const ( DefaultContainerImage = "ghcr.io/cosmos/relayer" - DefaultContainerVersion = "v2.5.0" + DefaultContainerVersion = "v2.5.2" ) // Capabilities returns the set of capabilities of the Cosmos relayer. @@ -200,13 +200,38 @@ func (commander) GeneratePath(srcChainID, dstChainID, pathName, homeDir string) } } -func (commander) UpdatePath(pathName, homeDir string, filter ibc.ChannelFilter) []string { - return []string{ +func (commander) UpdatePath(pathName, homeDir string, opts ibc.PathUpdateOptions) []string { + command := []string{ "rly", "paths", "update", pathName, "--home", homeDir, - "--filter-rule", filter.Rule, - "--filter-channels", strings.Join(filter.ChannelList, ","), } + + if opts.ChannelFilter != nil { + command = append(command, + "--filter-rule", opts.ChannelFilter.Rule, + "--filter-channels", strings.Join(opts.ChannelFilter.ChannelList, ",")) + } + + if opts.SrcChainID != nil { + command = append(command, "--src-chain-id", *opts.SrcChainID) + } + if opts.DstChainID != nil { + command = append(command, "--dst-chain-id", *opts.DstChainID) + } + if opts.SrcClientID != nil { + command = append(command, "--src-client-id", *opts.SrcClientID) + } + if opts.DstClientID != nil { + command = append(command, "--dst-client-id", *opts.DstClientID) + } + if opts.SrcConnID != nil { + command = append(command, "--src-connection-id", *opts.SrcConnID) + } + if opts.DstConnID != nil { + command = append(command, "--dst-connection-id", *opts.DstConnID) + } + + return command } func (commander) GetChannels(chainID, homeDir string) []string {