diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 088ad19801..17e3525cc8 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "encoding/base64" "encoding/json" "fmt" "log" @@ -16,10 +15,12 @@ import ( "sync" "time" + sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" "github.com/tidwall/gjson" "golang.org/x/mod/semver" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" e2e "github.com/cosmos/interchain-security/v5/tests/e2e/testlib" "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" @@ -42,6 +43,13 @@ type SendTokensAction struct { Amount uint } +type TxResponse struct { + TxHash string `json:"txhash"` + Code int `json:"code"` + RawLog string `json:"raw_log"` + Events []sdk.Event `json:"events"` +} + func (tr Chain) sendTokens( action SendTokensAction, verbose bool, @@ -250,6 +258,124 @@ func (tr Chain) submitTextProposal( tr.waitBlocks(action.Chain, 1, 10*time.Second) } +type UpdateConsumerChainAction struct { + Chain ChainID + From ValidatorID + ConsumerChain ChainID + SpawnTime uint + InitialHeight clienttypes.Height + DistributionChannel string + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string + MinStake uint64 + AllowInactiveVals bool + NewOwner string +} + +func (tr Chain) updateConsumerChain(action UpdateConsumerChainAction, verbose bool) { + + spawn_time := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) + params := ccvtypes.DefaultParams() + initParams := types.ConsumerInitializationParameters{ + InitialHeight: action.InitialHeight, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: spawn_time, + + UnbondingPeriod: params.UnbondingPeriod, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + DistributionTransmissionChannel: action.DistributionChannel, + } + + powerShapingParams := types.PowerShapingParameters{ + Top_N: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + MinStake: action.MinStake, + AllowInactiveVals: action.AllowInactiveVals, + } + + consumerID := tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId + update := types.MsgUpdateConsumer{ + ConsumerId: string(consumerID), + NewOwnerAddress: action.NewOwner, + InitializationParameters: &initParams, + PowerShapingParameters: &powerShapingParams, + } + tr.UpdateConsumer(action.Chain, action.From, update) +} + +type InitializeConsumerChainAction struct { + Chain ChainID + From ValidatorID + ConsumerChain ChainID + SpawnTime uint + InitialHeight clienttypes.Height + DistributionChannel string + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string + MinStake uint64 + AllowInactiveVals bool +} + +// initializeConsumerChain creates and initializes a consumer chain +func (tr Chain) initializeConsumerChain(action InitializeConsumerChainAction, verbose bool) { + + spawn_time := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) + params := ccvtypes.DefaultParams() + initParams := types.ConsumerInitializationParameters{ + InitialHeight: action.InitialHeight, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: spawn_time, + + UnbondingPeriod: params.UnbondingPeriod, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + DistributionTransmissionChannel: action.DistributionChannel, + } + + powerShapingParams := types.PowerShapingParameters{ + Top_N: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + MinStake: action.MinStake, + AllowInactiveVals: action.AllowInactiveVals, + } + + metadata := types.ConsumerMetadata{ + Name: "chain name of " + string(action.Chain), + Description: "no description", + Metadata: "no metadata", + } + + // create consumer + consumerID := tr.CreateConsumer(action.Chain, action.ConsumerChain, action.From, metadata, &initParams, nil) + + update := types.MsgUpdateConsumer{ + ConsumerId: consumerID, + PowerShapingParameters: &powerShapingParams, + } + tr.UpdateConsumer(action.Chain, action.From, update) +} + type SubmitConsumerAdditionProposalAction struct { PreCCV bool Chain ChainID @@ -268,72 +394,230 @@ type SubmitConsumerAdditionProposalAction struct { AllowInactiveVals bool } +func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer) { + + fmt.Println("Update proposal for consumer_id=", update.ConsumerId) + content, err := json.Marshal(update) + if err != nil { + log.Fatalf("failed marshalling MsgUpdate", err.Error()) + } + jsonFile := "/update_consumer.json" + bz, err := tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // Send consumer chain update + cmd := tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "tx", "provider", "update-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), + `--home`, tr.getValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + fmt.Println("Update Consumer", "cmd:", cmd.String(), "content:\n", string(content)) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("update consumer failed ", "error: ", err, "output: ", string(bz)) + } + + fmt.Println("update consumer", "output", string(bz)) + + // Check transaction + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx response on update-consumer: %s, json: %s", err.Error(), string(bz)) + } + + if txResponse.Code != 0 { + log.Fatalf("sending update-consumer transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) + } + tr.waitBlocks(ChainID("provi"), 2, 10*time.Second) +} + +// CreateConsumer creates a consumer chain and returns its consumer-id +func (tr Chain) CreateConsumer(providerChain, consumerChain ChainID, validator ValidatorID, metadata types.ConsumerMetadata, initParams *types.ConsumerInitializationParameters, powershaping *types.PowerShapingParameters) string { + + chainID := string(tr.testConfig.chainConfigs[consumerChain].ChainId) + rec := types.MsgCreateConsumer{ + ChainId: chainID, + Metadata: metadata, + InitializationParameters: initParams, + PowerShapingParameters: powershaping, + } + + content, err := json.Marshal(rec) + if err != nil { + log.Fatalf("failed marshalling ConsumerRegistrationRecord", err.Error()) + } + jsonFile := "/create_consumer.json" + bz, err := tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // Send consumer chain registration + cmd := tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "tx", "provider", "create-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), + `--home`, tr.getValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + fmt.Println("Create consumer", "cmd", cmd.String()) + fmt.Println("Create consumer", "json", string(content)) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("create consumer failed ", "error: ", err, "output: ", string(bz)) + } + + fmt.Println("create consumer output=", string(bz)) + + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx response on consumer-registration: %s, json: %s", err.Error(), string(bz)) + } + + if txResponse.Code != 0 { + log.Fatalf("sending transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) + } + + // TODO: introduce waitForTx + tr.waitBlocks(providerChain, 2, 10*time.Second) + + // Get Consumer ID from tx + cmd = tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "query", "tx", txResponse.TxHash, + `--node`, tr.getValidatorNode(providerChain, validator), + "--output", "json", + ) + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("not able to query tx containing consumer registration: cmd:", cmd, "err:", err.Error(), "out:", string(bz)) + } + + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx containing consumer registration: %s, json: %s", err.Error(), string(bz)) + } + + consumer_id := "" + for _, event := range txResponse.Events { + if event.Type != "consumer_creation" { + continue + } + attr, exists := event.GetAttribute("consumer_id") + if !exists { + log.Fatalf("no event with consumer_id found in tx content of consumer-registration: %v", event) + } + consumer_id = attr.Value + } + if consumer_id == "" { + log.Fatalf("no consumer-id found in consumer creation transaction events for chain '%s'. events: %v", consumerChain, txResponse.Events) + } + + cfg, exists := tr.testConfig.chainConfigs[e2e.ChainID(chainID)] + if !exists { + log.Fatal("no chain config found for consumer chain", chainID) + } + if cfg.ConsumerId != "" && cfg.ConsumerId != e2e.ConsumerID(consumer_id) { + log.Fatal("chain ", chainID, " registered already with a different consumer ID", consumer_id) + } + + // Set the new created consumer-id on the chain's config + cfg.ConsumerId = e2e.ConsumerID(consumer_id) + tr.testConfig.chainConfigs[e2e.ChainID(chainID)] = cfg + + return consumer_id +} + func (tr Chain) submitConsumerAdditionProposal( action SubmitConsumerAdditionProposalAction, verbose bool, ) { - spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) params := ccvtypes.DefaultParams() - template := ` - { - "messages": [ - { - "@type": "/interchain_security.ccv.provider.v1.MsgConsumerAddition", - "chain_id": "%s", - "initial_height": { - "revision_number": "%d", - "revision_height": "%d" - }, - "genesis_hash": "%s", - "binary_hash": "%s", - "spawn_time": "%s", - "unbonding_period": "%s", - "ccv_timeout_period": "%s", - "transfer_timeout_period": "%s", - "consumer_redistribution_fraction": "%s", - "blocks_per_distribution_transmission": "%d", - "historical_entries": "%d", - "distribution_transmission_channel": "%s", - "top_N": %d, - "validators_power_cap": %d, - "validator_set_cap": %d, - "allowlist": %s, - "denylist": %s, - "authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", - "allow_inactive_vals": %t, - "min_stake": "%d" - } - ], -"metadata": "ipfs://CID", -"deposit": "%dstake", -"title": "Propose the addition of a new chain", -"summary": "Gonna be a great chain", -"expedited": false -}` - jsonStr := fmt.Sprintf(template, - string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), - action.InitialHeight.RevisionNumber, - action.InitialHeight.RevisionHeight, - base64.StdEncoding.EncodeToString([]byte("gen_hash")), - base64.StdEncoding.EncodeToString([]byte("bin_hash")), - spawnTime.Local().Format(time.RFC3339Nano), - params.UnbondingPeriod, - params.CcvTimeoutPeriod, - params.TransferTimeoutPeriod, - params.ConsumerRedistributionFraction, - params.BlocksPerDistributionTransmission, - params.HistoricalEntries, - action.DistributionChannel, - action.TopN, - action.ValidatorsPowerCap, - action.ValidatorSetCap, - action.Allowlist, - action.Denylist, - action.AllowInactiveVals, - action.MinStake, - action.Deposit) + spawn_time := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) - //#nosec G204 -- bypass unsafe quoting warning (no production code) + Metadata := types.ConsumerMetadata{ + Name: "chain " + string(action.Chain), + Description: "no description", + Metadata: "no metadata", + } + + InitializationParameters := types.ConsumerInitializationParameters{ + InitialHeight: action.InitialHeight, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: spawn_time, + + UnbondingPeriod: params.UnbondingPeriod, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + DistributionTransmissionChannel: action.DistributionChannel, + } + + consumer_id := tr.CreateConsumer(action.Chain, action.ConsumerChain, action.From, Metadata, nil, nil) + authority := "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" + + // Update consumer to change owner to governance before submitting the proposal + update := &types.MsgUpdateConsumer{ + ConsumerId: consumer_id, + NewOwnerAddress: authority, + } + // For the MsgUpdateConsumer sen in the proposal + PowerShapingParameters := types.PowerShapingParameters{ + Top_N: 0, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + MinStake: action.MinStake, + AllowInactiveVals: action.AllowInactiveVals, + } + update.PowerShapingParameters = &PowerShapingParameters + tr.UpdateConsumer(action.Chain, action.From, *update) + + // - set PowerShaping params TopN > 0 for consumer chain + update.PowerShapingParameters.Top_N = action.TopN + update.Signer = authority + update.InitializationParameters = &InitializationParameters + update.InitializationParameters.SpawnTime = spawn_time + update.Metadata = &Metadata + + // Generate proposal content + title := "Propose the addition of a new chain" + description := "description of the consumer modification proposal" + summary := "Gonna be a great chain" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, update) + + // #nosec G204 -- bypass unsafe quoting warning (no production code) proposalFile := "/consumer-addition.proposal" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), @@ -352,6 +636,7 @@ func (tr Chain) submitConsumerAdditionProposal( `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, + `-o json`, `-y`, ) @@ -361,7 +646,17 @@ func (tr Chain) submitConsumerAdditionProposal( } bz, err = cmd.CombinedOutput() if err != nil { - log.Fatal("submit-proposal failed:", err, "\n", string(bz)) + log.Fatal("executing submit-proposal failed:", err, "\n", string(bz)) + } + + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("failed unmarshalling tx response on submit consumer update: %s, json: %s", err.Error(), string(bz)) + } + + if txResponse.Code != 0 { + log.Fatalf("gov submit consumer update transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) } if verbose { @@ -467,31 +762,22 @@ func (tr Chain) submitConsumerRemovalProposal( action SubmitConsumerRemovalProposalAction, verbose bool, ) { - template := ` - { - "messages": [ - { - "@type": "/interchain_security.ccv.provider.v1.MsgConsumerRemoval", - "chain_id": "%s", - "stop_time": "%s", - "authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" - } - ], - "metadata": "ipfs://CID", - "deposit": "%dstake", - "title": "%s", - "summary": "It was a great chain", - "expedited": false - } -` + consumerID := string(tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId) title := fmt.Sprintf("Stop the %v chain", action.ConsumerChain) - stopTime := tr.testConfig.containerConfig.Now.Add(action.StopTimeOffset).Format(time.RFC3339Nano) + description := "stop consumer chain" + summary := "It was a great chain" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + authority := "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" - jsonStr := fmt.Sprintf(template, - string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), - stopTime, - action.Deposit, - title) + msgRemoveConsumer := types.MsgRemoveConsumer{ + ConsumerId: consumerID, + StopTime: tr.testConfig.containerConfig.Now.Add(action.StopTimeOffset), + Signer: authority, + } + + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &msgRemoveConsumer) // #nosec G204 -- bypass unsafe quoting warning (no production code) proposalFile := "/consumer-removal.proposal" @@ -599,45 +885,29 @@ func (tr Chain) submitConsumerModificationProposal( action SubmitConsumerModificationProposalAction, verbose bool, ) { - template := ` -{ -"messages": [ - { - "@type": "/interchain_security.ccv.provider.v1.MsgConsumerModification", - "title": "Propose the modification of the PSS parameters of a chain", - "description": "description of the consumer modification proposal", - "chain_id": "%s", - "top_N": %d, - "validators_power_cap": %d, - "validator_set_cap": %d, - "allowlist": %s, - "denylist": %s, - "authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", - "min_stake": %d, - "allow_inactive_vals": %t - } - ], -"metadata": "ipfs://CID", -"deposit": "%sstake", -"title": "Propose the modification of the PSS parameters of a chain", -"summary": "summary of a modification proposal", -"expedited": false - } -` - - jsonStr := fmt.Sprintf(template, - string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), - action.TopN, - action.ValidatorsPowerCap, - action.ValidatorSetCap, - action.Allowlist, - action.Denylist, - action.Deposit, - action.MinStake, - action.AllowInactiveVals, - ) + consumerID := string(tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId) + title := "Propose the modification of the PSS parameters of a chain" + description := "description of the consumer modification proposal" + summary := "summary of a modification proposal" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + authority := "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" + + msgConsMod := types.MsgUpdateConsumer{ + Signer: authority, + ConsumerId: consumerID, + PowerShapingParameters: &types.PowerShapingParameters{ + Top_N: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + }, + } + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &msgConsMod) // #nosec G204 -- bypass unsafe quoting warning (no production code) proposalFile := "/consumer-mod.proposal" bz, err := tr.target.ExecCommand( @@ -754,26 +1024,21 @@ func (tr Chain) submitEnableTransfersProposalAction( ) { // gov signed address got by checking the gov module acc address in the test container // interchain-security-cdd q auth module-account gov --node tcp://7.7.9.253:26658 - template := ` - { - "messages": [ - { - "@type": "/ibc.applications.transfer.v1.MsgUpdateParams", - "signer": "consumer10d07y265gmmuvt4z0w9aw880jnsr700jlh7295", - "params": { - "send_enabled": true, - "receive_enabled": true - } - } - ], - "metadata": "ipfs://CID", - "deposit": "%dstake", - "title": "%s", - "summary": "Enable transfer send", - "expedited": false - } - ` - jsonStr := fmt.Sprintf(template, action.Deposit, action.Title) + + msgUpdateParams := ibctransfertypes.MsgUpdateParams{ + Signer: "consumer10d07y265gmmuvt4z0w9aw880jnsr700jlh7295", + Params: ibctransfertypes.Params{ + SendEnabled: true, + ReceiveEnabled: true, + }, + } + // Generate proposal content + description := "update IBC params" + summary := "Enable transfer send/receive" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + jsonStr := e2e.GenerateGovProposalContent(action.Title, summary, metadata, deposit, description, expedited, &msgUpdateParams) //#nosec G204 -- bypass unsafe quoting warning (no production code) bz, err := tr.target.ExecCommand( @@ -880,23 +1145,34 @@ func (tr *Chain) startConsumerChain( func (tr *Chain) getConsumerGenesis(providerChain, consumerChain ChainID) string { fmt.Println("Exporting consumer genesis from provider") providerBinaryName := tr.testConfig.chainConfigs[providerChain].BinaryName + consumerID := string(tr.testConfig.chainConfigs[consumerChain].ConsumerId) - cmd := tr.target.ExecCommand( - providerBinaryName, + now := time.Now() + timeout := now.Add(30 * time.Second) + var bz []byte + var err error + for { + cmd := tr.target.ExecCommand( + providerBinaryName, - "query", "provider", "consumer-genesis", - string(tr.testConfig.chainConfigs[consumerChain].ChainId), + "query", "provider", "consumer-genesis", consumerID, - `--node`, tr.target.GetQueryNode(providerChain), - `-o`, `json`, - ) + `--node`, tr.target.GetQueryNode(providerChain), + `-o`, `json`, + ) + bz, err = cmd.CombinedOutput() + if err == nil { + break + } - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) + if time.Now().After(timeout) { + log.Print("Failed running command: ", cmd) + log.Fatal(err, "\n", string(bz)) + } + time.Sleep(2 * time.Second) } - if tr.testConfig.transformGenesis || needsGenesisTransform(tr.testConfig) { + if tr.testConfig.transformGenesis || needsGenesisTransform(*tr.testConfig) { return string(tr.transformConsumerGenesis(consumerChain, bz)) } else { fmt.Println("No genesis transformation performed") @@ -1071,28 +1347,9 @@ func (tr Chain) changeoverChain( action ChangeoverChainAction, verbose bool, ) { - // sleep until the consumer chain genesis is ready on consumer - time.Sleep(5 * time.Second) - cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.ProviderChain].BinaryName, - - "query", "provider", "consumer-genesis", - string(tr.testConfig.chainConfigs[action.SovereignChain].ChainId), - - `--node`, tr.target.GetQueryNode(action.ProviderChain), - `-o`, `json`, - ) - - if verbose { - log.Println("changeoverChain cmd: ", cmd.String()) - } - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } + consumerGenesis := ".app_state.ccvconsumer = " + tr.getConsumerGenesis(action.ProviderChain, action.SovereignChain) - consumerGenesis := ".app_state.ccvconsumer = " + string(bz) consumerGenesisChanges := tr.testConfig.chainConfigs[action.SovereignChain].GenesisChanges if consumerGenesisChanges != "" { consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges @@ -2229,32 +2486,24 @@ type SubmitChangeRewardDenomsProposalAction struct { } func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { - template := ` -{ - "messages": [ - { - "@type": "/interchain_security.ccv.provider.v1.MsgChangeRewardDenoms", - "denoms_to_add": ["%s"], - "denoms_to_remove": ["%s"], - "authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" - } - ], - "metadata": "ipfs://CID", - "deposit": "%dstake", - "title": "change reward denoms", - "summary": "Proposal to change reward denoms", - "expedited": false -}` - denomsToAdd := action.Denom - denomsToRemove := "stake" - jsonStr := fmt.Sprintf(template, - denomsToAdd, - denomsToRemove, - action.Deposit) + changeRewMsg := types.MsgChangeRewardDenoms{ + DenomsToAdd: []string{action.Denom}, + DenomsToRemove: []string{"stake"}, + Authority: "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", + } + + // Generate proposal content + title := "change reward denoms" + description := "change reward denoms" + summary := "Proposal to change reward denoms" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &changeRewMsg) //#nosec G204 -- bypass unsafe quoting warning (no production code) - proposalFile := "/change-reward.proposal" + proposalFile := "/change-rewards.proposal" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), ).CombinedOutput() @@ -2480,10 +2729,11 @@ func (tr Chain) assignConsumerPubKey(action AssignConsumerPubKeyAction, verbose if tr.testConfig.useCometmock { gas = "9000000" } + assignKey := fmt.Sprintf( `%s tx provider assign-consensus-key %s '%s' --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, tr.testConfig.chainConfigs[ChainID("provi")].BinaryName, - string(tr.testConfig.chainConfigs[action.Chain].ChainId), + string(tr.testConfig.chainConfigs[action.Chain].ConsumerId), action.ConsumerPubkey, action.Validator, tr.testConfig.chainConfigs[ChainID("provi")].ChainId, @@ -2659,7 +2909,7 @@ type OptInAction struct { Validator ValidatorID } -func (tr Chain) optIn(action OptInAction, target ExecutionTarget, verbose bool) { +func (tr Chain) optIn(action OptInAction, verbose bool) { // Note: to get error response reported back from this command '--gas auto' needs to be set. gas := "auto" // Unfortunately, --gas auto does not work with CometMock. so when using CometMock, just use --gas 9000000 then @@ -2671,7 +2921,7 @@ func (tr Chain) optIn(action OptInAction, target ExecutionTarget, verbose bool) optIn := fmt.Sprintf( `%s tx provider opt-in %s --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, tr.testConfig.chainConfigs[ChainID("provi")].BinaryName, - string(tr.testConfig.chainConfigs[action.Chain].ChainId), + string(tr.testConfig.chainConfigs[action.Chain].ConsumerId), action.Validator, tr.testConfig.chainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Validator), @@ -2679,7 +2929,7 @@ func (tr Chain) optIn(action OptInAction, target ExecutionTarget, verbose bool) gas, ) - cmd := target.ExecCommand( + cmd := tr.target.ExecCommand( "/bin/bash", "-c", optIn, ) @@ -2709,7 +2959,7 @@ type OptOutAction struct { ExpectError bool } -func (tr Chain) optOut(action OptOutAction, target ExecutionTarget, verbose bool) { +func (tr Chain) optOut(action OptOutAction, verbose bool) { // Note: to get error response reported back from this command '--gas auto' needs to be set. gas := "auto" // Unfortunately, --gas auto does not work with CometMock. so when using CometMock, just use --gas 9000000 then @@ -2721,7 +2971,7 @@ func (tr Chain) optOut(action OptOutAction, target ExecutionTarget, verbose bool optOut := fmt.Sprintf( `%s tx provider opt-out %s --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, tr.testConfig.chainConfigs[ChainID("provi")].BinaryName, - string(tr.testConfig.chainConfigs[action.Chain].ChainId), + string(tr.testConfig.chainConfigs[action.Chain].ConsumerId), action.Validator, tr.testConfig.chainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Validator), @@ -2729,7 +2979,7 @@ func (tr Chain) optOut(action OptOutAction, target ExecutionTarget, verbose bool gas, ) - cmd := target.ExecCommand( + cmd := tr.target.ExecCommand( "/bin/bash", "-c", optOut, ) @@ -2759,6 +3009,7 @@ func (tr Chain) optOut(action OptOutAction, target ExecutionTarget, verbose bool type SetConsumerCommissionRateAction struct { Chain ChainID + ConsumerID ConsumerID Validator ValidatorID CommissionRate float64 @@ -2767,7 +3018,7 @@ type SetConsumerCommissionRateAction struct { ExpectedError string } -func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction, target ExecutionTarget, verbose bool) { +func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction, verbose bool) { // Note: to get error response reported back from this command '--gas auto' needs to be set. gas := "auto" // Unfortunately, --gas auto does not work with CometMock. so when using CometMock, just use --gas 9000000 then @@ -2775,11 +3026,16 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction gas = "9000000" } + consumerID := string(tr.testConfig.chainConfigs[action.Chain].ConsumerId) + if action.ConsumerID != "" { + consumerID = string(action.ConsumerID) + } + // Use: "set-consumer-commission-rate [consumer-chain-id] [commission-rate]" setCommissionRate := fmt.Sprintf( `%s tx provider set-consumer-commission-rate %s %f --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, tr.testConfig.chainConfigs[ChainID("provi")].BinaryName, - string(tr.testConfig.chainConfigs[action.Chain].ChainId), + consumerID, action.CommissionRate, action.Validator, tr.testConfig.chainConfigs[ChainID("provi")].ChainId, @@ -2788,7 +3044,7 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction gas, ) - cmd := target.ExecCommand( + cmd := tr.target.ExecCommand( "/bin/bash", "-c", setCommissionRate, ) diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 4fa762b5f7..ef5ed75d42 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -74,6 +74,7 @@ var hermesTemplates = map[string]string{ // type aliases for shared types from e2e package type ( ChainID = e2e.ChainID + ConsumerID = e2e.ConsumerID ValidatorID = e2e.ValidatorID ValidatorConfig = e2e.ValidatorConfig ChainConfig = e2e.ChainConfig @@ -122,6 +123,7 @@ type TestConfig struct { timeOffset time.Duration transformGenesis bool name string + Consumer2ChainID map[ConsumerID]ChainID // dynamic mapping of } // Initialize initializes the TestConfig instance by setting the runningChains field to an empty map. diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 2864c9b413..bb8480a049 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -4,6 +4,8 @@ import ( "flag" "fmt" "log" + "log/slog" + "os" "strings" "sync" "time" @@ -246,6 +248,12 @@ var stepChoices = map[string]StepChoice{ description: "test minting without inactive validators as a sanity check", testConfig: MintTestCfg, }, + "permissionless-ics": { + name: "permissionless-ics", + steps: stepsPermissionlessICS(), + description: "test permissionless ics", + testConfig: DefaultTestCfg, + }, "inactive-vals-outside-max-validators": { name: "inactive-vals-outside-max-validators", steps: stepsInactiveValsTopNReproduce(), @@ -334,27 +342,27 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet, providerVe // Run default tests if no test cases were selected if len(selectedPredefinedTests) == 0 && len(selectedTestFiles) == 0 { selectedPredefinedTests = TestSet{ - "changeover", - "happy-path", - "democracy-reward", - "democracy", - "slash-throttle", - "consumer-double-sign", - "consumer-misbehaviour", - "consumer-double-downtime", - "partial-set-security-opt-in", - "partial-set-security-top-n", - "partial-set-security-validator-set-cap", - "partial-set-security-validators-power-cap", - "partial-set-security-validators-allowlisted", - "partial-set-security-validators-denylisted", - "partial-set-security-modification-proposal", - "active-set-changes", - "inactive-provider-validators-on-consumer", - "inactive-vals-topN", - "inactive-provider-validators-governance", - "min-stake", - "inactive-vals-mint", + "changeover", // PASSED + "happy-path", // PASSED + "democracy-reward", // PASSED + "democracy", // PASSED + "slash-throttle", // PASSED + "consumer-double-sign", // TODO PERMISSIONLESS: failing + "consumer-misbehaviour", // TODO PERMISSIONLESS: failing + "consumer-double-downtime", // PASSED + "partial-set-security-opt-in", // PASSED + "partial-set-security-top-n", // PASSED + "partial-set-security-validator-set-cap", // PASSED + "partial-set-security-validators-power-cap", // PASSED + "partial-set-security-validators-allowlisted", // PASSED + "partial-set-security-validators-denylisted", // PASSED + "partial-set-security-modification-proposal", // TODO PERMISSIONLESS: failing + "active-set-changes", // PASSED + "inactive-provider-validators-on-consumer", // PASSED + "inactive-vals-topN", // TODO PERMISSIONLESS: failing + "inactive-provider-validators-governance", // PASSED + "min-stake", // PASSED + "inactive-vals-mint", // PASSED } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") @@ -483,6 +491,18 @@ func createTestRunners(testCases []testStepsWithConfig) []TestRunner { return runners } +func SetupLogger() { + opts := &slog.HandlerOptions{ + AddSource: false, + Level: slog.LevelInfo, + } + if *verbose { + opts.Level = slog.LevelDebug + } + logger := slog.New(slog.NewTextHandler(os.Stdout, opts)) + slog.SetDefault(logger) +} + func executeTests(runners []TestRunner) error { if parallel != nil && *parallel { fmt.Println("=============== running all tests in parallel ===============") @@ -596,6 +616,7 @@ func main() { log.Fatalf("Error parsing command arguments %s\n", err) } + //SetupLogger() testCases := getTestCases(selectedTests, selectedTestfiles, providerVersions, consumerVersions) testRunners := createTestRunners(testCases) defer deleteTargets(testRunners) diff --git a/tests/e2e/state.go b/tests/e2e/state.go index f29c8f59f0..5d06c5b403 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -12,6 +12,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" e2e "github.com/cosmos/interchain-security/v5/tests/e2e/testlib" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" "github.com/kylelemons/godebug/pretty" "github.com/tidwall/gjson" "gopkg.in/yaml.v2" @@ -38,7 +39,7 @@ type State map[ChainID]ChainState type Chain struct { target e2e.TargetDriver - testConfig TestConfig + testConfig *TestConfig } func (tr Chain) GetChainState(chain ChainID, modelState ChainState) ChainState { @@ -335,7 +336,7 @@ func intPtr(i int) *int { } type Commands struct { - containerConfig ContainerConfig // FIXME only needed for 'Now' time tracking + containerConfig *ContainerConfig validatorConfigs map[ValidatorID]ValidatorConfig chainConfigs map[ChainID]ChainConfig target e2e.PlatformDriver @@ -415,6 +416,10 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u return gjson.Get(string(bz), denomCondition).Float() } +/* func parseProposalMsg(rawMsg gjson.Result) Proposal { + +} +*/ // interchain-securityd query gov proposals func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) @@ -440,6 +445,10 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { log.Fatal(err, "\n", propRaw) } + messages := gjson.Get(propRaw, `proposal.messages`) + for _, msg := range messages.Array() { + fmt.Println("msg val", msg) + } // for legacy proposal types submitted using "tx submit-legacyproposal" (cosmos-sdk/v1/MsgExecLegacyContent) propType := gjson.Get(propRaw, `proposal.messages.0.value.content.type`).String() rawContent := gjson.Get(propRaw, `proposal.messages.0.value.content.value`) @@ -465,6 +474,28 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Title: title, Description: description, } + case "/interchain_security.ccv.provider.v1.MsgUpdateConsumer": + fmt.Printf("@@@@ container.Now in state %s\n", tr.containerConfig.Now.String()) + + spawnTime := rawContent.Get("initialization_parameters.spawn_time").Time().Sub(tr.containerConfig.Now) + consumerId := rawContent.Get("consumer_id").String() + consumer_chain_id := ChainID("") + for _, chainCfg := range tr.chainConfigs { + if chainCfg.ConsumerId == e2e.ConsumerID(consumerId) { + consumer_chain_id = chainCfg.ChainId + } + } + return e2e.ConsumerAdditionProposal{ + Deposit: uint(deposit), + Chain: consumer_chain_id, + Status: status, + SpawnTime: int(spawnTime.Milliseconds()), + InitialHeight: clienttypes.Height{ + RevisionNumber: rawContent.Get("initialization_parameters.initial_height.revision_number").Uint(), + RevisionHeight: rawContent.Get("initialization_parameters.initial_height.revision_height").Uint(), + }, + } + case "/interchain_security.ccv.provider.v1.MsgConsumerAddition": chainId := rawContent.Get("chain_id").String() spawnTime := rawContent.Get("spawn_time").Time().Sub(tr.containerConfig.Now) @@ -498,13 +529,13 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Title: title, Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", } - case "/interchain_security.ccv.provider.v1.MsgConsumerRemoval": - chainId := rawContent.Get("chain_id").String() + case "/interchain_security.ccv.provider.v1.MsgRemoveConsumer": + consumerID := rawContent.Get("consumer_id").String() stopTime := rawContent.Get("stop_time").Time().Sub(tr.containerConfig.Now) var chain ChainID for i, conf := range tr.chainConfigs { - if string(conf.ChainId) == chainId { + if string(conf.ConsumerId) == consumerID { chain = i break } @@ -738,16 +769,23 @@ func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { `-o`, `json`, ) + // TODO PERMISSIONLESS; check if this should only list launched consumer chains bz, err := cmd.CombinedOutput() if err != nil { + fmt.Println("@@@ failed getting consumer chains:", cmd) log.Fatal(err, "\n", string(bz)) } arr := gjson.Get(string(bz), "chains").Array() chains := make(map[ChainID]bool) for _, c := range arr { - id := c.Get("chain_id").String() - chains[ChainID(id)] = true + phase := c.Get("phase").String() + if phase == types.ConsumerPhase_name[int32(types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED)] || + phase == types.ConsumerPhase_name[int32(types.ConsumerPhase_CONSUMER_PHASE_REGISTERED)] || + phase == types.ConsumerPhase_name[int32(types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED)] { + id := c.Get("chain_id").String() + chains[ChainID(id)] = true + } } return chains @@ -755,10 +793,11 @@ func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { binaryName := tr.chainConfigs[ChainID("provi")].BinaryName + consumer_id := tr.chainConfigs[ChainID(consumerChain)].ConsumerId cmd := tr.target.ExecCommand(binaryName, "query", "provider", "validator-consumer-key", - string(consumerChain), tr.validatorConfigs[validator].ValconsAddress, + string(consumer_id), tr.validatorConfigs[validator].ValconsAddress, `--node`, tr.GetQueryNode(ChainID("provi")), `-o`, `json`, ) @@ -773,10 +812,12 @@ func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator Validator func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { binaryName := tr.chainConfigs[ChainID("provi")].BinaryName + consumer_id := tr.chainConfigs[ChainID(consumerChain)].ConsumerId + cmd := tr.target.ExecCommand(binaryName, "query", "provider", "validator-provider-key", - string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, + string(consumer_id), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, `--node`, tr.GetQueryNode(ChainID("provi")), `-o`, `json`, ) @@ -898,7 +939,11 @@ func (tr Commands) GetHasToValidate( arr := gjson.Get(string(bz), "consumer_chain_ids").Array() chains := []ChainID{} for _, c := range arr { - chains = append(chains, ChainID(c.String())) + for _, chain := range tr.chainConfigs { + if chain.ConsumerId == ConsumerID(c.String()) { + chains = append(chains, chain.ChainId) + } + } } return chains @@ -969,20 +1014,26 @@ func (tr Commands) GetTrustedHeight( func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - "query", "provider", "list-proposed-consumer-chains", + cmd := tr.target.ExecCommand(binaryName, + "query", "provider", "list-consumer-chains", `--node`, tr.GetQueryNode(chain), `-o`, `json`, - ).CombinedOutput() + ) + bz, err := cmd.CombinedOutput() if err != nil { + fmt.Println("@@@ failed getting proposed chains:", cmd) log.Fatal(err, "\n", string(bz)) } - arr := gjson.Get(string(bz), "proposedChains").Array() + arr := gjson.Get(string(bz), "chains").Array() chains := []string{} for _, c := range arr { - cid := c.Get("chainID").String() - chains = append(chains, cid) + cid := c.Get("chain_id").String() + phase := c.Get("phase").String() + if phase == types.ConsumerPhase_name[int32(types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED)] || + phase == types.ConsumerPhase_name[int32(types.ConsumerPhase_CONSUMER_PHASE_REGISTERED)] { + chains = append(chains, cid) + } } return chains @@ -1013,9 +1064,11 @@ func (tr Commands) GetQueryNodeIP(chain ChainID) string { // GetConsumerCommissionRate returns the commission rate of the given validator on the given consumerChain func (tr Commands) GetConsumerCommissionRate(consumerChain ChainID, validator ValidatorID) float64 { binaryName := tr.chainConfigs[ChainID("provi")].BinaryName + consumerID := tr.chainConfigs[consumerChain].ConsumerId + cmd := tr.target.ExecCommand(binaryName, "query", "provider", "validator-consumer-commission-rate", - string(consumerChain), tr.validatorConfigs[validator].ValconsAddress, + string(consumerID), tr.validatorConfigs[validator].ValconsAddress, `--node`, tr.GetQueryNode(ChainID("provi")), `-o`, `json`, ) diff --git a/tests/e2e/steps_partial_set_security.go b/tests/e2e/steps_partial_set_security.go index 80eff1aedd..d9c385eaf0 100644 --- a/tests/e2e/steps_partial_set_security.go +++ b/tests/e2e/steps_partial_set_security.go @@ -2,6 +2,7 @@ package main import ( "strconv" + "time" gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -32,6 +33,7 @@ func stepsOptInChain() []Step { { Action: SetConsumerCommissionRateAction{ Chain: ChainID("consu"), + ConsumerID: "99999", Validator: ValidatorID("bob"), CommissionRate: 0.123, ExpectError: true, @@ -40,33 +42,15 @@ func stepsOptInChain() []Step { State: State{}, }, { - Action: SubmitConsumerAdditionProposalAction{ + Action: InitializeConsumerChainAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), - Deposit: 10000001, ConsumerChain: ChainID("consu"), - SpawnTime: 0, + SpawnTime: uint(time.Minute * 10), // set spawn-time far in the future InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, TopN: 0, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, - ValidatorID("carol"): {}, - }, - }, - }, + State: State{}, }, // Οpt in "alice" and "bob" so the chain is not empty when it is about to start. Note, that "alice" and "bob" use // the provider's public key (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not @@ -127,25 +111,15 @@ func stepsOptInChain() []Step { }, }, { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, // launch now + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, }, + State: State{}, }, { // we start all the validators but only "alice" and "bob" have opted in and hence @@ -1052,35 +1026,16 @@ func stepsValidatorSetCappedChain() []Step { }, }, { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 0, - // we can have at most 2 validators validating the consumer chain + Action: InitializeConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: uint(time.Minute * 10), // set spawn-time far in the future + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, ValidatorSetCap: 2, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, - ValidatorID("carol"): {}, - }, - }, - }, + State: State{}, }, // Οpt in "alice", "bob", and "carol." Note, that "alice" and "bob" use the provider's public key // (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not need a consumer-key assignment. @@ -1143,25 +1098,17 @@ func stepsValidatorSetCappedChain() []Step { State: State{}, }, { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, + // Update with SpawnTime 0 will trigger launch of consumer chain + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, // launch now + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + ValidatorSetCap: 2, }, + State: State{}, }, { Action: StartConsumerChainAction{ @@ -1513,38 +1460,15 @@ func stepsValidatorsAllowlistedChain() []Step { }, }, { - Action: SubmitConsumerAdditionProposalAction{ + Action: InitializeConsumerChainAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), - Deposit: 10000001, ConsumerChain: ChainID("consu"), - SpawnTime: 0, + SpawnTime: uint(time.Minute * 10), // set spawn-time far in the future InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, TopN: 0, - // only "alice" and "bob" are allowlisted (see `getDefaultValidators` in `tests/e2e/config.go`) - Allowlist: []string{ - "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", - "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - }, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, - ValidatorID("carol"): {}, - }, - }, }, + State: State{}, }, // Οpt in "alice", "bob", and "carol." Note, that "alice" and "bob" use the provider's public key // (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not need a consumer-key assignment. @@ -1606,25 +1530,21 @@ func stepsValidatorsAllowlistedChain() []Step { State: State{}, }, { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, + // Update with SpawnTime 0 will trigger launch of consumer chain + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, // launch now + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + // only "alice" and "bob" are allowlisted (see `getDefaultValidators` in `tests/e2e/config.go`) + Allowlist: []string{ + "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", }, }, + State: State{}, }, { Action: StartConsumerChainAction{ @@ -1719,35 +1639,16 @@ func stepsValidatorsDenylistedChain() []Step { }, }, { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 0, - // only "bob" is denylisted (see `getDefaultValidators` in `tests/e2e/config.go`) - Denylist: []string{"cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39"}, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, - ValidatorID("carol"): {}, - }, - }, + Action: InitializeConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: uint(time.Minute * 10), // set spawn-time far in the future + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + ValidatorSetCap: 2, }, + State: State{}, }, // Οpt in "alice", "bob", and "carol." Note, that "alice" and "bob" use the provider's public key // (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not need a consumer-key assignment. @@ -1809,25 +1710,17 @@ func stepsValidatorsDenylistedChain() []Step { State: State{}, }, { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, + // Update with SpawnTime 0 will trigger launch of consumer chain + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, // launch now + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + Denylist: []string{"cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39"}, }, + State: State{}, }, { Action: StartConsumerChainAction{ @@ -1923,6 +1816,17 @@ func stepsModifyChain() []Step { }, }, { + Action: InitializeConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: uint(time.Minute * 10), // set spawn-time far in the future + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + }, + State: State{}, + }, + /* { Action: SubmitConsumerAdditionProposalAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), @@ -1950,7 +1854,7 @@ func stepsModifyChain() []Step { }, }, }, - }, + }, */ // Οpt in "alice", "bob", and "carol." Note, that "alice" and "bob" use the provider's public key // (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not need a consumer-key assignment. { @@ -2011,26 +1915,39 @@ func stepsModifyChain() []Step { State: State{}, }, { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, + // Update with SpawnTime 0 will trigger launch of consumer chain + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, // launch now + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, }, + State: State{}, }, + /* { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + */ { Action: StartConsumerChainAction{ ConsumerChain: ChainID("consu"), @@ -2105,44 +2022,56 @@ func stepsModifyChain() []Step { // 1. set `ValidatorsPowerCap` to 40% { - Action: SubmitConsumerModificationProposalAction{ + Action: UpdateConsumerChainAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), - Deposit: 10000001, ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, ValidatorsPowerCap: 40, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 2: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - }, - }, - }, - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: 2, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 2: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, - }, + State: State{}, }, + /* { + Action: SubmitConsumerModificationProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + ValidatorsPowerCap: 40, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 2: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 2, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 2: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, */ { Action: RelayPacketsAction{ ChainA: ChainID("provi"), @@ -2171,45 +2100,59 @@ func stepsModifyChain() []Step { // 2. set the `ValidatorSetCap` to a maximum of 2 validators { - Action: SubmitConsumerModificationProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - ValidatorSetCap: 2, + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + ValidatorsPowerCap: 40, + ValidatorSetCap: 2, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 3: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + State: State{}, + }, + /* + { + Action: SubmitConsumerModificationProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + ValidatorSetCap: 2, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 3: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, }, }, }, }, - }, - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: 3, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 3: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 3, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 3: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, }, }, }, }, - }, - { + */{ Action: RelayPacketsAction{ ChainA: ChainID("provi"), ChainB: ChainID("consu"), @@ -2238,6 +2181,23 @@ func stepsModifyChain() []Step { // 3. set an allowlist with 2 validators { + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + ValidatorsPowerCap: 40, + ValidatorSetCap: 2, + Allowlist: []string{ + "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + }, + }, + State: State{}, + }, + /* { Action: SubmitConsumerModificationProposalAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), @@ -2279,7 +2239,7 @@ func stepsModifyChain() []Step { }, }, }, - }, + }, */ { Action: RelayPacketsAction{ ChainA: ChainID("provi"), @@ -2307,45 +2267,64 @@ func stepsModifyChain() []Step { // 4. set a denylist with 1 validator { - Action: SubmitConsumerModificationProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + ValidatorsPowerCap: 40, + ValidatorSetCap: 2, + Allowlist: []string{ + "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + }, // only "alice" is denylisted (see `getDefaultValidators` in `tests/e2e/config.go`) Denylist: []string{"cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq"}, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 5: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - }, - }, - }, - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: 5, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 5: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, - }, + State: State{}, }, + /* { + Action: SubmitConsumerModificationProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + // only "alice" is denylisted (see `getDefaultValidators` in `tests/e2e/config.go`) + Denylist: []string{"cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq"}, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 5: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 5, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 5: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, */ { Action: RelayPacketsAction{ ChainA: ChainID("provi"), @@ -2372,45 +2351,47 @@ func stepsModifyChain() []Step { }, // 5. modify the chain from Opt In to Top 100% - { - Action: SubmitConsumerModificationProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - TopN: 100, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 6: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), - }, - }, - }, - }, - }, - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: 6, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 6: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, - }, - }, + // PERMISSIONLESS: NOT SUPPORTED AT THE CURRENT STATE OF WORK + /* { + Action: SubmitConsumerModificationProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + TopN: 100, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 6: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 6, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 6: ConsumerModificationProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + */ { Action: OptOutAction{ Chain: ChainID("consu"), diff --git a/tests/e2e/steps_permissionless_ics.go b/tests/e2e/steps_permissionless_ics.go new file mode 100644 index 0000000000..0107693c60 --- /dev/null +++ b/tests/e2e/steps_permissionless_ics.go @@ -0,0 +1,1009 @@ +package main + +import ( + "strconv" + + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +// stepsPermissionlessICS tests adding consumer chains using Permissionless ICS feature +// - start the provider chain +// - start a consumer chain +// - check that permissionless chains with one or more validators opt-ed in can be launched +// - check that a permissionless chain without a validator does not not launch +func stepsPermissionlessICS() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards + // if we would look at total rewards, alice would trivially also get rewards, + // because she gets rewards in the first block due to being in the genesis + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + }, + // Launch Permissionless Consumer Chain - with multiple validators opting in + []Step{ + { + Action: InitializeConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + AllowInactiveVals: true, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + }, + // Opt in validators on chains before SpawnTime has passed + // -- one chain with multiple validators + // -- one chain with only one validator + // -- one chain without a validator opting in + // + // Opt in validator on the chain without any validator (AFTER spawn time has passed) + // Stop chains + []Step{ + // check that active-but-not-consensus validators do not get slashed for downtime + { + // alices provider node goes offline + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // still 0 consensus power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, // but alice does not get jailed or slashed + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // give carol more power so that she has enough power to validate if bob goes down + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 700000000, // carol needs to have more than 2/3rds of power(alice) + power(carol) + power(bob) to run both chains alone, so we stake some more to her + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 200, + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 1000000000, + }, + // check that bob and carol get rewards, but alice does not + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards since block 1 + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + // bob goes offline + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice gets into the active set + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 198000000, // 1% slash + ValidatorID("carol"): 1000000000, + }, + }, + }, + }, + { + // relay packets so that the consumer gets up to date with the provider + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): true, // alice is participating right now, so gets rewards + ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus + ValidatorID("carol"): true, + }, + }, + }, + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 198, // bob was slashed 1% + ValidatorID("carol"): 1000, + }, + // check that between two blocks now, alice does not get rewarded with the native denom + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + // bob is still at 0 power on the consumer chain + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay packets so that the consumer gets up to date with the provider + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // alice goes offline on the consumer chain + { + Action: DowntimeSlashAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // power not affected yet + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the provider chain knows about alice's downtime + { + Action: RelayPacketsAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + Port: "consumer", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too. + // we cannot test directly whether alice is jailed, but we will test this below + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // we need to double-check that alice is actually jailed, so we get bob jailed, too, which usually would mean alice gets into power + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail alice + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // alice was not slashed because consumer downtime just jails without slashing tokens + ValidatorID("alice"): 100, // alice is back as an active consensus validator. + ValidatorID("bob"): 0, // bob is still jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // both alice and bob are validating the consumer + ValidatorID("bob"): 196, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + }, + ) + + return s +} + +/* // Precondition: The provider chain is running. +// Postcondition: A consumer chain with Top N = 0 is running, including an up-and-running IBC connection to the provider. +// "alice", "bob", "carol" have opted in and are validating. +func setupOptInChain() []Step { + return concatSteps([]Step{ + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + AllowInactiveVals: true, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + { + // we start all the validators but only "alice" and "bob" have opted in and hence + // only "alice" and "bob" are validating blocks + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ClientA: 0, + ClientB: 0, + }, + State: State{}, + }, + { + Action: AddIbcChannelAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ConnectionA: 0, + PortA: "consumer", + PortB: "provider", + Order: "ordered", + }, + State: State{}, + }, + }, + ) +} + +func stepsOptInValidators(validators ...ValidatorID) []Step { + s := make([]Step, 0) + for _, val := range validators { + // Οpt in all validators + s = append(s, Step{ + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: val, + }, + State: State{ + ChainID("provi"): ChainState{}, + }, + }, + ) + } + return s +} + +// stepsInactiveProviderValidatorsGovernance validates that inactive validators +// are not included in the calculation of the quorum for governance proposals. +// It checks that when the quorum is met *among active validators*, +// the proposal can pass, even though the quorum would not be met if inactive validators +// would be counted. +func stepsInactiveProviderValidatorsGovernance() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 1, so alice and bob should not be in power + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should have passed because carol voted for it. + // carol alone is enough to pass the quorum, because stake of the other validators is not counted + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsInactiveProviderValidatorsGovernanceBasecase is a sanity check to go along with +// stepsInactiveProviderValidatorsGovernance. It tests that with all validators being active, +// the proposal does not pass if it does not meet the quorum among validators. +func stepsInactiveProviderValidatorsGovernanceBasecase() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should *not* have passed because only carol voted for it, + // and carol is not enough to pass the quorum + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_REJECTED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsMinStake validates that a validator with less stake than the specified minStake parameter +// cannot validate the consumer chain. +func stepsMinStake() []Step { + return concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + MinStake: 300000000, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + { + // we start all the validators, but due to the min stake, only carol can validate + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, // due to min stake of 300000000, only carol can validate + }, + }, + }, + }, + }, + ) +} + +// This test case validates that inactive validators are not included when computing +// the top N. +func stepsInactiveValsWithTopN() []Step { + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards + // if we would look at total rewards, alice would trivially also get rewards, + // because she gets rewards in the first block due to being in the genesis + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, // bob doesn't have to validate because he is not in the top N + ValidatorID("carol"): {"consu"}, + }, + }, + }, + }, + { + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice and bob are not in the top N, so aren't in the validator set + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + }, + }, + }, + } +} + +// stepsInactiveValsMint tests the minting of tokens with inactive validators +// It checks that inactive validators are not counted when computing whether the +// inflation rate should go up or down. +func stepsInactiveValsMint() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 29, // other validators are not in power since only 1 can be active + }, + InflationRateChange: intPtr(1), // inflation rate goes up because less than the goal is bonded, since only carol is active + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal + }, + }, + }, + } +} + +// stepsMintBasecase tests the minting of tokens without inactive validators. +// This is done as a sanity check to complement stepsInactiveValsMint. +func stepsMintBasecase() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 29, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down because more than the goal is bonded + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate *still* goes down + }, + }, + }, + } +} +*/ diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index f65ddfe6ab..a7255c2231 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -79,7 +79,7 @@ func (td *DefaultDriver) getIcsVersion(chainID ChainID) string { func (td *DefaultDriver) getTargetDriver(chainID ChainID) Chain { target := Chain{ - testConfig: td.testCfg, + testConfig: &td.testCfg, } icsVersion := td.getIcsVersion(chainID) switch icsVersion { @@ -95,7 +95,7 @@ func (td *DefaultDriver) getTargetDriver(chainID ChainID) Chain { } default: target.target = Commands{ - containerConfig: td.testCfg.containerConfig, + containerConfig: &td.testCfg.containerConfig, validatorConfigs: td.testCfg.validatorConfigs, chainConfigs: td.testCfg.chainConfigs, target: td.target, @@ -227,12 +227,16 @@ func (td *DefaultDriver) runAction(action interface{}) error { } else { target.submitChangeRewardDenomsProposal(action, td.verbose) } + case InitializeConsumerChainAction: + target.initializeConsumerChain(action, td.verbose) + case UpdateConsumerChainAction: + target.updateConsumerChain(action, td.verbose) case OptInAction: - target.optIn(action, td.target, td.verbose) + target.optIn(action, td.verbose) case OptOutAction: - target.optOut(action, td.target, td.verbose) + target.optOut(action, td.verbose) case SetConsumerCommissionRateAction: - target.setConsumerCommissionRate(action, td.target, td.verbose) + target.setConsumerCommissionRate(action, td.verbose) default: log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.name, action) } diff --git a/tests/e2e/testlib/types.go b/tests/e2e/testlib/types.go index bd7996b988..8694e7185d 100644 --- a/tests/e2e/testlib/types.go +++ b/tests/e2e/testlib/types.go @@ -12,6 +12,7 @@ import ( type ( ChainID string + ConsumerID string ValidatorID string ) @@ -124,7 +125,8 @@ type ValidatorConfig struct { // Attributes that are unique to a chain. Allows us to map (part of) // the set of strings defined above to a set of viable chains type ChainConfig struct { - ChainId ChainID + ChainId ChainID + ConsumerId ConsumerID // The account prefix configured on the chain. For example, on the Hub, this is "cosmos" AccountPrefix string // Must be unique per chain diff --git a/tests/e2e/testlib/utils.go b/tests/e2e/testlib/utils.go index 08f410269f..c9d663a3f5 100644 --- a/tests/e2e/testlib/utils.go +++ b/tests/e2e/testlib/utils.go @@ -2,11 +2,60 @@ package e2e import ( "bufio" + "encoding/json" "fmt" "log" "os/exec" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) +// GovernanceProposal is used to generate content to be used for `gov submit-proposal` command +type GovernanceProposal struct { + // Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys. + Messages []json.RawMessage `json:"messages,omitempty"` + Metadata string `json:"metadata"` + Deposit string `json:"deposit"` + Title string `json:"title"` + Summary string `json:"summary"` + Expedited bool `json:"expedited"` +} + +// GenerateGovProposalContent creates proposal content ready to be used by `gov submit-proposal` command +func GenerateGovProposalContent(title, summary, metadata, deposit, description string, expedited bool, msgs ...sdk.Msg) string { + + // Register the messages. Needed for correct type annotation in the resulting json + modcodec := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + modcodec.InterfaceRegistry().RegisterImplementations( + (*sdk.Msg)(nil), + msgs..., + ) + + proposal := GovernanceProposal{ + Metadata: metadata, + Deposit: deposit, + Title: title, + Summary: summary, + Expedited: expedited, + } + + for _, msg := range msgs { + msgJson, err := modcodec.MarshalInterfaceJSON(msg) + if err != nil { + panic(fmt.Errorf("failed marshalling message '%v' for gov proposal: err=%s", msg, err)) + } + proposal.Messages = append(proposal.Messages, msgJson) + } + raw, err := json.MarshalIndent(proposal, "", " ") + if err != nil { + panic(fmt.Errorf("failed to marshal proposal: %w", err)) + } + + return string(raw) +} + func ExecuteCommand(cmd *exec.Cmd, cmdName string, verbose bool) { if verbose { fmt.Println(cmdName+" cmd:", cmd.String())