diff --git a/chain/cosmos/chain_node.go b/chain/cosmos/chain_node.go index 8d04c339a..a28946180 100644 --- a/chain/cosmos/chain_node.go +++ b/chain/cosmos/chain_node.go @@ -38,6 +38,7 @@ 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" @@ -721,6 +722,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 []types.Coin) error { amount := "" diff --git a/chain/cosmos/cosmos_chain.go b/chain/cosmos/cosmos_chain.go index cbfe10370..56c352991 100644 --- a/chain/cosmos/cosmos_chain.go +++ b/chain/cosmos/cosmos_chain.go @@ -10,6 +10,7 @@ import ( "io" "math" "os" + "path" "strconv" "strings" "sync" @@ -17,8 +18,6 @@ import ( govv1beta1 "cosmossdk.io/api/cosmos/gov/v1beta1" sdkmath "cosmossdk.io/math" - abcitypes "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/proto/tendermint/crypto" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -34,11 +33,7 @@ import ( cosmosproto "github.com/cosmos/gogoproto/proto" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" chanTypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ccvconsumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" ccvclient "github.com/cosmos/interchain-security/v3/x/ccv/provider/client" - ccvprovidertypes "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" dockertypes "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" @@ -50,6 +45,7 @@ import ( "github.com/strangelove-ventures/interchaintest/v7/internal/dockerutil" "github.com/strangelove-ventures/interchaintest/v7/testutil" "go.uber.org/zap" + "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -1197,6 +1193,47 @@ func (c *CosmosChain) StartProvider(testName string, ctx context.Context, additi return nil } +func (c *CosmosChain) transformCCVState(ctx context.Context, ccvState []byte, consumerVersion, providerVersion string) ([]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, "v3.3.0") < 0 && semver.Compare(consumerVersion, "v3.3.0") < 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, "v3.3.0") >= 0 && semver.Compare(providerVersion, consumerVersion) > 0 { + imageVersion = "v4.0.0" + if semver.Compare(providerVersion, "v4.0.0") > 0 { + imageVersion = providerVersion + } + toVersion = semver.Major(consumerVersion) + if toVersion == "v3" { + toVersion = semver.MajorMinor(consumerVersion) + } + } else { + imageVersion = consumerVersion + } + 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() @@ -1263,83 +1300,21 @@ func (c *CosmosChain) StartConsumer(testName string, ctx context.Context, additi } } - providerHeight, err := c.Provider.Height(ctx) - if err != nil { - return fmt.Errorf("failed to query provider height") - } - providerHeightInt64 := int64(providerHeight) - - block, err := c.Provider.getFullNode().Client.Block(ctx, &providerHeightInt64) - if err != nil { - return fmt.Errorf("failed to query provider block to initialize consumer client") - } - genbz, err := validator0.GenesisFileContent(ctx) if err != nil { return err } - // populate genesis file ccvconsumer module app_state. - // fetch provider latest block (timestamp, root.hash, and next_validators_hash) to populate provider_consensus_state - // populate provider_client_state with trusting and unbonding periods, latest_height.revision_height of height which is used for consensus state - // populate initial_val_set with provider val pubkeys and power - - nextValidatorsHash := block.Block.NextValidatorsHash - timestamp := block.Block.Time - rootHash := block.Block.AppHash - - page := int(1) - perPage := int(1000) - providerVals, err := c.Provider.getFullNode().Client.Validators(ctx, &providerHeightInt64, &page, &perPage) + ccvStateMarshaled, _, err := c.Provider.GetNode().ExecQuery(ctx, "provider", "consumer-genesis", c.cfg.ChainID) if err != nil { - return fmt.Errorf("failed to get provider validators: %w", err) + return fmt.Errorf("failed to query provider for ccv state: %w", err) } - - initialVals := make([]abcitypes.ValidatorUpdate, len(providerVals.Validators)) - for i, val := range providerVals.Validators { - initialVals[i] = abcitypes.ValidatorUpdate{ - PubKey: crypto.PublicKey{Sum: &crypto.PublicKey_Ed25519{Ed25519: val.PubKey.Bytes()}}, - Power: val.VotingPower, - } - } - - providerCfg := c.Provider.Config() - - clientState := ibctmtypes.NewClientState( - providerCfg.ChainID, - ibctmtypes.DefaultTrustLevel, - DefaultProviderUnbondingPeriod/2, - DefaultProviderUnbondingPeriod, // Needs to match provider unbonding period - ccvprovidertypes.DefaultMaxClockDrift, - clienttypes.Height{ - RevisionHeight: uint64(providerHeight), - RevisionNumber: clienttypes.ParseChainID(providerCfg.ChainID), - }, - commitmenttypes.GetSDKSpecs(), - defaultUpgradePath, - ) - - root := commitmenttypes.MerkleRoot{ - Hash: rootHash, - } - - consensusState := ibctmtypes.NewConsensusState(timestamp, root, nextValidatorsHash) - - ccvState := ccvconsumertypes.NewInitialGenesisState( - clientState, - consensusState, - initialVals, - ccvconsumertypes.DefaultGenesisState().GetParams(), - ) - - ccvState.Params.Enabled = true - - ccvStateMarshaled, err := c.cfg.EncodingConfig.Codec.MarshalJSON(ccvState) - c.log.Info("HERE STATE!", zap.String("GEN", string(ccvStateMarshaled))) + consumerICS := c.GetNode().ICSVersion(ctx) + providerICS := c.Provider.GetNode().ICSVersion(ctx) + ccvStateMarshaled, err = c.transformCCVState(ctx, ccvStateMarshaled, consumerICS, providerICS) if err != nil { - return fmt.Errorf("failed to marshal ccv state to json: %w", err) + return err } - var ccvStateUnmarshaled interface{} if err := json.Unmarshal(ccvStateMarshaled, &ccvStateUnmarshaled); err != nil { return fmt.Errorf("failed to unmarshal ccv state json: %w", err) diff --git a/examples/ibc/ics_test.go b/examples/ibc/ics_test.go index 3b7d0b47a..1fb581725 100644 --- a/examples/ibc/ics_test.go +++ b/examples/ibc/ics_test.go @@ -3,6 +3,7 @@ package ibc_test import ( "context" "fmt" + "strings" "testing" "time" @@ -20,7 +21,7 @@ func TestICS(t *testing.T) { t.Skip("skipping in short mode") } - tests := []relayerImp{ + relayers := []relayerImp{ { name: "Cosmos Relayer", relayerImp: ibc.CosmosRly, @@ -31,64 +32,83 @@ func TestICS(t *testing.T) { }, } - for _, tt := range tests { - tt := tt - testname := tt.name + icsVersions := []string{"v3.1.0", "v3.3.0", "v4.0.0"} + + for _, rly := range relayers { + rly := rly + testname := rly.name t.Run(testname, func(t *testing.T) { + // We paralellize the relayers, but not the versions. That would be too many tests running at once, and things can become unstable. t.Parallel() - ctx := context.Background() + for _, providerVersion := range icsVersions { + providerVersion := providerVersion + for _, consumerVersion := range icsVersions { + consumerVersion := consumerVersion + testname := fmt.Sprintf("provider%s-consumer%s", providerVersion, consumerVersion) + testname = strings.ReplaceAll(testname, ".", "") + t.Run(testname, func(t *testing.T) { + fullNodes := 0 + validators := 1 + ctx := context.Background() + var consumerBechPrefix string + if consumerVersion == "v4.0.0" { + consumerBechPrefix = "consumer" + } - // Chain Factory - cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ - {Name: "ics-provider", Version: "v3.1.0", ChainConfig: ibc.ChainConfig{GasAdjustment: 1.5}}, - {Name: "ics-consumer", Version: "v3.1.0"}, - }) + // Chain Factory + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ + {Name: "ics-provider", Version: providerVersion, NumValidators: &validators, NumFullNodes: &fullNodes, ChainConfig: ibc.ChainConfig{GasAdjustment: 1.5}}, + {Name: "ics-consumer", Version: consumerVersion, NumValidators: &validators, NumFullNodes: &fullNodes, ChainConfig: ibc.ChainConfig{Bech32Prefix: consumerBechPrefix}}, + }) - chains, err := cf.Chains(t.Name()) - require.NoError(t, err) - provider, consumer := chains[0], chains[1] + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + provider, consumer := chains[0], chains[1] - // Relayer Factory - client, network := interchaintest.DockerSetup(t) + // Relayer Factory + client, network := interchaintest.DockerSetup(t) - r := interchaintest.NewBuiltinRelayerFactory( - tt.relayerImp, - zaptest.NewLogger(t), - ).Build(t, client, network) + r := interchaintest.NewBuiltinRelayerFactory( + rly.relayerImp, + 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, - }) + // 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) + // 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(), + // 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") + 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") + err = testutil.WaitForBlocks(ctx, 10, provider, consumer) + require.NoError(t, err, "failed to wait for blocks") + }) + } + } }) } } diff --git a/go.mod b/go.mod index de3bda997..b140939a2 100644 --- a/go.mod +++ b/go.mod @@ -224,7 +224,7 @@ require ( go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.12.0 golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.16.0 // indirect