diff --git a/tests/interchain/consumer_chain/changeover_test.go b/tests/interchain/consumer_chain/changeover_test.go new file mode 100644 index 0000000000..7f88fd2395 --- /dev/null +++ b/tests/interchain/consumer_chain/changeover_test.go @@ -0,0 +1,242 @@ +package consumer_chain_test + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/gaia/v21/tests/interchain/chainsuite" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testutil" + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" + "golang.org/x/sync/errgroup" +) + +type ChangeoverSuite struct { + *chainsuite.Suite + consumerCfg chainsuite.ConsumerConfig + Consumer *chainsuite.Chain +} + +func (s *ChangeoverSuite) SetupSuite() { + s.Suite.SetupSuite() + validators := 1 + fullNodes := 0 + genesisChanges := []cosmos.GenesisKV{ + cosmos.NewGenesisKV("app_state.gov.params.voting_period", chainsuite.GovVotingPeriod.String()), + cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", chainsuite.GovDepositPeriod.String()), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", chainsuite.Ucon), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", strconv.Itoa(chainsuite.GovMinDepositAmount)), + } + spec := &interchaintest.ChainSpec{ + Name: "ics-consumer", + ChainName: "ics-consumer", + // Unfortunately, this rc is a bit of a bespoke version; it corresponds to an rc + // in hypha's fork that has a fix for sovereign -> consumer changeovers + Version: "v6.2.0-rc1", + NumValidators: &validators, + NumFullNodes: &fullNodes, + ChainConfig: ibc.ChainConfig{ + Denom: chainsuite.Ucon, + GasPrices: "0.025" + chainsuite.Ucon, + GasAdjustment: 2.0, + Gas: "auto", + ConfigFileOverrides: map[string]any{ + "config/config.toml": chainsuite.DefaultConfigToml(), + }, + ModifyGenesisAmounts: chainsuite.DefaultGenesisAmounts(chainsuite.Ucon), + ModifyGenesis: cosmos.ModifyGenesis(genesisChanges), + Bin: "interchain-security-sd", + Images: []ibc.DockerImage{ + { + Repository: chainsuite.HyphaICSRepo, + Version: "v6.2.0-rc1", + UidGid: chainsuite.ICSUidGuid, + }, + }, + Bech32Prefix: "consumer", + }, + } + consumer, err := s.Chain.AddLinkedChain(s.GetContext(), s.T(), s.Relayer, spec) + s.Require().NoError(err) + + s.Consumer = consumer + + s.UpgradeChain() +} + +func (s *ChangeoverSuite) TestRewardsWithChangeover() { + transferCh, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, s.Consumer) + s.Require().NoError(err) + rewardDenom := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", transferCh.ChannelID, s.Consumer.Config().Denom)).IBCDenom() + + s.Run("changeover", func() { + s.changeSovereignToConsumer(s.Consumer, transferCh) + }) + + s.Run("rewards", func() { + govAuthority, err := s.Chain.GetGovernanceAddress(s.GetContext()) + s.Require().NoError(err) + rewardDenomsProp := providertypes.MsgChangeRewardDenoms{ + DenomsToAdd: []string{rewardDenom}, + Authority: govAuthority, + } + prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{&rewardDenomsProp}, + "add denoms to list of registered reward denoms", + "add denoms to list of registered reward denoms", + "", chainsuite.GovDepositAmount, "", false) + s.Require().NoError(err) + propResult, err := s.Chain.SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) + s.Require().NoError(err) + s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propResult.ProposalID)) + + faucetAddrBts, err := s.Consumer.GetAddress(s.GetContext(), interchaintest.FaucetAccountKeyName) + s.Require().NoError(err) + faucetAddr := types.MustBech32ifyAddressBytes(s.Consumer.Config().Bech32Prefix, faucetAddrBts) + _, err = s.Consumer.Validators[0].ExecTx( + s.GetContext(), interchaintest.FaucetAccountKeyName, + "bank", "send", string(faucetAddr), s.Consumer.ValidatorWallets[0].Address, + "1"+s.Consumer.Config().Denom, "--fees", "100000000"+s.Consumer.Config().Denom, + ) + s.Require().NoError(err) + + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, s.Consumer)) + s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, s.Consumer)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, s.Consumer)) + + rewardStr, err := s.Chain.QueryJSON( + s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", rewardDenom), + "distribution", "rewards", s.Chain.ValidatorWallets[0].Address, + ) + s.Require().NoError(err) + rewards, err := chainsuite.StrToSDKInt(rewardStr.String()) + s.Require().NoError(err) + s.Require().True(rewards.GT(sdkmath.NewInt(0)), "rewards: %s", rewards.String()) + }) +} + +func TestChangeover(t *testing.T) { + s := &ChangeoverSuite{ + Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ + CreateRelayer: true, + ChainSpec: &interchaintest.ChainSpec{ + NumValidators: &chainsuite.OneValidator, + }, + }), + consumerCfg: chainsuite.ConsumerConfig{ + ChainName: "ics-consumer", + ShouldCopyProviderKey: allProviderKeysCopied(), + Denom: chainsuite.Ucon, + TopN: 0, + AllowInactiveVals: true, + MinStake: 1_000_000, + }, + } + suite.Run(t, s) +} + +func (s *ChangeoverSuite) changeSovereignToConsumer(consumer *chainsuite.Chain, transferCh *ibc.ChannelOutput) { + cfg := s.consumerCfg + currentHeight, err := consumer.Height(s.GetContext()) + s.Require().NoError(err) + initialHeight := uint64(currentHeight) + 60 + cfg.InitialHeight = initialHeight + spawnTime := time.Now().Add(60 * time.Second) + cfg.DistributionTransmissionChannel = transferCh.ChannelID + + err = s.Chain.CreateConsumerPermissionless(s.GetContext(), consumer.Config().ChainID, cfg, spawnTime) + s.Require().NoError(err) + + consumerChains, _, err := s.Chain.GetNode().ExecQuery(s.GetContext(), "provider", "list-consumer-chains") + s.Require().NoError(err) + consumerChain := gjson.GetBytes(consumerChains, fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID)) + consumerID := consumerChain.Get("consumer_id").String() + + eg := errgroup.Group{} + for i := range consumer.Validators { + i := i + eg.Go(func() error { + key, _, err := consumer.Validators[i].ExecBin(s.GetContext(), "tendermint", "show-validator") + if err != nil { + return err + } + keyStr := strings.TrimSpace(string(key)) + _, err = s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID, keyStr) + return err + }) + } + s.Require().NoError(eg.Wait()) + + s.Require().NoError(err) + time.Sleep(time.Until(spawnTime)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain)) + + proposal := cosmos.SoftwareUpgradeProposal{ + Deposit: "5000000" + chainsuite.Ucon, + Title: "Changeover", + Name: "sovereign-changeover", + Description: "Changeover", + Height: int64(initialHeight) - 3, + } + upgradeTx, err := consumer.UpgradeProposal(s.GetContext(), interchaintest.FaucetAccountKeyName, proposal) + s.Require().NoError(err) + err = consumer.PassProposal(s.GetContext(), upgradeTx.ProposalID) + s.Require().NoError(err) + + currentHeight, err = consumer.Height(s.GetContext()) + s.Require().NoError(err) + + timeoutCtx, timeoutCtxCancel := context.WithTimeout(s.GetContext(), (time.Duration(int64(initialHeight)-currentHeight)+10)*chainsuite.CommitTimeout) + defer timeoutCtxCancel() + err = testutil.WaitForBlocks(timeoutCtx, int(int64(initialHeight)-currentHeight)+3, consumer) + s.Require().Error(err) + + s.Require().NoError(consumer.StopAllNodes(s.GetContext())) + + genesis, err := consumer.GetNode().GenesisFileContent(s.GetContext()) + s.Require().NoError(err) + + ccvState, _, err := s.Chain.GetNode().ExecQuery(s.GetContext(), "provider", "consumer-genesis", consumerID) + s.Require().NoError(err) + genesis, err = sjson.SetRawBytes(genesis, "app_state.ccvconsumer", ccvState) + s.Require().NoError(err) + + genesis, err = sjson.SetBytes(genesis, "app_state.slashing.params.signed_blocks_window", strconv.Itoa(chainsuite.SlashingWindowConsumer)) + s.Require().NoError(err) + genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.reward_denoms", []string{chainsuite.Ucon}) + s.Require().NoError(err) + genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.provider_reward_denoms", []string{s.Chain.Config().Denom}) + s.Require().NoError(err) + genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.blocks_per_distribution_transmission", chainsuite.BlocksPerDistribution) + s.Require().NoError(err) + + for _, val := range consumer.Validators { + val := val + eg.Go(func() error { + if err := val.OverwriteGenesisFile(s.GetContext(), []byte(genesis)); err != nil { + return err + } + return val.WriteFile(s.GetContext(), []byte(genesis), ".sovereign/config/genesis.json") + }) + } + s.Require().NoError(eg.Wait()) + + consumer.ChangeBinary(s.GetContext(), "interchain-security-cdd") + s.Require().NoError(consumer.StartAllNodes(s.GetContext())) + s.Require().NoError(s.Relayer.ConnectProviderConsumer(s.GetContext(), s.Chain, consumer)) + s.Require().NoError(s.Relayer.StopRelayer(s.GetContext(), chainsuite.GetRelayerExecReporter(s.GetContext()))) + s.Require().NoError(s.Relayer.StartRelayer(s.GetContext(), chainsuite.GetRelayerExecReporter(s.GetContext()))) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) +} diff --git a/tests/interchain/consumer_chain/consumer_launch_test.go b/tests/interchain/consumer_chain/consumer_launch_test.go index 38d172aae7..7ea2635300 100644 --- a/tests/interchain/consumer_chain/consumer_launch_test.go +++ b/tests/interchain/consumer_chain/consumer_launch_test.go @@ -154,52 +154,3 @@ func TestICS6ConsumerNoKeysChainLaunch(t *testing.T) { } suite.Run(t, s) } - -type MainnetConsumerChainsSuite struct { - *chainsuite.Suite -} - -func (s *MainnetConsumerChainsSuite) TestMainnetConsumerChainsAfterUpgrade() { - // We can't do these consumer launches yet because the chains aren't compatible with launching on v21 yet - if semver.Major(s.Env.OldGaiaImageVersion) == s.Env.UpgradeName && s.Env.UpgradeName == "v21" { - s.T().Skip("Skipping Consumer Launch tests when going from v21 -> v21") - } - neutron, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{ - ChainName: "neutron", - Version: chainsuite.NeutronVersion, - ShouldCopyProviderKey: allProviderKeysCopied(), - Denom: chainsuite.NeutronDenom, - TopN: 95, - }) - s.Require().NoError(err) - stride, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{ - ChainName: "stride", - Version: chainsuite.StrideVersion, - ShouldCopyProviderKey: allProviderKeysCopied(), - Denom: chainsuite.StrideDenom, - TopN: 95, - }) - s.Require().NoError(err) - - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), neutron, s.Relayer, 1_000_000, 0, 1)) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), stride, s.Relayer, 1_000_000, 0, 1)) - - s.UpgradeChain() - - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), neutron, s.Relayer, 1_000_000, 0, 1)) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), stride, s.Relayer, 1_000_000, 0, 1)) - s.Require().NoError(chainsuite.SendSimpleIBCTx(s.GetContext(), s.Chain, neutron, s.Relayer)) - s.Require().NoError(chainsuite.SendSimpleIBCTx(s.GetContext(), s.Chain, stride, s.Relayer)) -} - -func TestMainnetConsumerChainsAfterUpgrade(t *testing.T) { - s := &MainnetConsumerChainsSuite{ - Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ - CreateRelayer: true, - ChainSpec: &interchaintest.ChainSpec{ - NumValidators: &chainsuite.SixValidators, - }, - }), - } - suite.Run(t, s) -} diff --git a/tests/interchain/consumer_chain/consumer_modification_test.go b/tests/interchain/consumer_chain/consumer_modification_test.go new file mode 100644 index 0000000000..d4239efa6c --- /dev/null +++ b/tests/interchain/consumer_chain/consumer_modification_test.go @@ -0,0 +1,409 @@ +package consumer_chain_test + +import ( + "context" + "encoding/json" + "fmt" + "path" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/gaia/v21/tests/interchain/chainsuite" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testutil" + "github.com/stretchr/testify/suite" + "golang.org/x/sync/errgroup" +) + +const ( + permissionlessDepositPeriod = 7 * time.Minute +) + +type ConsumerModificationSuite struct { + *chainsuite.Suite + consumerCfg chainsuite.ConsumerConfig +} + +func (s *ConsumerModificationSuite) TestChangeOwner() { + cfg := s.consumerCfg + cfg.TopN = 0 + cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + eg := errgroup.Group{} + for i := 0; i < 3; i++ { + i := i + eg.Go(func() error { + _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) + return err + }) + } + s.Require().NoError(eg.Wait()) + } + consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) + s.Require().NoError(err) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) + + govAddress, err := s.Chain.GetGovernanceAddress(s.GetContext()) + s.Require().NoError(err) + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + update := &providertypes.MsgUpdateConsumer{ + ConsumerId: consumerID, + NewOwnerAddress: govAddress, + Metadata: &providertypes.ConsumerMetadata{ + Name: consumer.Config().Name, + Description: "Consumer chain", + Metadata: "ipfs://", + }, + } + updateBz, err := json.Marshal(update) + s.Require().NoError(err) + err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") + s.Require().NoError(err) + _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, + "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) + s.Require().NoError(err) + + update.Owner = govAddress + update.NewOwnerAddress = s.Chain.ValidatorWallets[0].Address + prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{update}, + "update consumer", "update consumer", "", + chainsuite.GovDepositAmount, "", false) + s.Require().NoError(err) + txhash, err := s.Chain.GetNode().SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) + s.Require().NoError(err) + propID, err := s.Chain.GetProposalID(s.GetContext(), txhash) + s.Require().NoError(err) + s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propID)) +} + +func (s *ConsumerModificationSuite) TestChangePowerShaping() { + cfg := s.consumerCfg + cfg.TopN = 0 + const ( + oldValidatorCount = 4 + newValidatorCount = 3 + ) + cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + eg := errgroup.Group{} + for i := 0; i < oldValidatorCount; i++ { + i := i + eg.Go(func() error { + _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) + return err + }) + } + s.Require().NoError(eg.Wait()) + } + consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) + s.Require().NoError(err) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) + + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + update := &providertypes.MsgUpdateConsumer{ + ConsumerId: consumerID, + Metadata: &providertypes.ConsumerMetadata{ + Name: consumer.Config().Name, + Description: "Consumer chain", + Metadata: "ipfs://", + }, + PowerShapingParameters: &providertypes.PowerShapingParameters{ + ValidatorSetCap: newValidatorCount, + }, + } + updateBz, err := json.Marshal(update) + s.Require().NoError(err) + err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") + s.Require().NoError(err) + _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, + "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) + s.Require().NoError(err) + + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) + + vals, err := consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") + s.Require().NoError(err) + s.Require().Equal(newValidatorCount, len(vals.Array()), vals) + for i := 0; i < newValidatorCount; i++ { + valCons := vals.Array()[i].Get("address").String() + s.Require().NoError(err) + s.Require().Equal(consumer.ValidatorWallets[i].ValConsAddress, valCons) + } +} + +func (s *ConsumerModificationSuite) TestConsumerCommissionRate() { + cfg := s.consumerCfg + cfg.TopN = 0 + cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "opt-in", consumerID) + s.Require().NoError(err) + } + + images := []ibc.DockerImage{ + { + Repository: "ghcr.io/hyphacoop/ics", + Version: "v4.5.0", + UidGid: "1025:1025", + }, + } + chainID := fmt.Sprintf("%s-test-%d", cfg.ChainName, len(s.Chain.Consumers)+1) + spawnTime := time.Now().Add(chainsuite.ChainSpawnWait) + cfg.Spec = s.Chain.DefaultConsumerChainSpec(s.GetContext(), chainID, cfg, spawnTime, nil) + cfg.Spec.Version = "v4.5.0" + cfg.Spec.Images = images + consumer1, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) + s.Require().NoError(err) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer1, s.Relayer, 1_000_000, 0, 1)) + + chainID = fmt.Sprintf("%s-test-%d", cfg.ChainName, len(s.Chain.Consumers)+1) + spawnTime = time.Now().Add(chainsuite.ChainSpawnWait) + cfg.Spec = s.Chain.DefaultConsumerChainSpec(s.GetContext(), chainID, cfg, spawnTime, nil) + cfg.Spec.Version = "v4.5.0" + cfg.Spec.Images = images + consumer2, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) + s.Require().NoError(err) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer2, s.Relayer, 1_000_000, 0, 1)) + + for i := 1; i < len(consumer1.Validators); i++ { + s.Require().NoError(consumer1.Validators[i].StopContainer(s.GetContext())) + s.Require().NoError(consumer2.Validators[i].StopContainer(s.GetContext())) + } + + consumer1Ch, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer1) + s.Require().NoError(err) + consumer2Ch, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer2) + s.Require().NoError(err) + denom1 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", consumer1Ch.ChannelID, consumer1.Config().Denom)).IBCDenom() + denom2 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", consumer2Ch.ChannelID, consumer2.Config().Denom)).IBCDenom() + + s.Require().NotEqual(denom1, denom2, "denom1: %s, denom2: %s; channel1: %s, channel2: %s", denom1, denom2, consumer1Ch.Counterparty.ChannelID, consumer2Ch.Counterparty.ChannelID) + + govAuthority, err := s.Chain.GetGovernanceAddress(s.GetContext()) + s.Require().NoError(err) + rewardDenomsProp := providertypes.MsgChangeRewardDenoms{ + DenomsToAdd: []string{denom1, denom2}, + Authority: govAuthority, + } + prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{&rewardDenomsProp}, + "add denoms to list of registered reward denoms", + "add denoms to list of registered reward denoms", + "", chainsuite.GovDepositAmount, "", false) + s.Require().NoError(err) + propResult, err := s.Chain.SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) + s.Require().NoError(err) + s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propResult.ProposalID)) + + eg := errgroup.Group{} + + _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-all-rewards") + s.Require().NoError(err) + + _, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") + s.Require().NoError(err) + + consumerID1, err := s.Chain.GetConsumerID(s.GetContext(), consumer1.Config().ChainID) + s.Require().NoError(err) + consumerID2, err := s.Chain.GetConsumerID(s.GetContext(), consumer2.Config().ChainID) + s.Require().NoError(err) + + eg.Go(func() error { + _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID1, "0.5") + return err + }) + eg.Go(func() error { + _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID2, "0.5") + return err + }) + s.Require().NoError(eg.Wait()) + + _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") + s.Require().NoError(err) + + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, consumer1, consumer2)) + + eg.Go(func() error { + _, err := consumer1.Validators[0].ExecTx(s.GetContext(), consumer1.ValidatorWallets[0].Moniker, "bank", "send", consumer1.ValidatorWallets[0].Address, consumer1.ValidatorWallets[1].Address, "1"+consumer1.Config().Denom, "--fees", "100000000"+consumer1.Config().Denom) + return err + }) + eg.Go(func() error { + _, err := consumer2.Validators[0].ExecTx(s.GetContext(), consumer2.ValidatorWallets[0].Moniker, "bank", "send", consumer2.ValidatorWallets[0].Address, consumer2.ValidatorWallets[1].Address, "1"+consumer2.Config().Denom, "--fees", "100000000"+consumer2.Config().Denom) + return err + }) + s.Require().NoError(eg.Wait()) + + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, consumer1, consumer2)) + s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, consumer1)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, consumer1, consumer2)) + + rewardStr, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom1), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) + s.Require().NoError(err) + rewardsDenom1, err := chainsuite.StrToSDKInt(rewardStr.String()) + s.Require().NoError(err) + rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom2), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) + s.Require().NoError(err) + rewardsDenom2, err := chainsuite.StrToSDKInt(rewardStr.String()) + s.Require().NoError(err) + + s.Require().NotEmpty(rewardsDenom1) + s.Require().NotEmpty(rewardsDenom2) + s.Require().True(rewardsDenom1.Sub(rewardsDenom2).Abs().LT(sdkmath.NewInt(1000)), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) + + _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") + s.Require().NoError(err) + + eg.Go(func() error { + _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID1, "0.25") + return err + }) + eg.Go(func() error { + _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID2, "0.5") + return err + }) + s.Require().NoError(eg.Wait()) + + _, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") + s.Require().NoError(err) + + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, consumer1, consumer2)) + + eg.Go(func() error { + _, err := consumer1.Validators[0].ExecTx(s.GetContext(), consumer1.ValidatorWallets[0].Moniker, "bank", "send", consumer1.ValidatorWallets[0].Address, consumer1.ValidatorWallets[1].Address, "1"+consumer1.Config().Denom, "--fees", "100000000"+consumer1.Config().Denom) + return err + }) + eg.Go(func() error { + _, err := consumer2.Validators[0].ExecTx(s.GetContext(), consumer2.ValidatorWallets[0].Moniker, "bank", "send", consumer2.ValidatorWallets[0].Address, consumer2.ValidatorWallets[1].Address, "1"+consumer2.Config().Denom, "--fees", "100000000"+consumer2.Config().Denom) + return err + }) + s.Require().NoError(eg.Wait()) + + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, consumer1, consumer2)) + s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, consumer1)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, consumer1, consumer2)) + + rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom1), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) + s.Require().NoError(err) + rewardsDenom1, err = chainsuite.StrToSDKInt(rewardStr.String()) + s.Require().NoError(err) + rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom2), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) + s.Require().NoError(err) + rewardsDenom2, err = chainsuite.StrToSDKInt(rewardStr.String()) + s.Require().NoError(err) + + s.Require().True(rewardsDenom1.GT(rewardsDenom2), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) + s.Require().False(rewardsDenom1.Sub(rewardsDenom2).Abs().LT(sdkmath.NewInt(1000)), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) +} + +func (s *ConsumerModificationSuite) TestLaunchWithAllowListThenModify() { + consumerConfig := s.consumerCfg + consumerConfig.Allowlist = []string{ + s.Chain.ValidatorWallets[0].ValConsAddress, + s.Chain.ValidatorWallets[1].ValConsAddress, + s.Chain.ValidatorWallets[2].ValConsAddress, + } + consumerConfig.TopN = 0 + consumerConfig.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + eg := errgroup.Group{} + for i := 0; i < 3; i++ { + i := i + eg.Go(func() error { + _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) + return err + }) + } + s.Require().NoError(eg.Wait()) + } + + consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, consumerConfig) + s.Require().NoError(err) + + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) + + consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) + s.Require().NoError(err) + + // ensure we can't opt in a non-allowlisted validator + _, err = s.Chain.Validators[3].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[3].Moniker, + "provider", "opt-in", consumerID) + s.Require().NoError(err) + + validators, err := consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") + s.Require().NoError(err) + s.Require().Equal(3, len(validators.Array())) + + update := &providertypes.MsgUpdateConsumer{ + ConsumerId: consumerID, + PowerShapingParameters: &providertypes.PowerShapingParameters{ + Allowlist: []string{}, + }, + } + updateBz, err := json.Marshal(update) + s.Require().NoError(err) + err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") + s.Require().NoError(err) + _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, + "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) + s.Require().NoError(err) + + // // ensure we can opt in a non-allowlisted validator after the modification + _, err = s.Chain.Validators[3].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[3].Moniker, + "provider", "opt-in", consumerID) + s.Require().NoError(err) + validators, err = consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") + s.Require().NoError(err) + s.Require().Equal(4, len(validators.Array())) +} + +func TestConsumerModification(t *testing.T) { + genesis := chainsuite.DefaultGenesis() + genesis = append(genesis, + cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", permissionlessDepositPeriod.String()), + ) + s := &ConsumerModificationSuite{ + Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ + CreateRelayer: true, + Scope: chainsuite.ChainScopeTest, + UpgradeOnSetup: true, + ChainSpec: &interchaintest.ChainSpec{ + NumValidators: &chainsuite.SixValidators, + ChainConfig: ibc.ChainConfig{ + ModifyGenesis: cosmos.ModifyGenesis(genesis), + }, + }, + }), + consumerCfg: chainsuite.ConsumerConfig{ + ChainName: "ics-consumer", + Version: "v4.5.0", + ShouldCopyProviderKey: allProviderKeysCopied(), + Denom: chainsuite.Ucon, + TopN: 100, + AllowInactiveVals: true, + MinStake: 1_000_000, + Spec: &interchaintest.ChainSpec{ + NumValidators: &chainsuite.SixValidators, + ChainConfig: ibc.ChainConfig{ + Images: []ibc.DockerImage{ + { + Repository: chainsuite.HyphaICSRepo, + Version: "v4.5.0", + UidGid: chainsuite.ICSUidGuid, + }, + }, + }, + }, + }, + } + suite.Run(t, s) +} diff --git a/tests/interchain/consumer_chain/mainnet_consumers_test.go b/tests/interchain/consumer_chain/mainnet_consumers_test.go new file mode 100644 index 0000000000..20188ad87e --- /dev/null +++ b/tests/interchain/consumer_chain/mainnet_consumers_test.go @@ -0,0 +1,59 @@ +package consumer_chain_test + +import ( + "testing" + + "github.com/cosmos/gaia/v21/tests/interchain/chainsuite" + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/stretchr/testify/suite" + "golang.org/x/mod/semver" +) + +type MainnetConsumerChainsSuite struct { + *chainsuite.Suite +} + +func (s *MainnetConsumerChainsSuite) TestMainnetConsumerChainsAfterUpgrade() { + // We can't do these consumer launches yet because the chains aren't compatible with launching on v21 yet + if semver.Major(s.Env.OldGaiaImageVersion) == s.Env.UpgradeName && s.Env.UpgradeName == "v21" { + s.T().Skip("Skipping Consumer Launch tests when going from v21 -> v21") + } + neutron, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{ + ChainName: "neutron", + Version: chainsuite.NeutronVersion, + ShouldCopyProviderKey: allProviderKeysCopied(), + Denom: chainsuite.NeutronDenom, + TopN: 95, + }) + s.Require().NoError(err) + stride, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{ + ChainName: "stride", + Version: chainsuite.StrideVersion, + ShouldCopyProviderKey: allProviderKeysCopied(), + Denom: chainsuite.StrideDenom, + TopN: 95, + }) + s.Require().NoError(err) + + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), neutron, s.Relayer, 1_000_000, 0, 1)) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), stride, s.Relayer, 1_000_000, 0, 1)) + + s.UpgradeChain() + + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), neutron, s.Relayer, 1_000_000, 0, 1)) + s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), stride, s.Relayer, 1_000_000, 0, 1)) + s.Require().NoError(chainsuite.SendSimpleIBCTx(s.GetContext(), s.Chain, neutron, s.Relayer)) + s.Require().NoError(chainsuite.SendSimpleIBCTx(s.GetContext(), s.Chain, stride, s.Relayer)) +} + +func TestMainnetConsumerChainsAfterUpgrade(t *testing.T) { + s := &MainnetConsumerChainsSuite{ + Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ + CreateRelayer: true, + ChainSpec: &interchaintest.ChainSpec{ + NumValidators: &chainsuite.SixValidators, + }, + }), + } + suite.Run(t, s) +} diff --git a/tests/interchain/consumer_chain/permissionless_test.go b/tests/interchain/consumer_chain/permissionless_test.go deleted file mode 100644 index 3b754b5bc0..0000000000 --- a/tests/interchain/consumer_chain/permissionless_test.go +++ /dev/null @@ -1,911 +0,0 @@ -package consumer_chain_test - -import ( - "context" - "encoding/json" - "fmt" - "path" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - sdkmath "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - "github.com/cosmos/gaia/v21/tests/interchain/chainsuite" - transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" - ccvclient "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" - providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" - "github.com/strangelove-ventures/interchaintest/v8" - "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" - "github.com/strangelove-ventures/interchaintest/v8/ibc" - "github.com/strangelove-ventures/interchaintest/v8/testutil" - "github.com/stretchr/testify/suite" - "github.com/tidwall/gjson" - "github.com/tidwall/sjson" - "golang.org/x/mod/semver" - "golang.org/x/sync/errgroup" -) - -const ( - permissionlessDepositPeriod = 7 * time.Minute -) - -type PermissionlessConsumersSuite struct { - *chainsuite.Suite - consumerCfg chainsuite.ConsumerConfig -} - -func (s *PermissionlessConsumersSuite) addConsumer() *chainsuite.Chain { - consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, s.consumerCfg) - s.Require().NoError(err) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) - return consumer -} - -func (s *PermissionlessConsumersSuite) isOverV19() bool { - return semver.Compare(s.Env.OldGaiaImageVersion, "v19.0.0") > 0 -} - -func (s *PermissionlessConsumersSuite) TestConsumerAdditionMigration() { - if s.isOverV19() { - s.T().Skip("Migration test for v19 -> v20") - } - consumer := s.addConsumer() - json, _, err := s.Chain.GetNode().ExecQuery(s.GetContext(), "gov", "proposals") - s.Require().NoError(err) - chainsuite.GetLogger(s.GetContext()).Sugar().Infof("%s", string(json)) - - proposals, err := s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_PASSED) - s.Require().NoError(err) - s.Require().Len(proposals, 1) - oldProposalCh1 := proposals[0] - - chainIDCh2 := s.consumerCfg.ChainName + "-2" - propWaiter, errCh, err := s.Chain.SubmitConsumerAdditionProposal(s.GetContext(), chainIDCh2, s.consumerCfg, time.Now().Add(permissionlessDepositPeriod+2*time.Minute)) - s.Require().NoError(err) - - s.UpgradeChain() - - proposals, err = s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_PASSED) - s.Require().NoError(err) - s.Require().Len(proposals, 2) - newProposalCh1 := proposals[0] - s.Require().Equal(oldProposalCh1, newProposalCh1) - - proposals, err = s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_DEPOSIT_PERIOD) - s.Require().NoError(err) - s.Require().Len(proposals, 1) - newProposalCh2 := proposals[0] - s.Require().Contains(newProposalCh2.Messages[0].TypeUrl, "MsgUpdateConsumer") - - // check that the new chain is around - chain, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain.Exists()) - - chain2, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", chainIDCh2), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain2.Exists()) - s.Require().Equal(uint64(0), chain2.Get("top_N").Uint()) - - propWaiter.AllowDeposit() - propWaiter.WaitForVotingPeriod() - propWaiter.AllowVote() - propWaiter.WaitForPassed() - s.Require().NoError(<-errCh) - - testutil.WaitForBlocks(s.GetContext(), 2, s.Chain) - - chain2, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", chainIDCh2), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain2.Exists()) - s.Require().Equal(uint64(100), chain2.Get("top_N").Uint()) -} - -func (s *PermissionlessConsumersSuite) TestConsumerRemovalMigration() { - if s.isOverV19() { - s.T().Skip("Migration test for v19 -> v20") - } - - consumer := s.addConsumer() - - stopTime := time.Now().Add(permissionlessDepositPeriod + 2*time.Minute) - - propID := s.submitConsumerRemoval(consumer, stopTime) - - s.UpgradeChain() - - proposals, err := s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_DEPOSIT_PERIOD) - s.Require().NoError(err) - s.Require().Len(proposals, 1) - newProposalCh2 := proposals[0] - s.Require().Contains(newProposalCh2.Messages[0].TypeUrl, "MsgRemoveConsumer") - - chain, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain.Exists()) - - s.depositAndPass(propID) - - chainsuite.GetLogger(s.GetContext()).Sugar().Infof("waiting for stop time %s", stopTime) - time.Sleep(time.Until(stopTime)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain)) - - chain, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain.Exists()) - s.Require().Equal("CONSUMER_PHASE_STOPPED", chain.Get("phase").String()) -} - -func (s *PermissionlessConsumersSuite) TestConsumerModificationMigration() { - if s.isOverV19() { - s.T().Skip("Migration test for v19 -> v20") - } - - consumer := s.addConsumer() - - propID := s.submitConsumerModification(consumer) - - s.UpgradeChain() - - proposals, err := s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_DEPOSIT_PERIOD) - s.Require().NoError(err) - s.Require().Len(proposals, 1) - newProposalCh2 := proposals[0] - s.Require().Contains(newProposalCh2.Messages[0].TypeUrl, "MsgUpdateConsumer") - - s.depositAndPass(propID) - - chain, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain.Exists()) - s.Require().Equal(uint64(80), chain.Get("top_N").Uint()) -} - -func (s *PermissionlessConsumersSuite) TestChangeRewardDenomMigration() { - if s.isOverV19() { - s.T().Skip("Migration test for v19 -> v20") - } - - consumer := s.addConsumer() - - denom, propID := s.submitChangeRewardDenoms(consumer) - - s.UpgradeChain() - - proposals, err := s.Chain.GovQueryProposalsV1(s.GetContext(), govtypes.ProposalStatus_PROPOSAL_STATUS_DEPOSIT_PERIOD) - s.Require().NoError(err) - s.Require().Len(proposals, 1) - newProposalCh2 := proposals[0] - s.Require().Contains(newProposalCh2.Messages[0].TypeUrl, "MsgChangeRewardDenoms") - - s.depositAndPass(propID) - - denoms, err := s.Chain.QueryJSON(s.GetContext(), "denoms", "provider", "registered-consumer-reward-denoms") - s.Require().NoError(err) - s.Require().Contains(denoms.String(), denom) -} - -func (s *PermissionlessConsumersSuite) depositAndPass(propID string) { - _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "gov", "deposit", propID, chainsuite.GovDepositAmount) - s.Require().NoError(err) - s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propID)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain)) -} - -func (s *PermissionlessConsumersSuite) TestPassedProposalsDontChange() { - if s.isOverV19() { - s.T().Skip("Migration test for v19 -> v20") - } - consumer := s.addConsumer() - - _, denomPropID := s.submitChangeRewardDenoms(consumer) - s.depositAndPass(denomPropID) - - denomPropIDInt, err := strconv.Atoi(denomPropID) - s.Require().NoError(err) - denomProposal, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(denomPropIDInt)) - s.Require().NoError(err) - - modificationPropID := s.submitConsumerModification(consumer) - s.depositAndPass(modificationPropID) - - modificationPropIDInt, err := strconv.Atoi(modificationPropID) - s.Require().NoError(err) - modificationProposal, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(modificationPropIDInt)) - s.Require().NoError(err) - - stopTime := time.Now().Add(permissionlessDepositPeriod + 2*time.Minute) - removalPropID := s.submitConsumerRemoval(consumer, stopTime) - s.depositAndPass(removalPropID) - - removalPropIDInt, err := strconv.Atoi(removalPropID) - s.Require().NoError(err) - removalProposal, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(removalPropIDInt)) - s.Require().NoError(err) - - s.UpgradeChain() - - denomProposalAfter, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(denomPropIDInt)) - s.Require().NoError(err) - s.Require().Equal(denomProposal, denomProposalAfter) - - modificationProposalAfter, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(modificationPropIDInt)) - s.Require().NoError(err) - s.Require().Equal(modificationProposal, modificationProposalAfter) - - removalProposalAfter, err := s.Chain.GovQueryProposalV1(s.GetContext(), uint64(removalPropIDInt)) - s.Require().NoError(err) - s.Require().Equal(removalProposal, removalProposalAfter) - - time.Sleep(time.Until(stopTime)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain)) - - chain, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID), "provider", "list-consumer-chains") - s.Require().NoError(err) - s.Require().True(chain.Exists()) - s.Require().Equal("CONSUMER_PHASE_STOPPED", chain.Get("phase").String()) -} - -func (s *PermissionlessConsumersSuite) TestChangeOwner() { - s.UpgradeChain() - - cfg := s.consumerCfg - cfg.TopN = 0 - cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - eg := errgroup.Group{} - for i := 0; i < 3; i++ { - i := i - eg.Go(func() error { - _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) - return err - }) - } - s.Require().NoError(eg.Wait()) - } - consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) - s.Require().NoError(err) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) - - govAddress, err := s.Chain.GetGovernanceAddress(s.GetContext()) - s.Require().NoError(err) - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - update := &providertypes.MsgUpdateConsumer{ - ConsumerId: consumerID, - NewOwnerAddress: govAddress, - Metadata: &providertypes.ConsumerMetadata{ - Name: consumer.Config().Name, - Description: "Consumer chain", - Metadata: "ipfs://", - }, - } - updateBz, err := json.Marshal(update) - s.Require().NoError(err) - err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") - s.Require().NoError(err) - _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, - "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) - s.Require().NoError(err) - - update.Owner = govAddress - update.NewOwnerAddress = s.Chain.ValidatorWallets[0].Address - prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{update}, - "update consumer", "update consumer", "", - chainsuite.GovDepositAmount, "", false) - s.Require().NoError(err) - txhash, err := s.Chain.GetNode().SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) - s.Require().NoError(err) - propID, err := s.Chain.GetProposalID(s.GetContext(), txhash) - s.Require().NoError(err) - s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propID)) -} - -func (s *PermissionlessConsumersSuite) TestChangePowerShaping() { - s.UpgradeChain() - - cfg := s.consumerCfg - cfg.TopN = 0 - const ( - oldValidatorCount = 4 - newValidatorCount = 3 - ) - cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - eg := errgroup.Group{} - for i := 0; i < oldValidatorCount; i++ { - i := i - eg.Go(func() error { - _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) - return err - }) - } - s.Require().NoError(eg.Wait()) - } - consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) - s.Require().NoError(err) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) - - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - update := &providertypes.MsgUpdateConsumer{ - ConsumerId: consumerID, - Metadata: &providertypes.ConsumerMetadata{ - Name: consumer.Config().Name, - Description: "Consumer chain", - Metadata: "ipfs://", - }, - PowerShapingParameters: &providertypes.PowerShapingParameters{ - ValidatorSetCap: newValidatorCount, - }, - } - updateBz, err := json.Marshal(update) - s.Require().NoError(err) - err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") - s.Require().NoError(err) - _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, - "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) - s.Require().NoError(err) - - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) - - vals, err := consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") - s.Require().NoError(err) - s.Require().Equal(newValidatorCount, len(vals.Array()), vals) - for i := 0; i < newValidatorCount; i++ { - valCons := vals.Array()[i].Get("address").String() - s.Require().NoError(err) - s.Require().Equal(consumer.ValidatorWallets[i].ValConsAddress, valCons) - } -} - -func (s *PermissionlessConsumersSuite) TestConsumerCommissionRate() { - s.UpgradeChain() - cfg := s.consumerCfg - - cfg.TopN = 0 - cfg.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "opt-in", consumerID) - s.Require().NoError(err) - } - - images := []ibc.DockerImage{ - { - Repository: "ghcr.io/hyphacoop/ics", - Version: "v4.5.0", - UidGid: "1025:1025", - }, - } - chainID := fmt.Sprintf("%s-test-%d", cfg.ChainName, len(s.Chain.Consumers)+1) - spawnTime := time.Now().Add(chainsuite.ChainSpawnWait) - cfg.Spec = s.Chain.DefaultConsumerChainSpec(s.GetContext(), chainID, cfg, spawnTime, nil) - cfg.Spec.Version = "v4.5.0" - cfg.Spec.Images = images - consumer1, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) - s.Require().NoError(err) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer1, s.Relayer, 1_000_000, 0, 1)) - - chainID = fmt.Sprintf("%s-test-%d", cfg.ChainName, len(s.Chain.Consumers)+1) - spawnTime = time.Now().Add(chainsuite.ChainSpawnWait) - cfg.Spec = s.Chain.DefaultConsumerChainSpec(s.GetContext(), chainID, cfg, spawnTime, nil) - cfg.Spec.Version = "v4.5.0" - cfg.Spec.Images = images - consumer2, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, cfg) - s.Require().NoError(err) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer2, s.Relayer, 1_000_000, 0, 1)) - - for i := 1; i < len(consumer1.Validators); i++ { - s.Require().NoError(consumer1.Validators[i].StopContainer(s.GetContext())) - s.Require().NoError(consumer2.Validators[i].StopContainer(s.GetContext())) - } - - consumer1Ch, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer1) - s.Require().NoError(err) - consumer2Ch, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer2) - s.Require().NoError(err) - denom1 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", consumer1Ch.ChannelID, consumer1.Config().Denom)).IBCDenom() - denom2 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", consumer2Ch.ChannelID, consumer2.Config().Denom)).IBCDenom() - - s.Require().NotEqual(denom1, denom2, "denom1: %s, denom2: %s; channel1: %s, channel2: %s", denom1, denom2, consumer1Ch.Counterparty.ChannelID, consumer2Ch.Counterparty.ChannelID) - - govAuthority, err := s.Chain.GetGovernanceAddress(s.GetContext()) - s.Require().NoError(err) - rewardDenomsProp := providertypes.MsgChangeRewardDenoms{ - DenomsToAdd: []string{denom1, denom2}, - Authority: govAuthority, - } - prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{&rewardDenomsProp}, - "add denoms to list of registered reward denoms", - "add denoms to list of registered reward denoms", - "", chainsuite.GovDepositAmount, "", false) - s.Require().NoError(err) - propResult, err := s.Chain.SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) - s.Require().NoError(err) - s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propResult.ProposalID)) - - eg := errgroup.Group{} - - _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-all-rewards") - s.Require().NoError(err) - - _, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") - s.Require().NoError(err) - - consumerID1, err := s.Chain.GetConsumerID(s.GetContext(), consumer1.Config().ChainID) - s.Require().NoError(err) - consumerID2, err := s.Chain.GetConsumerID(s.GetContext(), consumer2.Config().ChainID) - s.Require().NoError(err) - - eg.Go(func() error { - _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID1, "0.5") - return err - }) - eg.Go(func() error { - _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID2, "0.5") - return err - }) - s.Require().NoError(eg.Wait()) - - _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") - s.Require().NoError(err) - - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, consumer1, consumer2)) - - eg.Go(func() error { - _, err := consumer1.Validators[0].ExecTx(s.GetContext(), consumer1.ValidatorWallets[0].Moniker, "bank", "send", consumer1.ValidatorWallets[0].Address, consumer1.ValidatorWallets[1].Address, "1"+consumer1.Config().Denom, "--fees", "100000000"+consumer1.Config().Denom) - return err - }) - eg.Go(func() error { - _, err := consumer2.Validators[0].ExecTx(s.GetContext(), consumer2.ValidatorWallets[0].Moniker, "bank", "send", consumer2.ValidatorWallets[0].Address, consumer2.ValidatorWallets[1].Address, "1"+consumer2.Config().Denom, "--fees", "100000000"+consumer2.Config().Denom) - return err - }) - s.Require().NoError(eg.Wait()) - - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, consumer1, consumer2)) - s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, consumer1)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, consumer1, consumer2)) - - rewardStr, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom1), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) - s.Require().NoError(err) - rewardsDenom1, err := chainsuite.StrToSDKInt(rewardStr.String()) - s.Require().NoError(err) - rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom2), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) - s.Require().NoError(err) - rewardsDenom2, err := chainsuite.StrToSDKInt(rewardStr.String()) - s.Require().NoError(err) - - s.Require().NotEmpty(rewardsDenom1) - s.Require().NotEmpty(rewardsDenom2) - s.Require().True(rewardsDenom1.Sub(rewardsDenom2).Abs().LT(sdkmath.NewInt(1000)), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) - - _, err = s.Chain.Validators[0].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") - s.Require().NoError(err) - - eg.Go(func() error { - _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID1, "0.25") - return err - }) - eg.Go(func() error { - _, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "provider", "set-consumer-commission-rate", consumerID2, "0.5") - return err - }) - s.Require().NoError(eg.Wait()) - - _, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, "distribution", "withdraw-rewards", s.Chain.ValidatorWallets[0].ValoperAddress, "--commission") - s.Require().NoError(err) - - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, consumer1, consumer2)) - - eg.Go(func() error { - _, err := consumer1.Validators[0].ExecTx(s.GetContext(), consumer1.ValidatorWallets[0].Moniker, "bank", "send", consumer1.ValidatorWallets[0].Address, consumer1.ValidatorWallets[1].Address, "1"+consumer1.Config().Denom, "--fees", "100000000"+consumer1.Config().Denom) - return err - }) - eg.Go(func() error { - _, err := consumer2.Validators[0].ExecTx(s.GetContext(), consumer2.ValidatorWallets[0].Moniker, "bank", "send", consumer2.ValidatorWallets[0].Address, consumer2.ValidatorWallets[1].Address, "1"+consumer2.Config().Denom, "--fees", "100000000"+consumer2.Config().Denom) - return err - }) - s.Require().NoError(eg.Wait()) - - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, consumer1, consumer2)) - s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, consumer1)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, consumer1, consumer2)) - - rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom1), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) - s.Require().NoError(err) - rewardsDenom1, err = chainsuite.StrToSDKInt(rewardStr.String()) - s.Require().NoError(err) - rewardStr, err = s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", denom2), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) - s.Require().NoError(err) - rewardsDenom2, err = chainsuite.StrToSDKInt(rewardStr.String()) - s.Require().NoError(err) - - s.Require().True(rewardsDenom1.GT(rewardsDenom2), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) - s.Require().False(rewardsDenom1.Sub(rewardsDenom2).Abs().LT(sdkmath.NewInt(1000)), "rewards1Int: %s, rewards2Int: %s", rewardsDenom1.String(), rewardsDenom2.String()) -} - -func (s *PermissionlessConsumersSuite) TestLaunchWithAllowListThenModify() { - s.UpgradeChain() - - consumerConfig := s.consumerCfg - consumerConfig.Allowlist = []string{ - s.Chain.ValidatorWallets[0].ValConsAddress, - s.Chain.ValidatorWallets[1].ValConsAddress, - s.Chain.ValidatorWallets[2].ValConsAddress, - } - consumerConfig.TopN = 0 - consumerConfig.BeforeSpawnTime = func(ctx context.Context, consumer *cosmos.CosmosChain) { - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - eg := errgroup.Group{} - for i := 0; i < 3; i++ { - i := i - eg.Go(func() error { - _, err := s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID) - return err - }) - } - s.Require().NoError(eg.Wait()) - } - - consumer, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, consumerConfig) - s.Require().NoError(err) - - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) - - consumerID, err := s.Chain.GetConsumerID(s.GetContext(), consumer.Config().ChainID) - s.Require().NoError(err) - - // ensure we can't opt in a non-allowlisted validator - _, err = s.Chain.Validators[3].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[3].Moniker, - "provider", "opt-in", consumerID) - s.Require().NoError(err) - - validators, err := consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") - s.Require().NoError(err) - s.Require().Equal(3, len(validators.Array())) - - update := &providertypes.MsgUpdateConsumer{ - ConsumerId: consumerID, - PowerShapingParameters: &providertypes.PowerShapingParameters{ - Allowlist: []string{}, - }, - } - updateBz, err := json.Marshal(update) - s.Require().NoError(err) - err = s.Chain.GetNode().WriteFile(s.GetContext(), updateBz, "consumer-update.json") - s.Require().NoError(err) - _, err = s.Chain.GetNode().ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, - "provider", "update-consumer", path.Join(s.Chain.GetNode().HomeDir(), "consumer-update.json")) - s.Require().NoError(err) - - // // ensure we can opt in a non-allowlisted validator after the modification - _, err = s.Chain.Validators[3].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[3].Moniker, - "provider", "opt-in", consumerID) - s.Require().NoError(err) - validators, err = consumer.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") - s.Require().NoError(err) - s.Require().Equal(4, len(validators.Array())) -} - -func (s *PermissionlessConsumersSuite) TestRewardsWithChangeover() { - validators := 1 - fullNodes := 0 - genesisChanges := []cosmos.GenesisKV{ - cosmos.NewGenesisKV("app_state.gov.params.voting_period", chainsuite.GovVotingPeriod.String()), - cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", chainsuite.GovDepositPeriod.String()), - cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", chainsuite.Ucon), - cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", strconv.Itoa(chainsuite.GovMinDepositAmount)), - } - spec := &interchaintest.ChainSpec{ - Name: "ics-consumer", - ChainName: "ics-consumer", - // Unfortunately, this rc is a bit of a bespoke version; it corresponds to an rc - // in hypha's fork that has a fix for sovereign -> consumer changeovers - Version: "v6.2.0-rc1", - NumValidators: &validators, - NumFullNodes: &fullNodes, - ChainConfig: ibc.ChainConfig{ - Denom: chainsuite.Ucon, - GasPrices: "0.025" + chainsuite.Ucon, - GasAdjustment: 2.0, - Gas: "auto", - ConfigFileOverrides: map[string]any{ - "config/config.toml": chainsuite.DefaultConfigToml(), - }, - ModifyGenesisAmounts: chainsuite.DefaultGenesisAmounts(chainsuite.Ucon), - ModifyGenesis: cosmos.ModifyGenesis(genesisChanges), - Bin: "interchain-security-sd", - Images: []ibc.DockerImage{ - { - Repository: chainsuite.HyphaICSRepo, - Version: "v6.2.0-rc1", - UidGid: chainsuite.ICSUidGuid, - }, - }, - Bech32Prefix: "consumer", - }, - } - consumer, err := s.Chain.AddLinkedChain(s.GetContext(), s.T(), s.Relayer, spec) - s.Require().NoError(err) - - transferCh, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer) - s.Require().NoError(err) - rewardDenom := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", transferCh.ChannelID, consumer.Config().Denom)).IBCDenom() - - s.UpgradeChain() - - s.changeSovereignToConsumer(consumer, transferCh) - - govAuthority, err := s.Chain.GetGovernanceAddress(s.GetContext()) - s.Require().NoError(err) - rewardDenomsProp := providertypes.MsgChangeRewardDenoms{ - DenomsToAdd: []string{rewardDenom}, - Authority: govAuthority, - } - prop, err := s.Chain.BuildProposal([]cosmos.ProtoMessage{&rewardDenomsProp}, - "add denoms to list of registered reward denoms", - "add denoms to list of registered reward denoms", - "", chainsuite.GovDepositAmount, "", false) - s.Require().NoError(err) - propResult, err := s.Chain.SubmitProposal(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, prop) - s.Require().NoError(err) - s.Require().NoError(s.Chain.PassProposal(s.GetContext(), propResult.ProposalID)) - - faucetAddrBts, err := consumer.GetAddress(s.GetContext(), interchaintest.FaucetAccountKeyName) - s.Require().NoError(err) - faucetAddr := types.MustBech32ifyAddressBytes(consumer.Config().Bech32Prefix, faucetAddrBts) - _, err = consumer.Validators[0].ExecTx(s.GetContext(), interchaintest.FaucetAccountKeyName, "bank", "send", string(faucetAddr), consumer.ValidatorWallets[0].Address, "1"+consumer.Config().Denom, "--fees", "100000000"+consumer.Config().Denom) - s.Require().NoError(err) - - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), chainsuite.BlocksPerDistribution+2, s.Chain, consumer)) - s.Require().NoError(s.Relayer.ClearTransferChannel(s.GetContext(), s.Chain, consumer)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain, consumer)) - - rewardStr, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("total.#(%%\"*%s\")", rewardDenom), "distribution", "rewards", s.Chain.ValidatorWallets[0].Address) - s.Require().NoError(err) - rewards, err := chainsuite.StrToSDKInt(rewardStr.String()) - s.Require().NoError(err) - s.Require().True(rewards.GT(sdkmath.NewInt(0)), "rewards: %s", rewards.String()) -} - -func TestPermissionlessConsumers(t *testing.T) { - genesis := chainsuite.DefaultGenesis() - genesis = append(genesis, - cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", permissionlessDepositPeriod.String()), - ) - s := &PermissionlessConsumersSuite{ - Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ - CreateRelayer: true, - Scope: chainsuite.ChainScopeTest, - ChainSpec: &interchaintest.ChainSpec{ - NumValidators: &chainsuite.SixValidators, - ChainConfig: ibc.ChainConfig{ - ModifyGenesis: cosmos.ModifyGenesis(genesis), - }, - }, - }), - consumerCfg: chainsuite.ConsumerConfig{ - ChainName: "ics-consumer", - Version: "v4.5.0", - ShouldCopyProviderKey: allProviderKeysCopied(), - Denom: chainsuite.Ucon, - TopN: 100, - AllowInactiveVals: true, - MinStake: 1_000_000, - Spec: &interchaintest.ChainSpec{ - NumValidators: &chainsuite.SixValidators, - ChainConfig: ibc.ChainConfig{ - Images: []ibc.DockerImage{ - { - Repository: chainsuite.HyphaICSRepo, - Version: "v4.5.0", - UidGid: chainsuite.ICSUidGuid, - }, - }, - }, - }, - }, - } - suite.Run(t, s) -} - -func (s *PermissionlessConsumersSuite) changeSovereignToConsumer(consumer *chainsuite.Chain, transferCh *ibc.ChannelOutput) { - cfg := s.consumerCfg - cfg.TopN = 0 - currentHeight, err := consumer.Height(s.GetContext()) - s.Require().NoError(err) - initialHeight := uint64(currentHeight) + 60 - cfg.InitialHeight = initialHeight - spawnTime := time.Now().Add(60 * time.Second) - cfg.DistributionTransmissionChannel = transferCh.ChannelID - - err = s.Chain.CreateConsumerPermissionless(s.GetContext(), consumer.Config().ChainID, cfg, spawnTime) - s.Require().NoError(err) - - consumerChains, _, err := s.Chain.GetNode().ExecQuery(s.GetContext(), "provider", "list-consumer-chains") - s.Require().NoError(err) - consumerChain := gjson.GetBytes(consumerChains, fmt.Sprintf("chains.#(chain_id=%q)", consumer.Config().ChainID)) - consumerID := consumerChain.Get("consumer_id").String() - - eg := errgroup.Group{} - for i := range consumer.Validators { - i := i - eg.Go(func() error { - key, _, err := consumer.Validators[i].ExecBin(s.GetContext(), "tendermint", "show-validator") - if err != nil { - return err - } - keyStr := strings.TrimSpace(string(key)) - _, err = s.Chain.Validators[i].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[i].Moniker, "provider", "opt-in", consumerID, keyStr) - return err - }) - } - s.Require().NoError(eg.Wait()) - - s.Require().NoError(err) - time.Sleep(time.Until(spawnTime)) - s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 2, s.Chain)) - - proposal := cosmos.SoftwareUpgradeProposal{ - Deposit: "5000000" + chainsuite.Ucon, - Title: "Changeover", - Name: "sovereign-changeover", - Description: "Changeover", - Height: int64(initialHeight) - 3, - } - upgradeTx, err := consumer.UpgradeProposal(s.GetContext(), interchaintest.FaucetAccountKeyName, proposal) - s.Require().NoError(err) - err = consumer.PassProposal(s.GetContext(), upgradeTx.ProposalID) - s.Require().NoError(err) - - currentHeight, err = consumer.Height(s.GetContext()) - s.Require().NoError(err) - - timeoutCtx, timeoutCtxCancel := context.WithTimeout(s.GetContext(), (time.Duration(int64(initialHeight)-currentHeight)+10)*chainsuite.CommitTimeout) - defer timeoutCtxCancel() - err = testutil.WaitForBlocks(timeoutCtx, int(int64(initialHeight)-currentHeight)+3, consumer) - s.Require().Error(err) - - s.Require().NoError(consumer.StopAllNodes(s.GetContext())) - - genesis, err := consumer.GetNode().GenesisFileContent(s.GetContext()) - s.Require().NoError(err) - - ccvState, _, err := s.Chain.GetNode().ExecQuery(s.GetContext(), "provider", "consumer-genesis", consumerID) - s.Require().NoError(err) - genesis, err = sjson.SetRawBytes(genesis, "app_state.ccvconsumer", ccvState) - s.Require().NoError(err) - - genesis, err = sjson.SetBytes(genesis, "app_state.slashing.params.signed_blocks_window", strconv.Itoa(chainsuite.SlashingWindowConsumer)) - s.Require().NoError(err) - genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.reward_denoms", []string{chainsuite.Ucon}) - s.Require().NoError(err) - genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.provider_reward_denoms", []string{s.Chain.Config().Denom}) - s.Require().NoError(err) - genesis, err = sjson.SetBytes(genesis, "app_state.ccvconsumer.params.blocks_per_distribution_transmission", chainsuite.BlocksPerDistribution) - s.Require().NoError(err) - - for _, val := range consumer.Validators { - val := val - eg.Go(func() error { - if err := val.OverwriteGenesisFile(s.GetContext(), []byte(genesis)); err != nil { - return err - } - return val.WriteFile(s.GetContext(), []byte(genesis), ".sovereign/config/genesis.json") - }) - } - s.Require().NoError(eg.Wait()) - - consumer.ChangeBinary(s.GetContext(), "interchain-security-cdd") - s.Require().NoError(consumer.StartAllNodes(s.GetContext())) - s.Require().NoError(s.Relayer.ConnectProviderConsumer(s.GetContext(), s.Chain, consumer)) - s.Require().NoError(s.Relayer.StopRelayer(s.GetContext(), chainsuite.GetRelayerExecReporter(s.GetContext()))) - s.Require().NoError(s.Relayer.StartRelayer(s.GetContext(), chainsuite.GetRelayerExecReporter(s.GetContext()))) - s.Require().NoError(s.Chain.CheckCCV(s.GetContext(), consumer, s.Relayer, 1_000_000, 0, 1)) -} - -func (s *PermissionlessConsumersSuite) submitChangeRewardDenoms(consumer *chainsuite.Chain) (string, string) { - consumerCh, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, consumer) - s.Require().NoError(err) - denom := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", consumerCh.ChannelID, consumer.Config().Denom)).IBCDenom() - - denomProp := &ccvclient.ChangeRewardDenomsProposalJSON{ - ChangeRewardDenomsProposal: providertypes.ChangeRewardDenomsProposal{ - Title: "change reward denoms", - Description: "change reward denoms", - DenomsToAdd: []string{denom}, - DenomsToRemove: []string{}, - }, - Deposit: fmt.Sprintf("%d%s", chainsuite.GovMinDepositAmount/2, s.Chain.Config().Denom), - Summary: "change reward denoms", - } - propBz, err := json.Marshal(denomProp) - s.Require().NoError(err) - - fileName := "proposal_consumer_denoms.json" - - s.Require().NoError(s.Chain.GetNode().WriteFile(s.GetContext(), propBz, fileName)) - - filePath := filepath.Join(s.Chain.GetNode().HomeDir(), fileName) - - txhash, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, - "gov", "submit-legacy-proposal", "change-reward-denoms", filePath, - "--gas", "auto", - ) - s.Require().NoError(err) - - propID, err := s.Chain.GetProposalID(s.GetContext(), txhash) - s.Require().NoError(err) - return denom, propID -} - -func (s *PermissionlessConsumersSuite) submitConsumerModification(consumer *chainsuite.Chain) string { - modifyProp := &ccvclient.ConsumerModificationProposalJSON{ - Title: "modify consumer", - Summary: "modify consumer", - ChainId: consumer.Config().ChainID, - TopN: 80, - Deposit: fmt.Sprintf("%d%s", chainsuite.GovMinDepositAmount/2, s.Chain.Config().Denom), - } - - propBz, err := json.Marshal(modifyProp) - s.Require().NoError(err) - - propBz, err = sjson.DeleteBytes(propBz, "allow_inactive_vals") - s.Require().NoError(err) - propBz, err = sjson.DeleteBytes(propBz, "min_stake") - s.Require().NoError(err) - - fileName := "proposal_consumer_modification.json" - - s.Require().NoError(s.Chain.GetNode().WriteFile(s.GetContext(), propBz, fileName)) - - filePath := filepath.Join(s.Chain.GetNode().HomeDir(), fileName) - - txhash, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, - "gov", "submit-legacy-proposal", "consumer-modification", filePath, - "--gas", "auto", - ) - s.Require().NoError(err) - - propID, err := s.Chain.GetProposalID(s.GetContext(), txhash) - s.Require().NoError(err) - return propID -} - -func (s *PermissionlessConsumersSuite) submitConsumerRemoval(consumer *chainsuite.Chain, stopTime time.Time) string { - removalProp := &ccvclient.ConsumerRemovalProposalJSON{ - Title: "remove consumer", - Summary: "remove consumer", - ChainId: consumer.Config().ChainID, - StopTime: stopTime, - Deposit: fmt.Sprintf("%d%s", chainsuite.GovMinDepositAmount/2, s.Chain.Config().Denom), - } - - propBz, err := json.Marshal(removalProp) - s.Require().NoError(err) - - fileName := "proposal_consumer_removal.json" - - s.Require().NoError(s.Chain.GetNode().WriteFile(s.GetContext(), propBz, fileName)) - - filePath := filepath.Join(s.Chain.GetNode().HomeDir(), fileName) - - txhash, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Moniker, - "gov", "submit-legacy-proposal", "consumer-removal", filePath, - "--gas", "auto", - ) - s.Require().NoError(err) - - propID, err := s.Chain.GetProposalID(s.GetContext(), txhash) - s.Require().NoError(err) - return propID -} diff --git a/tests/interchain/delegator/lsm_test.go b/tests/interchain/delegator/lsm_test.go index 88071cb3fb..2bb778466c 100644 --- a/tests/interchain/delegator/lsm_test.go +++ b/tests/interchain/delegator/lsm_test.go @@ -462,10 +462,11 @@ func (s *LSMSuite) SetupSuite() { s.T().Skip("Skipping LSM when going from v21 -> v21") } stride, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{ - ChainName: "stride", - Version: chainsuite.StrideVersion, - Denom: chainsuite.StrideDenom, - TopN: 100, + ChainName: "stride", + Version: chainsuite.StrideVersion, + Denom: chainsuite.StrideDenom, + TopN: 100, + ShouldCopyProviderKey: []bool{true}, }) s.Require().NoError(err) s.Stride = stride diff --git a/tests/interchain/consumer_chain/consensus_test.go b/tests/interchain/validator/inactive_validator_test.go similarity index 94% rename from tests/interchain/consumer_chain/consensus_test.go rename to tests/interchain/validator/inactive_validator_test.go index 1f5605cb7a..56faff11d8 100644 --- a/tests/interchain/consumer_chain/consensus_test.go +++ b/tests/interchain/validator/inactive_validator_test.go @@ -1,4 +1,4 @@ -package consumer_chain_test +package validator_test import ( "encoding/json" @@ -20,12 +20,12 @@ const ( maxProviderConsensusValidators = 4 ) -type ConsensusSuite struct { +type InactiveValidatorsSuite struct { *chainsuite.Suite Consumer *chainsuite.Chain } -func (s *ConsensusSuite) SetupSuite() { +func (s *InactiveValidatorsSuite) SetupSuite() { s.Suite.SetupSuite() authority, err := s.Chain.GetGovernanceAddress(s.GetContext()) s.Require().NoError(err) @@ -94,7 +94,7 @@ func (s *ConsensusSuite) SetupSuite() { cfg := chainsuite.ConsumerConfig{ ChainName: "ics-consumer", Version: "v6.2.1", - ShouldCopyProviderKey: allProviderKeysCopied(), + ShouldCopyProviderKey: []bool{true, true, true, true, true, true}, Denom: chainsuite.Ucon, TopN: 100, AllowInactiveVals: true, @@ -119,7 +119,7 @@ func (s *ConsensusSuite) SetupSuite() { } // This is called 0ValidatorSets because it should run first; if the validator sets are wrong, obviously the other tests will fail -func (s *ConsensusSuite) Test0ValidatorSets() { +func (s *InactiveValidatorsSuite) Test0ValidatorSets() { vals, err := s.Chain.QueryJSON(s.GetContext(), "validators", "tendermint-validator-set") s.Require().NoError(err) s.Require().Equal(maxProviderConsensusValidators, len(vals.Array()), vals) @@ -139,7 +139,7 @@ func (s *ConsensusSuite) Test0ValidatorSets() { } } -func (s *ConsensusSuite) TestProviderJailing() { +func (s *InactiveValidatorsSuite) TestProviderJailing() { for i := 1; i < maxProviderConsensusValidators; i++ { jailed, err := s.Chain.IsValidatorJailedForConsumerDowntime(s.GetContext(), s.Relayer, s.Chain, i) s.Require().NoError(err) @@ -152,7 +152,7 @@ func (s *ConsensusSuite) TestProviderJailing() { } } -func (s *ConsensusSuite) TestConsumerJailing() { +func (s *InactiveValidatorsSuite) TestConsumerJailing() { for i := 1; i < maxProviderConsensusValidators; i++ { jailed, err := s.Chain.IsValidatorJailedForConsumerDowntime(s.GetContext(), s.Relayer, s.Consumer, i) s.Require().NoError(err) @@ -168,7 +168,7 @@ func (s *ConsensusSuite) TestConsumerJailing() { } } -func (s *ConsensusSuite) TestOptInInactive() { +func (s *InactiveValidatorsSuite) TestOptInInactive() { consumerID := s.getConsumerID() // Validator 4 will have been opted in automatically when the other ones went down _, err := s.Chain.Validators[maxProviderConsensusValidators].ExecTx(s.GetContext(), s.Chain.ValidatorWallets[maxProviderConsensusValidators].Moniker, "provider", "opt-out", s.getConsumerID()) @@ -210,15 +210,15 @@ func (s *ConsensusSuite) TestOptInInactive() { s.Assert().False(jailed, "validator 5 should not be jailed") } -func (s *ConsensusSuite) getConsumerID() string { +func (s *InactiveValidatorsSuite) getConsumerID() string { consumerIDJSON, err := s.Chain.QueryJSON(s.GetContext(), fmt.Sprintf("chains.#(chain_id=%q).consumer_id", s.Consumer.Config().ChainID), "provider", "list-consumer-chains") s.Require().NoError(err) consumerID := consumerIDJSON.String() return consumerID } -func TestConsensus(t *testing.T) { - s := &ConsensusSuite{ +func TestInactiveValidators(t *testing.T) { + s := &InactiveValidatorsSuite{ Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{ CreateRelayer: true, ChainSpec: &interchaintest.ChainSpec{