Skip to content

Commit

Permalink
fix(v7): get consumer genesis from provider chain and transform it
Browse files Browse the repository at this point in the history
This is preferred to building the ccvgenesis ourselves as it guarantees
that it works across different consumer/provider versions of ICS
  • Loading branch information
fastfadingviolets committed Apr 24, 2024
1 parent 07af56f commit 68c8b8a
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 126 deletions.
19 changes: 19 additions & 0 deletions chain/cosmos/chain_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -708,6 +709,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 := ""
Expand Down
123 changes: 49 additions & 74 deletions chain/cosmos/cosmos_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import (
"io"
"math"
"os"
"path"
"strconv"
"strings"
"sync"
"time"

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"
Expand All @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
119 changes: 68 additions & 51 deletions examples/ibc/ics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ibc_test
import (
"context"
"fmt"
"strings"
"testing"
"time"

Expand All @@ -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,
Expand All @@ -31,64 +32,80 @@ func TestICS(t *testing.T) {
},
}

for _, tt := range tests {
tt := tt
testname := tt.name
t.Run(testname, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
icsVersions := []string{"v3.1.0", "v3.3.0", "v4.0.0"}

// 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"},
})
for _, rly := range relayers {
rly := rly
testname := rly.name
for _, providerVersion := range icsVersions {
providerVersion := providerVersion
for _, consumerVersion := range icsVersions {
consumerVersion := consumerVersion
testname := fmt.Sprintf("%s-provider%s-consumer%s", testname, providerVersion, consumerVersion)
testname = strings.ReplaceAll(testname, ".", "")
t.Run(testname, func(t *testing.T) {
t.Parallel()
fullNodes := 0
validators := 1
ctx := context.Background()
var consumerBechPrefix string
if consumerVersion == "v4.0.0" {
consumerBechPrefix = "consumer"
}

chains, err := cf.Chains(t.Name())
require.NoError(t, err)
provider, consumer := chains[0], chains[1]
// 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}},
})

// Relayer Factory
client, network := interchaintest.DockerSetup(t)
chains, err := cf.Chains(t.Name())
require.NoError(t, err)
provider, consumer := chains[0], chains[1]

r := interchaintest.NewBuiltinRelayerFactory(
tt.relayerImp,
zaptest.NewLogger(t),
).Build(t, client, network)
// Relayer Factory
client, network := interchaintest.DockerSetup(t)

// 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,
})
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,
})

// 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")
})
}
}
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 68c8b8a

Please sign in to comment.