diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 088ad19801..22e1842050 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,121 @@ func (tr Chain) submitTextProposal( tr.waitBlocks(action.Chain, 1, 10*time.Second) } +type UpdateConsumerChainAction struct { + Chain ChainID + From ValidatorID + ConsumerChain ChainID + NewOwner string + InitialHeight clienttypes.Height + SpawnTime uint + DistributionChannel string + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string + MinStake uint64 + AllowInactiveVals bool +} + +func (tr Chain) updateConsumerChain(action UpdateConsumerChainAction, verbose bool) { + + spawnTime := 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: spawnTime, + + 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 + msg := types.MsgUpdateConsumer{ + ConsumerId: string(consumerId), + NewOwnerAddress: action.NewOwner, + InitializationParameters: &initParams, + PowerShapingParameters: &powerShapingParams, + } + tr.UpdateConsumer(action.Chain, action.From, msg) +} + +type CreateConsumerChainAction struct { + Chain ChainID + From ValidatorID + ConsumerChain ChainID + InitialHeight clienttypes.Height + SpawnTime uint + DistributionChannel string + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string + MinStake uint64 + AllowInactiveVals bool +} + +// createConsumerChain creates and initializes a consumer chain +func (tr Chain) createConsumerChain(action CreateConsumerChainAction, verbose bool) { + + spawnTime := 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: spawnTime, + + 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 to get a consumer-id + consumerId := tr.CreateConsumer(action.Chain, action.ConsumerChain, action.From, metadata, &initParams, &powerShapingParams) + if verbose { + fmt.Println("Create consumer chain", string(action.ConsumerChain), " with consumer-id", string(consumerId)) + } +} + type SubmitConsumerAdditionProposalAction struct { PreCCV bool Chain ChainID @@ -268,73 +391,223 @@ type SubmitConsumerAdditionProposalAction struct { AllowInactiveVals bool } +func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer) { + content, err := json.Marshal(update) + if err != nil { + log.Fatal("failed marshalling MsgUpdateConsumer: ", 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`, + ) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("update consumer failed ", "error: ", err, "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(providerChain, 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, powerShapingParams *types.PowerShapingParameters) ConsumerID { + + chainID := string(tr.testConfig.chainConfigs[consumerChain].ChainId) + msg := types.MsgCreateConsumer{ + ChainId: chainID, + Metadata: metadata, + InitializationParameters: initParams, + PowerShapingParameters: powerShapingParams, + } + + content, err := json.Marshal(msg) + if err != nil { + log.Fatalf("failed marshalling MsgCreateConsumer: %s", 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 creation + 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`, + ) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("create consumer failed ", "error: ", err, "output: ", string(bz)) + } + + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx response on create-consumer: %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 (see issue #2198) + tr.waitBlocks(providerChain, 2, 10*time.Second) + + // Get Consumer ID from transaction + 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.Fatalf("not able to query tx containing creation-consumer: tx: %s, err: %s, out: %s", + txResponse.TxHash, err.Error(), string(bz)) + } + + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx containing create-consumer: %s, json: %s", err.Error(), string(bz)) + } + + consumerId := "" + 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 create-consumer: %v", event) + } + consumerId = attr.Value + } + if consumerId == "" { + 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(consumerId) { + log.Fatalf("chain '%s'registered already with a different consumer ID '%s'", chainID, consumerId) + } + + // Set the new created consumer-id on the chain's config + cfg.ConsumerId = e2e.ConsumerID(consumerId) + tr.testConfig.chainConfigs[e2e.ChainID(chainID)] = cfg + + return e2e.ConsumerID(consumerId) +} + +// submitConsumerAdditionProposal initializes a consumer chain and submits a governance proposal 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) + spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) - //#nosec G204 -- bypass unsafe quoting warning (no production code) - proposalFile := "/consumer-addition.proposal" + 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: spawnTime, + + UnbondingPeriod: params.UnbondingPeriod, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + DistributionTransmissionChannel: action.DistributionChannel, + } + + consumerId := 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: string(consumerId), + NewOwnerAddress: authority, + } + // For the MsgUpdateConsumer sent 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.NewOwnerAddress = "" + update.InitializationParameters = &initializationParameters + update.InitializationParameters.SpawnTime = spawnTime + 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 := "/update-consumer-proposal.json" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), ).CombinedOutput() @@ -352,6 +625,7 @@ func (tr Chain) submitConsumerAdditionProposal( `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, + `-o json`, `-y`, ) @@ -361,7 +635,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,34 +751,25 @@ 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) + msg := types.MsgRemoveConsumer{ + ConsumerId: consumerId, + StopTime: tr.testConfig.containerConfig.Now.Add(action.StopTimeOffset), + Signer: authority, + } + + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &msg) // #nosec G204 -- bypass unsafe quoting warning (no production code) - proposalFile := "/consumer-removal.proposal" + proposalFile := "/remove-consumer-proposal.json" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), ).CombinedOutput() @@ -599,47 +874,31 @@ 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" + + msg := 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, &msg) // #nosec G204 -- bypass unsafe quoting warning (no production code) - proposalFile := "/consumer-mod.proposal" + proposalFile := "/update-consumer-proposal.json" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), ).CombinedOutput() @@ -754,26 +1013,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 +1134,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 +1336,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 +2475,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.json" bz, err := tr.target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), ).CombinedOutput() @@ -2480,10 +2718,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 +2898,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 +2910,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 +2918,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 +2948,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 +2960,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 +2968,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 +2998,7 @@ func (tr Chain) optOut(action OptOutAction, target ExecutionTarget, verbose bool type SetConsumerCommissionRateAction struct { Chain ChainID + ConsumerID ConsumerID Validator ValidatorID CommissionRate float64 @@ -2767,7 +3007,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 +3015,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 +3033,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..c3f8f712b1 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 diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 2864c9b413..e8ae6b2166 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -246,6 +246,13 @@ var stepChoices = map[string]StepChoice{ description: "test minting without inactive validators as a sanity check", testConfig: MintTestCfg, }, + // TODO PERMISSIONLESS: ADD NEW E2E TEST + /* "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(), diff --git a/tests/e2e/state.go b/tests/e2e/state.go index f29c8f59f0..676d8dcfc5 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 @@ -465,6 +466,30 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Title: title, Description: description, } + case "/interchain_security.ccv.provider.v1.MsgUpdateConsumer": + consumerId := rawContent.Get("consumer_id").String() + consumerChainId := ChainID("") + for _, chainCfg := range tr.chainConfigs { + if chainCfg.ConsumerId == e2e.ConsumerID(consumerId) { + consumerChainId = chainCfg.ChainId + break + } + } + + updateProposal := ConsumerAdditionProposal{ + Deposit: uint(deposit), + Chain: consumerChainId, + Status: status, + } + if rawContent.Get("initialization_parameters").Exists() { + spawnTime := rawContent.Get("initialization_parameters.spawn_time").Time().Sub(tr.containerConfig.Now) + updateProposal.SpawnTime = int(spawnTime.Milliseconds()) + updateProposal.InitialHeight = clienttypes.Height{ + RevisionNumber: rawContent.Get("initialization_parameters.initial_height.revision_number").Uint(), + RevisionHeight: rawContent.Get("initialization_parameters.initial_height.revision_height").Uint(), + } + } + return updateProposal 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 +523,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 } @@ -746,8 +771,13 @@ func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { 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 +785,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 + consumerId := tr.chainConfigs[ChainID(consumerChain)].ConsumerId cmd := tr.target.ExecCommand(binaryName, "query", "provider", "validator-consumer-key", - string(consumerChain), tr.validatorConfigs[validator].ValconsAddress, + string(consumerId), tr.validatorConfigs[validator].ValconsAddress, `--node`, tr.GetQueryNode(ChainID("provi")), `-o`, `json`, ) @@ -773,10 +804,12 @@ func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator Validator func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { binaryName := tr.chainConfigs[ChainID("provi")].BinaryName + consumerId := tr.chainConfigs[ChainID(consumerChain)].ConsumerId + cmd := tr.target.ExecCommand(binaryName, "query", "provider", "validator-provider-key", - string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, + string(consumerId), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, `--node`, tr.GetQueryNode(ChainID("provi")), `-o`, `json`, ) @@ -898,7 +931,12 @@ 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) + break + } + } } return chains @@ -969,20 +1007,25 @@ 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 { 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 +1056,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..156457769b 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,31 +42,17 @@ func stepsOptInChain() []Step { State: State{}, }, { - Action: SubmitConsumerAdditionProposalAction{ + Action: CreateConsumerChainAction{ 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"): {}, - }, + ProposedConsumerChains: &[]string{"consu"}, }, }, }, @@ -127,25 +115,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,33 +1030,18 @@ 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: CreateConsumerChainAction{ + 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"): {}, - }, + ProposedConsumerChains: &[]string{"consu"}, }, }, }, @@ -1143,25 +1106,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,36 +1468,17 @@ func stepsValidatorsAllowlistedChain() []Step { }, }, { - Action: SubmitConsumerAdditionProposalAction{ + Action: CreateConsumerChainAction{ 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"): {}, - }, + ProposedConsumerChains: &[]string{"consu"}, }, }, }, @@ -1606,25 +1542,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,33 +1651,18 @@ 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"}, + Action: CreateConsumerChainAction{ + 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"): {}, - }, + ProposedConsumerChains: &[]string{"consu"}, }, }, }, @@ -1809,25 +1726,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,31 +1832,17 @@ func stepsModifyChain() []Step { }, }, { - Action: SubmitConsumerAdditionProposalAction{ + Action: CreateConsumerChainAction{ 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"): {}, - }, + ProposedConsumerChains: &[]string{"consu"}, }, }, }, @@ -2011,25 +1906,16 @@ 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: StartConsumerChainAction{ @@ -2105,43 +1991,16 @@ 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: RelayPacketsAction{ @@ -2171,43 +2030,16 @@ func stepsModifyChain() []Step { // 2. set the `ValidatorSetCap` to a maximum of 2 validators { - 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, 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)), - }, - }, - }, - }, + State: State{}, }, { Action: RelayPacketsAction{ @@ -2235,50 +2067,21 @@ func stepsModifyChain() []Step { }, }, }, - // 3. set an allowlist with 2 validators { - Action: SubmitConsumerModificationProposalAction{ + Action: UpdateConsumerChainAction{ Chain: ChainID("provi"), From: ValidatorID("alice"), - Deposit: 10000001, ConsumerChain: ChainID("consu"), - // only "alice" and "carol" are allowlisted (see `getDefaultValidators` in `tests/e2e/config.go`) + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, Allowlist: []string{ "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", }, }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 4: 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: 4, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 4: ConsumerModificationProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), - }, - }, - }, - }, + State: State{}, }, { Action: RelayPacketsAction{ @@ -2304,47 +2107,19 @@ func stepsModifyChain() []Step { }, }, }, - // 4. set a denylist with 1 validator { - 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, // 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: RelayPacketsAction{ @@ -2370,8 +2145,20 @@ func stepsModifyChain() []Step { }, }, }, - // 5. modify the chain from Opt In to Top 100% + // -- Change the owner to governance authority + { + Action: UpdateConsumerChainAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + ConsumerChain: ChainID("consu"), + NewOwner: "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + }, + State: State{}, + }, { Action: SubmitConsumerModificationProposalAction{ Chain: ChainID("provi"), @@ -2383,7 +2170,7 @@ func stepsModifyChain() []Step { State: State{ ChainID("provi"): ChainState{ Proposals: &map[uint]Proposal{ - 6: ConsumerModificationProposal{ + 1: ConsumerAdditionProposal{ Deposit: 10000001, Chain: ChainID("consu"), Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), @@ -2397,12 +2184,12 @@ func stepsModifyChain() []Step { Chain: ChainID("provi"), From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, Vote: []string{"yes", "yes", "yes"}, - PropNumber: 6, + PropNumber: 1, }, State: State{ ChainID("provi"): ChainState{ Proposals: &map[uint]Proposal{ - 6: ConsumerModificationProposal{ + 1: ConsumerAdditionProposal{ Deposit: 10000001, Chain: ChainID("consu"), Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index f65ddfe6ab..6c2bc35d77 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 CreateConsumerChainAction: + target.createConsumerChain(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())