mukan-ibc/e2e/testsuite/testconfig.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

1098 lines
34 KiB
Go

package testsuite
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/cosmos/interchaintest/v10"
"github.com/cosmos/interchaintest/v10/ibc"
interchaintestutil "github.com/cosmos/interchaintest/v10/testutil"
"gopkg.in/yaml.v2"
"git.cw.tr/mukan-network/mukan-sdk/codec"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-sdk/types/module/testutil"
genutiltypes "git.cw.tr/mukan-network/mukan-sdk/x/genutil/types"
govtypes "git.cw.tr/mukan-network/mukan-sdk/x/gov/types"
govv1 "git.cw.tr/mukan-network/mukan-sdk/x/gov/types/v1"
govv1beta1 "git.cw.tr/mukan-network/mukan-sdk/x/gov/types/v1beta1"
cmtjson "git.cw.tr/mukan-network/mukan-consensus/libs/json"
"github.com/cosmos/ibc-go/e2e/internal/directories"
"github.com/cosmos/ibc-go/e2e/relayer"
"github.com/cosmos/ibc-go/e2e/semverutil"
"github.com/cosmos/ibc-go/e2e/testvalues"
wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
ibcexported "git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
ibctypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/types"
)
const (
// ChainImageEnv specifies the image that the chains will use. If left unspecified, it will
// default to being determined based on the specified binary. E.g. ghcr.io/cosmos/ibc-go-simd
ChainImageEnv = "CHAIN_IMAGE"
// ChainATagEnv specifies the tag that Chain A will use.
ChainATagEnv = "CHAIN_A_TAG"
// ChainBTagEnv specifies the tag that Chain B will use. If unspecified
// the value will default to the same value as Chain A.
ChainBTagEnv = "CHAIN_B_TAG"
// ChainCTagEnv specifies the tag that Chain C will use.
// the value will default to the same value as Chain A.
ChainCTagEnv = "CHAIN_C_TAG"
// ChainDTagEnv specifies the tag that Chain D will use. If unspecified
// the value will default to the same value as Chain A.
ChainDTagEnv = "CHAIN_D_TAG"
// RelayerIDEnv specifies the ID of the relayer to use.
RelayerIDEnv = "RELAYER_ID"
// ChainBinaryEnv binary is the binary that will be used for both chains.
ChainBinaryEnv = "CHAIN_BINARY"
// ChainUpgradePlanEnv specifies the upgrade plan name
ChainUpgradePlanEnv = "CHAIN_UPGRADE_PLAN"
// E2EConfigFilePathEnv allows you to specify a custom path for the config file to be used. It can be relative
// or absolute.
E2EConfigFilePathEnv = "E2E_CONFIG_PATH"
// KeepContainersEnv instructs interchaintest to not delete the containers after a test has run.
// this ensures that chain containers are not deleted after a test suite is run if other tests
// depend on those chains.
KeepContainersEnv = "KEEP_CONTAINERS"
// defaultBinary is the default binary that will be used by the chains.
defaultBinary = "simd"
// defaultRlyTag is the tag that will be used if no relayer tag is specified.
// all images are here https://github.com/cosmos/relayer/pkgs/container/relayer/versions
defaultRlyTag = "latest"
// defaultHermesTag is the tag that will be used if no relayer tag is specified for hermes.
defaultHermesTag = "1.10.4"
// defaultChainTag is the tag that will be used for the chains if none is specified.
defaultChainTag = "main"
// defaultConfigFileName is the default filename for the config file that can be used to configure
// e2e tests. See sample.config.yaml or sample.config.extended.yaml as an example for what this should look like.
defaultConfigFileName = ".ibc-go-e2e-config.yaml"
// defaultCIConfigFileName is the default filename for the config file that should be used for CI.
defaultCIConfigFileName = "ci-e2e-config.yaml"
)
// defaultChainNames contains the default name for chainA, chainB, ChainC and ChainD.
var defaultChainNames = []string{"simapp-a", "simapp-b", "simapp-c", "simapp-d"}
func getChainImage(binary string) string {
if binary == "" {
binary = defaultBinary
}
return fmt.Sprintf("ghcr.io/cosmos/ibc-go-%s", binary)
}
// TestConfig holds configuration used throughout the different e2e tests.
type TestConfig struct {
// ChainConfigs holds configuration values related to the chains used in the tests.
ChainConfigs []ChainConfig `yaml:"chains"`
// RelayerConfigs holds all known relayer configurations that can be used in the tests.
RelayerConfigs []relayer.Config `yaml:"relayers"`
// ActiveRelayer specifies the relayer that will be used. It must match the ID of one of the entries in RelayerConfigs.
ActiveRelayer string `yaml:"activeRelayer"`
// CometBFTConfig holds values for configuring CometBFT.
CometBFTConfig CometBFTConfig `yaml:"cometbft"`
// DebugConfig holds configuration for miscellaneous options.
DebugConfig DebugConfig `yaml:"debug"`
// UpgradePlanName specifies which upgrade plan to use. It must match a plan name for an entry in the
// list of UpgradeConfigs.
UpgradePlanName string `yaml:"upgradePlanName"`
// UpgradeConfigs provides a list of all possible upgrades.
UpgradeConfigs []UpgradeConfig `yaml:"upgrades"`
}
// Validate validates the test configuration is valid for use within the tests.
// this should be called before using the configuration.
func (tc TestConfig) Validate() error {
if err := tc.validateChains(); err != nil {
return fmt.Errorf("invalid chain configuration: %w", err)
}
if err := tc.validateRelayers(); err != nil {
return fmt.Errorf("invalid relayer configuration: %w", err)
}
if err := tc.validateGenesisDebugConfig(); err != nil {
return fmt.Errorf("invalid Genesis debug configuration: %w", err)
}
if err := tc.validateUpgradeConfig(); err != nil {
return fmt.Errorf("invalid upgrade configuration: %w", err)
}
return nil
}
// validateChains validates the chain configurations.
func (tc TestConfig) validateChains() error {
for _, cfg := range tc.ChainConfigs {
if cfg.Binary == "" {
return fmt.Errorf("chain config missing binary: %+v", cfg)
}
if cfg.Image == "" {
return fmt.Errorf("chain config missing image: %+v", cfg)
}
if cfg.Tag == "" {
return fmt.Errorf("chain config missing tag: %+v", cfg)
}
if cfg.NumValidators == 0 && cfg.NumFullNodes == 0 {
return fmt.Errorf("chain config missing number of validators or full nodes: %+v", cfg)
}
}
// clienttypes.ParseChainID is used to determine revision heights. If the chainIDs are not in the expected format,
// tests can fail with timeout errors.
if clienttypes.ParseChainID(tc.GetChainAID()) != clienttypes.ParseChainID(tc.GetChainBID()) {
return fmt.Errorf("ensure both chainIDs are in the format {chainID}-{revision} and have the same revision. Got: chainA: %s, chainB: %s", tc.GetChainAID(), tc.GetChainBID())
}
return nil
}
// validateRelayers validates relayer configuration.
func (tc TestConfig) validateRelayers() error {
if len(tc.RelayerConfigs) < 1 {
return errors.New("no relayer configurations specified")
}
for _, r := range tc.RelayerConfigs {
if r.ID == "" {
return fmt.Errorf("relayer config missing ID: %+v", r)
}
if r.Image == "" {
return fmt.Errorf("relayer config missing image: %+v", r)
}
if r.Tag == "" {
return fmt.Errorf("relayer config missing tag: %+v", r)
}
}
if tc.GetActiveRelayerConfig() == nil {
return fmt.Errorf("active relayer %s not found in relayer configs: %+v", tc.ActiveRelayer, tc.RelayerConfigs)
}
return nil
}
// GetUpgradeConfig returns the upgrade configuration for the current test configuration.
func (tc TestConfig) GetUpgradeConfig() UpgradeConfig {
for _, upgrade := range tc.UpgradeConfigs {
if upgrade.PlanName == tc.UpgradePlanName {
return upgrade
}
}
panic("upgrade plan not found in upgrade configs, this test config should not have passed validation")
}
// GetChainIndex returns the index of the chain with the given name, if it
// exists.
func (tc TestConfig) GetChainIndex(name string) (int, error) {
for i := range tc.ChainConfigs {
chainName := tc.GetChainName(i)
if chainName == name {
return i, nil
}
}
return -1, fmt.Errorf("chain %s not found in chain configs", name)
}
// validateGenesisDebugConfig validates configuration of Genesis debug options/
func (tc TestConfig) validateGenesisDebugConfig() error {
cfg := tc.DebugConfig.GenesisDebug
if !cfg.DumpGenesisDebugInfo {
return nil
}
// Verify that the provided chain exists in our config
_, err := tc.GetChainIndex(tc.GetGenesisChainName())
return err
}
// validateUpgradeConfig ensures the upgrade configuration is valid.
func (tc TestConfig) validateUpgradeConfig() error {
if strings.TrimSpace(tc.UpgradePlanName) == "" {
return nil
}
// the upgrade plan name specified must match one of the upgrade plans in the upgrade configs.
foundPlan := false
for _, upgrade := range tc.UpgradeConfigs {
if strings.TrimSpace(upgrade.Tag) == "" {
return fmt.Errorf("upgrade config missing tag: %+v", upgrade)
}
if strings.TrimSpace(upgrade.PlanName) == "" {
return fmt.Errorf("upgrade config missing plan name: %+v", upgrade)
}
if upgrade.PlanName == tc.UpgradePlanName {
foundPlan = true
}
}
if foundPlan {
return nil
}
return fmt.Errorf("upgrade plan %s not found in upgrade configs: %+v", tc.UpgradePlanName, tc.UpgradeConfigs)
}
// GetActiveRelayerConfig returns the currently specified relayer config.
func (tc TestConfig) GetActiveRelayerConfig() *relayer.Config {
for _, r := range tc.RelayerConfigs {
if r.ID == tc.ActiveRelayer {
return &r
}
}
return nil
}
// GetChainNumValidators returns the number of validators for the specific chain index.
// default 1
func (tc TestConfig) GetChainNumValidators(idx int) int {
if tc.ChainConfigs[idx].NumValidators > 0 {
return tc.ChainConfigs[idx].NumValidators
}
return 1
}
// GetChainNumFullNodes returns the number of full nodes for the specific chain index.
// default 0
func (tc TestConfig) GetChainNumFullNodes(idx int) int {
if tc.ChainConfigs[idx].NumFullNodes > 0 {
return tc.ChainConfigs[idx].NumFullNodes
}
return 0
}
// GetChainID returns the chain-id for i. Assumes indicies are correct.
func (tc TestConfig) GetChainID(i int) string {
if tc.ChainConfigs[i].ChainID != "" {
return tc.ChainConfigs[i].ChainID
}
return fmt.Sprintf("chain%c-1", 'A'+i)
}
// GetChainAID returns the chain-id for chain A.
// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number.
func (tc TestConfig) GetChainAID() string {
if tc.ChainConfigs[0].ChainID != "" {
return tc.ChainConfigs[0].ChainID
}
return "chainA-1"
}
// GetChainBID returns the chain-id for chain B.
// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number.
func (tc TestConfig) GetChainBID() string {
if tc.ChainConfigs[1].ChainID != "" {
return tc.ChainConfigs[1].ChainID
}
return "chainB-1"
}
// GetChainCID returns the chain-id for chain C.
// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number.
func (tc TestConfig) GetChainCID() string {
if tc.ChainConfigs[2].ChainID != "" {
return tc.ChainConfigs[2].ChainID
}
return "chainC-1"
}
// GetChainDID returns the chain-id for chain D.
// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number.
func (tc TestConfig) GetChainDID() string {
if tc.ChainConfigs[3].ChainID != "" {
return tc.ChainConfigs[3].ChainID
}
return "chainD-1"
}
// GetChainName returns the name of the chain given an index.
func (tc TestConfig) GetChainName(idx int) string {
// Assumes that only valid indices are provided. We do the same in several other places.
chainName := tc.ChainConfigs[idx].Name
if chainName == "" {
chainName = defaultChainNames[idx]
}
return chainName
}
// GetGenesisChainName returns the name of the chain for which to dump Genesis files.
// If no chain is provided, it uses the default one (chainA).
func (tc TestConfig) GetGenesisChainName() string {
name := tc.DebugConfig.GenesisDebug.ChainName
if name == "" {
return tc.GetChainName(0)
}
return name
}
// UpgradeConfig holds values relevant to upgrade tests.
type UpgradeConfig struct {
PlanName string `yaml:"planName"`
Tag string `yaml:"tag"`
}
// ChainConfig holds information about an individual chain used in the tests.
type ChainConfig struct {
ChainID string `yaml:"chainId"`
Name string `yaml:"name"`
Image string `yaml:"image"`
Tag string `yaml:"tag"`
Binary string `yaml:"binary"`
NumValidators int `yaml:"numValidators"`
NumFullNodes int `yaml:"numFullNodes"`
}
type CometBFTConfig struct {
LogLevel string `yaml:"logLevel"`
}
type GenesisDebugConfig struct {
// DumpGenesisDebugInfo enables the output of Genesis debug files.
DumpGenesisDebugInfo bool `yaml:"dumpGenesisDebugInfo"`
// ExportFilePath specifies which path to export Genesis debug files to.
ExportFilePath string `yaml:"filePath"`
// ChainName represent which chain to get Genesis debug info for.
ChainName string `yaml:"chainName"`
}
type DebugConfig struct {
// DumpLogs forces the logs to be collected before removing test containers.
DumpLogs bool `yaml:"dumpLogs"`
// GenesisDebug contains debug information specific to Genesis.
GenesisDebug GenesisDebugConfig `yaml:"genesis"`
// KeepContainers specifies if the containers should be kept after the test suite is done.
// NOTE: when running a full test suite, this value should be set to true in order to preserve
// shared resources.
KeepContainers bool `yaml:"keepContainers"`
}
// LoadConfig attempts to load a test configuration from the default file path.
// if any environment variables are specified, they will take precedence over the individual configuration
// options.
func LoadConfig() TestConfig {
tc := getConfig()
if err := tc.Validate(); err != nil {
panic(err)
}
return tc
}
// getConfig returns the TestConfig with any environment variable overrides.
func getConfig() TestConfig {
fileTc, foundFile := fromFile()
if !foundFile {
return fromEnv()
}
testCfg := applyEnvironmentVariableOverrides(fileTc)
// If tags for chain C and D are not present in the file, also not set in the CI, fallback to A
if testCfg.ChainConfigs[2].Tag == "" {
testCfg.ChainConfigs[2].Tag = testCfg.ChainConfigs[0].Tag
}
if testCfg.ChainConfigs[3].Tag == "" {
testCfg.ChainConfigs[3].Tag = testCfg.ChainConfigs[0].Tag
}
return testCfg
}
// fromFile returns a TestConfig from a json file and a boolean indicating if the file was found.
func fromFile() (TestConfig, bool) {
var tc TestConfig
bz, err := os.ReadFile(getConfigFilePath())
if err != nil {
return TestConfig{}, false
}
if err := yaml.Unmarshal(bz, &tc); err != nil {
panic(err)
}
return populateDefaults(tc), true
}
// populateDefaults populates default values for the test config if
// certain required fields are not specified.
func populateDefaults(tc TestConfig) TestConfig {
chainIDs := []string{
"chainA-1",
"chainB-1",
"chainC-1",
"chainD-1",
}
for i := range tc.ChainConfigs {
if tc.ChainConfigs[i].ChainID == "" {
tc.ChainConfigs[i].ChainID = chainIDs[i]
}
if tc.ChainConfigs[i].Binary == "" {
tc.ChainConfigs[i].Binary = defaultBinary
}
if tc.ChainConfigs[i].Image == "" {
tc.ChainConfigs[i].Image = getChainImage(tc.ChainConfigs[i].Binary)
}
if tc.ChainConfigs[i].NumValidators == 0 {
tc.ChainConfigs[i].NumValidators = 1
}
// If tag not given for chain C and D, set to chain A' tag
if tc.ChainConfigs[i].Tag == "" && i != 0 {
tc.ChainConfigs[i].Tag = tc.ChainConfigs[0].Tag
}
}
if tc.ActiveRelayer == "" {
tc.ActiveRelayer = relayer.Hermes
}
if tc.RelayerConfigs == nil {
tc.RelayerConfigs = []relayer.Config{
getDefaultRlyRelayerConfig(),
getDefaultHermesRelayerConfig(),
}
}
if tc.CometBFTConfig.LogLevel == "" {
tc.CometBFTConfig.LogLevel = "info"
}
return tc
}
// applyEnvironmentVariableOverrides applies all environment variable changes to the config
// loaded from a file.
func applyEnvironmentVariableOverrides(fromFile TestConfig) TestConfig {
envTc := fromEnv()
if os.Getenv(ChainATagEnv) != "" {
fromFile.ChainConfigs[0].Tag = envTc.ChainConfigs[0].Tag
}
if os.Getenv(ChainBTagEnv) != "" {
fromFile.ChainConfigs[1].Tag = envTc.ChainConfigs[1].Tag
}
if os.Getenv(ChainCTagEnv) != "" {
fromFile.ChainConfigs[2].Tag = envTc.ChainConfigs[2].Tag
}
if os.Getenv(ChainDTagEnv) != "" {
fromFile.ChainConfigs[3].Tag = envTc.ChainConfigs[3].Tag
}
if os.Getenv(ChainBinaryEnv) != "" {
for i := range fromFile.ChainConfigs {
fromFile.ChainConfigs[i].Binary = envTc.ChainConfigs[i].Binary
}
}
if os.Getenv(ChainImageEnv) != "" {
for i := range fromFile.ChainConfigs {
fromFile.ChainConfigs[i].Image = envTc.ChainConfigs[i].Image
}
}
if os.Getenv(RelayerIDEnv) != "" {
fromFile.ActiveRelayer = envTc.ActiveRelayer
}
if os.Getenv(ChainUpgradePlanEnv) != "" {
fromFile.UpgradePlanName = envTc.UpgradePlanName
}
if isEnvTrue(KeepContainersEnv) {
fromFile.DebugConfig.KeepContainers = true
}
return fromFile
}
// fromEnv returns a TestConfig constructed from environment variables.
func fromEnv() TestConfig {
return TestConfig{
ChainConfigs: getChainConfigsFromEnv(),
UpgradePlanName: os.Getenv(ChainUpgradePlanEnv),
ActiveRelayer: os.Getenv(RelayerIDEnv),
CometBFTConfig: CometBFTConfig{LogLevel: "info"},
}
}
// getChainConfigsFromEnv returns the chain configs from environment variables.
func getChainConfigsFromEnv() []ChainConfig {
chainBinary, ok := os.LookupEnv(ChainBinaryEnv)
if !ok {
chainBinary = defaultBinary
}
chainATag, ok := os.LookupEnv(ChainATagEnv)
if !ok {
chainATag = defaultChainTag
}
chainBTag, ok := os.LookupEnv(ChainBTagEnv)
if !ok {
chainBTag = chainATag
}
chainCTag, ok := os.LookupEnv(ChainCTagEnv)
if !ok {
chainCTag = chainATag
}
chainDTag, ok := os.LookupEnv(ChainDTagEnv)
if !ok {
chainDTag = chainATag
}
chainAImage := getChainImage(chainBinary)
specifiedChainImage, ok := os.LookupEnv(ChainImageEnv)
if ok {
chainAImage = specifiedChainImage
}
numValidators := 4
numFullNodes := 1
chainBImage := chainAImage
chainCImage := chainAImage
chainDImage := chainAImage
return []ChainConfig{
{
Image: chainAImage,
Tag: chainATag,
Binary: chainBinary,
NumValidators: numValidators,
NumFullNodes: numFullNodes,
},
{
Image: chainBImage,
Tag: chainBTag,
Binary: chainBinary,
NumValidators: numValidators,
NumFullNodes: numFullNodes,
},
{
Image: chainCImage,
Tag: chainCTag,
Binary: chainBinary,
NumValidators: numValidators,
NumFullNodes: numFullNodes,
},
{
Image: chainDImage,
Tag: chainDTag,
Binary: chainBinary,
NumValidators: numValidators,
NumFullNodes: numFullNodes,
},
}
}
// getConfigFilePath returns the absolute path where the e2e config file should be.
func getConfigFilePath() string {
if specifiedConfigPath := os.Getenv(E2EConfigFilePathEnv); specifiedConfigPath != "" {
if path.IsAbs(specifiedConfigPath) {
return specifiedConfigPath
}
e2eDir, err := directories.E2E()
if err != nil {
panic(err)
}
return path.Join(e2eDir, specifiedConfigPath)
}
if IsCI() {
if err := os.Setenv(E2EConfigFilePathEnv, defaultCIConfigFileName); err != nil {
panic(err)
}
return getConfigFilePath()
}
// running locally.
homeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}
return path.Join(homeDir, defaultConfigFileName)
}
// TODO: remove in https://github.com/cosmos/ibc-go/issues/4697
// getDefaultHermesRelayerConfig returns the default config for the hermes relayer.
func getDefaultHermesRelayerConfig() relayer.Config {
return relayer.Config{
Tag: defaultHermesTag,
ID: relayer.Hermes,
Image: relayer.HermesRelayerRepository,
}
}
// TODO: remove in https://github.com/cosmos/ibc-go/issues/4697
// getDefaultRlyRelayerConfig returns the default config for the golang relayer.
func getDefaultRlyRelayerConfig() relayer.Config {
return relayer.Config{
Tag: defaultRlyTag,
ID: relayer.Rly,
Image: relayer.RlyRelayerRepository,
}
}
func GetChainATag() string {
return LoadConfig().ChainConfigs[0].Tag
}
func GetChainBTag() string {
if chainBTag := LoadConfig().ChainConfigs[1].Tag; chainBTag != "" {
return chainBTag
}
return GetChainATag()
}
// IsCI returns true if the tests are running in CI, false is returned
// if the tests are running locally.
// Note: github actions passes a CI env value of true by default to all runners.
func IsCI() bool {
return isEnvTrue("CI")
}
// IsFork returns true if the tests are running in fork mode, false is returned otherwise.
func IsFork() bool {
return isEnvTrue("FORK")
}
// IsRunSuite returns true if the tests are running in suite mode, false is returned otherwise.
func IsRunSuite() bool {
return isEnvTrue("RUN_SUITE")
}
func isEnvTrue(env string) bool {
return strings.ToLower(os.Getenv(env)) == "true"
}
// ChainOptions stores chain configurations for the chains that will be
// created for the tests. They can be modified by passing ChainOptionConfiguration
// to E2ETestSuite.GetChains.
type ChainOptions struct {
ChainSpecs []*interchaintest.ChainSpec
SkipPathCreation bool
RelayerCount int
}
// ChainOptionConfiguration enables arbitrary configuration of ChainOptions.
type ChainOptionConfiguration func(options *ChainOptions)
// DefaultChainOptions returns the default configuration for the chains.
// These options can be configured by passing configuration functions to E2ETestSuite.GetChains.
func DefaultChainOptions(chainCount int) (ChainOptions, error) {
tc := LoadConfig()
if len(tc.ChainConfigs) < chainCount {
return ChainOptions{}, fmt.Errorf("file has %d configs. want %d configs", len(tc.ChainConfigs), chainCount)
}
specs := make([]*interchaintest.ChainSpec, 0, chainCount)
for i := range chainCount {
denom := fmt.Sprintf("atom%c", 'a'+i)
chainName := tc.GetChainName(i)
chainID := tc.GetChainID(i)
cfg := newDefaultSimappConfig(tc.ChainConfigs[0], chainName, chainID, denom, tc.CometBFTConfig)
validators, fullNodes := getValidatorsAndFullNodes(i)
spec := &interchaintest.ChainSpec{
ChainConfig: cfg,
NumFullNodes: &fullNodes,
NumValidators: &validators,
}
specs = append(specs, spec)
}
// if running a single test, only one relayer is needed.
numRelayers := 1
if IsRunSuite() {
// Arbitrary number; if interchaintest supports dynamic relayer creation during tests,
// this can be reduced or simplified.
numRelayers = 10
}
return ChainOptions{
ChainSpecs: specs,
RelayerCount: numRelayers,
}, nil
}
// newDefaultSimappConfig creates an ibc configuration for simd.
func newDefaultSimappConfig(cc ChainConfig, name, chainID, denom string, cometCfg CometBFTConfig) ibc.ChainConfig {
configFileOverrides := make(map[string]any)
tmTomlOverrides := make(interchaintestutil.Toml)
tmTomlOverrides["log_level"] = cometCfg.LogLevel // change to debug in the e2e test config to increase cometbft logging.
configFileOverrides["config/config.toml"] = tmTomlOverrides
return ibc.ChainConfig{
Type: "cosmos",
Name: name,
ChainID: chainID,
Images: []ibc.DockerImage{
{
Repository: cc.Image,
Version: cc.Tag,
UIDGID: "1000:1000",
},
},
Bin: cc.Binary,
Bech32Prefix: "cosmos",
CoinType: fmt.Sprint(sdk.CoinType),
Denom: denom,
EncodingConfig: SDKEncodingConfig(),
GasPrices: fmt.Sprintf("0.00%s", denom),
GasAdjustment: 1.3,
TrustingPeriod: "508h",
NoHostMount: false,
ModifyGenesis: getGenesisModificationFunction(cc),
ConfigFileOverrides: configFileOverrides,
}
}
// getGenesisModificationFunction returns a genesis modification function that handles the GenesisState type
// correctly depending on if the govv1beta1 gov module is used or if govv1 is being used.
func getGenesisModificationFunction(cc ChainConfig) func(ibc.ChainConfig, []byte) ([]byte, error) {
binary := cc.Binary
version := cc.Tag
simdSupportsGovV1Genesis := binary == defaultBinary && testvalues.GovGenesisFeatureReleases.IsSupported(version)
// TODO: Remove after we drop v7 support (this is only needed right now because of v6 -> v7 upgrade tests)
if simdSupportsGovV1Genesis {
return defaultGovv1ModifyGenesis(version)
}
return defaultGovv1Beta1ModifyGenesis(version)
}
// defaultGovv1ModifyGenesis will only modify governance params to ensure the voting period and minimum deposit
// are functional for e2e testing purposes.
func defaultGovv1ModifyGenesis(version string) func(ibc.ChainConfig, []byte) ([]byte, error) {
stdlibJSONMarshalling := semverutil.FeatureReleases{MajorVersion: "v8"}
return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) {
appGenesis, err := genutiltypes.AppGenesisFromReader(bytes.NewReader(genbz))
if err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into genesis doc: %w", err)
}
var appState genutiltypes.AppMap
if err := json.Unmarshal(appGenesis.AppState, &appState); err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into app state: %w", err)
}
govGenBz, err := modifyGovV1AppState(chainConfig, appState[govtypes.ModuleName])
if err != nil {
return nil, err
}
appState[govtypes.ModuleName] = govGenBz
if !testvalues.AllowAllClientsWildcardFeatureReleases.IsSupported(version) {
ibcGenBz, err := modifyClientGenesisAppState(appState[ibcexported.ModuleName])
if err != nil {
return nil, err
}
appState[ibcexported.ModuleName] = ibcGenBz
}
if !testvalues.ChannelParamsFeatureReleases.IsSupported(version) {
ibcGenBz, err := modifyChannelGenesisAppState(appState[ibcexported.ModuleName])
if err != nil {
return nil, err
}
appState[ibcexported.ModuleName] = ibcGenBz
}
if !testvalues.ChannelsV2FeatureReleases.IsSupported(version) {
ibcGenBz, err := modifyChannelV2GenesisAppState(appState[ibcexported.ModuleName])
if err != nil {
return nil, err
}
appState[ibcexported.ModuleName] = ibcGenBz
}
if !testvalues.ClientV2FeatureReleases.IsSupported(version) {
ibcGenBz, err := modifyClientV2GenesisAppState(appState[ibcexported.ModuleName])
if err != nil {
return nil, err
}
appState[ibcexported.ModuleName] = ibcGenBz
}
appGenesis.AppState, err = json.Marshal(appState)
if err != nil {
return nil, err
}
// in older version < v8, tmjson marshal must be used.
// regular json marshalling must be used for v8 and above as the
// sdk is de-coupled from comet.
marshalIndentFn := cmtjson.MarshalIndent
if stdlibJSONMarshalling.IsSupported(version) {
marshalIndentFn = json.MarshalIndent
}
bz, err := marshalIndentFn(appGenesis, "", " ")
if err != nil {
return nil, err
}
return bz, nil
}
}
// defaultGovv1Beta1ModifyGenesis will only modify governance params to ensure the voting period and minimum deposit
// // are functional for e2e testing purposes.
func defaultGovv1Beta1ModifyGenesis(version string) func(ibc.ChainConfig, []byte) ([]byte, error) {
const appStateKey = "app_state"
return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) {
genesisDocMap := map[string]any{}
err := json.Unmarshal(genbz, &genesisDocMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into genesis doc: %w", err)
}
appStateMap, ok := genesisDocMap[appStateKey].(map[string]any)
if !ok {
return nil, errors.New("failed to extract to app_state")
}
govModuleBytes, err := json.Marshal(appStateMap[govtypes.ModuleName])
if err != nil {
return nil, fmt.Errorf("failed to extract gov genesis bytes: %s", err)
}
govModuleGenesisBytes, err := modifyGovv1Beta1AppState(chainConfig, govModuleBytes)
if err != nil {
return nil, err
}
govModuleGenesisMap := map[string]any{}
err = json.Unmarshal(govModuleGenesisBytes, &govModuleGenesisMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err)
}
if !testvalues.AllowAllClientsWildcardFeatureReleases.IsSupported(version) {
ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName])
if err != nil {
return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err)
}
ibcGenesisBytes, err := modifyClientGenesisAppState(ibcModuleBytes)
if err != nil {
return nil, err
}
ibcModuleGenesisMap := map[string]any{}
err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err)
}
appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap
}
if !testvalues.ChannelParamsFeatureReleases.IsSupported(version) {
ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName])
if err != nil {
return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err)
}
ibcGenesisBytes, err := modifyChannelGenesisAppState(ibcModuleBytes)
if err != nil {
return nil, err
}
ibcModuleGenesisMap := map[string]any{}
err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err)
}
appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap
}
if !testvalues.ChannelsV2FeatureReleases.IsSupported(version) {
ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName])
if err != nil {
return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err)
}
ibcGenesisBytes, err := modifyChannelV2GenesisAppState(ibcModuleBytes)
if err != nil {
return nil, err
}
ibcModuleGenesisMap := map[string]any{}
err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err)
}
appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap
}
if !testvalues.ClientV2FeatureReleases.IsSupported(version) {
ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName])
if err != nil {
return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err)
}
ibcGenesisBytes, err := modifyClientV2GenesisAppState(ibcModuleBytes)
if err != nil {
return nil, err
}
ibcModuleGenesisMap := map[string]any{}
err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err)
}
appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap
}
appStateMap[govtypes.ModuleName] = govModuleGenesisMap
genesisDocMap[appStateKey] = appStateMap
finalGenesisDocBytes, err := json.MarshalIndent(genesisDocMap, "", " ")
if err != nil {
return nil, err
}
return finalGenesisDocBytes, nil
}
}
// modifyGovV1AppState takes the existing gov app state and marshals it to a govv1 GenesisState.
func modifyGovV1AppState(chainConfig ibc.ChainConfig, govAppState []byte) ([]byte, error) {
cfg := testutil.MakeTestEncodingConfig()
cdc := codec.NewProtoCodec(cfg.InterfaceRegistry)
govv1.RegisterInterfaces(cfg.InterfaceRegistry)
govGenesisState := &govv1.GenesisState{}
if err := cdc.UnmarshalJSON(govAppState, govGenesisState); err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into gov genesis state: %w", err)
}
if govGenesisState.Params == nil {
govGenesisState.Params = &govv1.Params{}
}
govGenesisState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(chainConfig.Denom, govv1beta1.DefaultMinDepositTokens))
maxDep := time.Second * 10
govGenesisState.Params.MaxDepositPeriod = &maxDep
vp := testvalues.VotingPeriod
govGenesisState.Params.VotingPeriod = &vp
govGenBz := MustProtoMarshalJSON(govGenesisState)
return govGenBz, nil
}
// modifyGovv1Beta1AppState takes the existing gov app state and marshals it to a govv1beta1 GenesisState.
func modifyGovv1Beta1AppState(chainConfig ibc.ChainConfig, govAppState []byte) ([]byte, error) {
cfg := testutil.MakeTestEncodingConfig()
cdc := codec.NewProtoCodec(cfg.InterfaceRegistry)
govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry)
govGenesisState := &govv1beta1.GenesisState{}
if err := cdc.UnmarshalJSON(govAppState, govGenesisState); err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into govv1beta1 genesis state: %w", err)
}
govGenesisState.DepositParams.MinDeposit = sdk.NewCoins(sdk.NewCoin(chainConfig.Denom, govv1beta1.DefaultMinDepositTokens))
govGenesisState.VotingParams.VotingPeriod = testvalues.VotingPeriod
govGenBz, err := cdc.MarshalJSON(govGenesisState)
if err != nil {
return nil, fmt.Errorf("failed to marshal gov genesis state: %w", err)
}
return govGenBz, nil
}
// modifyClientGenesisAppState takes the existing ibc app state and marshals it to an ibc GenesisState.
func modifyClientGenesisAppState(ibcAppState []byte) ([]byte, error) {
cfg := testutil.MakeTestEncodingConfig()
cdc := codec.NewProtoCodec(cfg.InterfaceRegistry)
clienttypes.RegisterInterfaces(cfg.InterfaceRegistry)
ibcGenesisState := &ibctypes.GenesisState{}
if err := cdc.UnmarshalJSON(ibcAppState, ibcGenesisState); err != nil {
return nil, fmt.Errorf("failed to unmarshal genesis bytes into client genesis state: %w", err)
}
ibcGenesisState.ClientGenesis.Params.AllowedClients = append(ibcGenesisState.ClientGenesis.Params.AllowedClients, wasmtypes.Wasm)
ibcGenBz, err := cdc.MarshalJSON(ibcGenesisState)
if err != nil {
return nil, fmt.Errorf("failed to marshal gov genesis state: %w", err)
}
return ibcGenBz, nil
}
// modifyChannelGenesisAppState takes the existing ibc app state, unmarshals it to a map and removes the `params` entry from ibc channel genesis.
// It marshals and returns the ibc GenesisState JSON map as bytes.
func modifyChannelGenesisAppState(ibcAppState []byte) ([]byte, error) {
var ibcGenesisMap map[string]any
if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil {
return nil, err
}
var channelGenesis map[string]any
// be ashamed, be very ashamed
channelGenesis, ok := ibcGenesisMap["channel_genesis"].(map[string]any)
if !ok {
return nil, fmt.Errorf("can't convert IBC genesis map entry into type %T", &channelGenesis)
}
delete(channelGenesis, "params")
return json.Marshal(ibcGenesisMap)
}
func modifyChannelV2GenesisAppState(ibcAppState []byte) ([]byte, error) {
var ibcGenesisMap map[string]any
if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil {
return nil, err
}
delete(ibcGenesisMap, "channel_v2_genesis")
return json.Marshal(ibcGenesisMap)
}
func modifyClientV2GenesisAppState(ibcAppState []byte) ([]byte, error) {
var ibcGenesisMap map[string]any
if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil {
return nil, err
}
delete(ibcGenesisMap, "client_v2_genesis")
return json.Marshal(ibcGenesisMap)
}