diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 2751aa598f..ccbef27606 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -36,7 +36,7 @@ jobs: # Run compatibility tests for different consumer (-cv) and provider (-pv) versions. # Combination of all provider versions with consumer versions are tested. # For new versions to be tested add/modify -pc/-cv parameters. - run: go run ./tests/e2e/... --tc compatibility -pv latest -cv v5.2.0 -cv v4.4.0 + run: go run ./tests/e2e/... --tc compatibility -pv latest -cv latest -cv v5.2.0 -cv v6.3.0 happy-path-test: runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index f43bf0bc5e..588a1c5d56 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -9,7 +9,6 @@ import ( "os" "os/exec" "regexp" - "sort" "strconv" "strings" "sync" @@ -17,51 +16,50 @@ import ( ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/tidwall/gjson" - "golang.org/x/mod/semver" - - sdk "github.com/cosmos/cosmos-sdk/types" - e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" "github.com/cosmos/interchain-security/v6/x/ccv/provider/client" "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" + "github.com/tidwall/gjson" + "golang.org/x/mod/semver" ) const ( done = "done!!!!!!!!" VLatest = "latest" - V400 = "v4.0.0" - V330 = "v3.3.0" - V300 = "v3.0.0" + V630 = "v6.3.0" + V620 = "v6.2.0" ) // type aliases type ( - AssignConsumerPubKeyAction = e2e.AssignConsumerPubKeyAction + AssignConsumerPubKeyAction = e2e.AssignConsumerPubKeyAction + StartChainAction = e2e.StartChainAction + StartChainValidator = e2e.StartChainValidator + StartConsumerChainAction = e2e.StartConsumerChainAction + StartSovereignChainAction = e2e.StartSovereignChainAction + SubmitConsumerAdditionProposalAction = e2e.SubmitConsumerAdditionProposalAction + SubmitConsumerRemovalProposalAction = e2e.SubmitConsumerRemovalProposalAction + DelegateTokensAction = e2e.DelegateTokensAction + UnbondTokensAction = e2e.UnbondTokensAction + ChangeoverChainAction = e2e.ChangeoverChainAction ) type SendTokensAction struct { - Chain ChainID - From ValidatorID - To ValidatorID - Amount uint -} - -type TxResponse struct { - TxHash string `json:"txhash"` - Code int `json:"code"` - RawLog string `json:"raw_log"` - Events []sdk.Event `json:"events"` + Chain ChainID + From ValidatorID + To ValidatorID + Amount uint + ExpectErr bool } func (tr Chain) sendTokens( action SendTokensAction, verbose bool, ) { - fromValCfg := tr.testConfig.validatorConfigs[action.From] - toValCfg := tr.testConfig.validatorConfigs[action.To] + fromValCfg := tr.testConfig.ValidatorConfigs[action.From] + toValCfg := tr.testConfig.ValidatorConfigs[action.To] fromAddress := fromValCfg.DelAddress toAddress := toValCfg.DelAddress if action.Chain != ChainID("provi") { @@ -80,7 +78,7 @@ func (tr Chain) sendTokens( } } - binaryName := tr.testConfig.chainConfigs[action.Chain].BinaryName + binaryName := tr.testConfig.ChainConfigs[action.Chain].BinaryName cmd := tr.target.ExecCommand(binaryName, "tx", "bank", "send", @@ -88,10 +86,11 @@ func (tr Chain) sendTokens( toAddress, fmt.Sprint(action.Amount)+`stake`, - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, + `-o`, `json`, `-y`, ) if verbose { @@ -102,29 +101,21 @@ func (tr Chain) sendTokens( log.Fatal(err, "\n", string(bz)) } - // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(action.Chain, 2, 30*time.Second) -} - -type StartChainAction struct { - Chain ChainID - Validators []StartChainValidator - // Genesis changes specific to this action, appended to genesis changes defined in chain config - GenesisChanges string - IsConsumer bool -} - -type StartChainValidator struct { - Id ValidatorID - Allocation uint - Stake uint + if action.ExpectErr { + if e2e.GetTxResponse(bz).Code == 0 { + log.Fatalf("`tx bank send` did not fail as expected: %v", string(bz)) + } + } else { + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitForTx(action.Chain, bz, 30*time.Second) + } } -func (tr *Chain) startChain( +func (tr *Chain) StartChain( action StartChainAction, verbose bool, ) { - chainConfig := tr.testConfig.chainConfigs[action.Chain] + chainConfig := tr.testConfig.ChainConfigs[action.Chain] type jsonValAttrs struct { Mnemonic string `json:"mnemonic"` Allocation string `json:"allocation"` @@ -142,18 +133,18 @@ func (tr *Chain) startChain( var validators []jsonValAttrs for _, val := range action.Validators { validators = append(validators, jsonValAttrs{ - Mnemonic: tr.testConfig.validatorConfigs[val.Id].Mnemonic, - NodeKey: tr.testConfig.validatorConfigs[val.Id].NodeKey, + Mnemonic: tr.testConfig.ValidatorConfigs[val.Id].Mnemonic, + NodeKey: tr.testConfig.ValidatorConfigs[val.Id].NodeKey, ValId: fmt.Sprint(val.Id), - PrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].PrivValidatorKey, + PrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].PrivValidatorKey, Allocation: fmt.Sprint(val.Allocation) + "stake", Stake: fmt.Sprint(val.Stake) + "stake", - IpSuffix: tr.testConfig.validatorConfigs[val.Id].IpSuffix, + IpSuffix: tr.testConfig.ValidatorConfigs[val.Id].IpSuffix, - ConsumerMnemonic: tr.testConfig.validatorConfigs[val.Id].ConsumerMnemonic, - ConsumerPrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].ConsumerPrivValidatorKey, + ConsumerMnemonic: tr.testConfig.ValidatorConfigs[val.Id].ConsumerMnemonic, + ConsumerPrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].ConsumerPrivValidatorKey, // if true node will be started with consumer key for each consumer chain - StartWithConsumerKey: tr.testConfig.validatorConfigs[val.Id].UseConsumerKey, + StartWithConsumerKey: tr.testConfig.ValidatorConfigs[val.Id].UseConsumerKey, }) } @@ -171,7 +162,7 @@ func (tr *Chain) startChain( } var cometmockArg string - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { cometmockArg = "true" } else { cometmockArg = "false" @@ -187,7 +178,7 @@ func (tr *Chain) startChain( // usually timeout_commit and peer_gossip_sleep_duration are changed to vary the test run duration // lower timeout_commit means the blocks are produced faster making the test run shorter // with short timeout_commit (eg. timeout_commit = 1s) some nodes may miss blocks causing the test run to fail - tr.testConfig.tendermintConfigOverride, + tr.testConfig.TendermintConfigOverride, cometmockArg, chainHome, ) @@ -224,11 +215,11 @@ func (tr *Chain) startChain( }, verbose) // store the fact that we started the chain - tr.testConfig.runningChains[action.Chain] = true + tr.testConfig.RunningChains[action.Chain] = true fmt.Println("Started chain", action.Chain) - if tr.testConfig.timeOffset != 0 { + if tr.testConfig.TimeOffset != 0 { // advance time for this chain so that it is in sync with the rest of the network - tr.AdvanceTimeForChain(action.Chain, tr.testConfig.timeOffset) + tr.AdvanceTimeForChain(action.Chain, tr.testConfig.TimeOffset) } } @@ -246,13 +237,13 @@ func (tr Chain) submitTextProposal( ) { // TEXT PROPOSAL bz, err := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", `--title`, action.Title, `--description`, action.Description, `--deposit`, fmt.Sprint(action.Deposit)+`stake`, `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, @@ -296,7 +287,7 @@ func (tr Chain) updateConsumerChain(action UpdateConsumerChainAction, verbose bo var initParams *types.ConsumerInitializationParameters if action.InitParams != nil { - spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.InitParams.SpawnTime) * time.Millisecond) + spawnTime := tr.testConfig.ContainerConfig.Now.Add(time.Duration(action.InitParams.SpawnTime) * time.Millisecond) params := ccvtypes.DefaultParams() initParams = &types.ConsumerInitializationParameters{ InitialHeight: action.InitParams.InitialHeight, @@ -324,14 +315,19 @@ func (tr Chain) updateConsumerChain(action UpdateConsumerChainAction, verbose bo Prioritylist: action.PowerShapingParams.Prioritylist, } - consumerId := tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId + 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, verbose) + + bz, err := tr.target.UpdateConsumer(action.Chain, action.From, msg, verbose) + if err != nil { + log.Fatalf("error on update consumer consumer-id=%s, err=%s", consumerId, err.Error()) + } + tr.waitForTx(action.Chain, bz, 10*time.Second) } type CreateConsumerChainAction struct { @@ -344,8 +340,8 @@ type CreateConsumerChainAction struct { // createConsumerChain creates and initializes a consumer chain func (tr Chain) createConsumerChain(action CreateConsumerChainAction, verbose bool) { - consumerChainCfg := tr.testConfig.chainConfigs[action.ConsumerChain] - providerChainCfg := tr.testConfig.chainConfigs[action.Chain] + consumerChainCfg := tr.testConfig.ChainConfigs[action.ConsumerChain] + providerChainCfg := tr.testConfig.ChainConfigs[action.Chain] if consumerChainCfg.ConsumerId != "" { log.Fatalf("consumer chain already created for '%s'", action.ConsumerChain) @@ -353,7 +349,7 @@ func (tr Chain) createConsumerChain(action CreateConsumerChainAction, verbose bo var initParams *types.ConsumerInitializationParameters if action.InitParams != nil { - spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.InitParams.SpawnTime) * time.Millisecond) + spawnTime := tr.testConfig.ContainerConfig.Now.Add(time.Duration(action.InitParams.SpawnTime) * time.Millisecond) params := ccvtypes.DefaultParams() initParams = &types.ConsumerInitializationParameters{ InitialHeight: action.InitParams.InitialHeight, @@ -388,14 +384,37 @@ func (tr Chain) createConsumerChain(action CreateConsumerChainAction, verbose bo } // create consumer to get a consumer-id - consumerId := tr.CreateConsumer(providerChainCfg.ChainId, consumerChainCfg.ChainId, action.From, metadata, initParams, powerShapingParams) + bz, err := tr.target.CreateConsumer(providerChainCfg.ChainId, consumerChainCfg.ChainId, action.From, metadata, initParams, powerShapingParams) + if err != nil { + log.Fatalf("create consumer failed error: '%s'\n%s", err.Error(), string(bz)) + } + + txResponse := tr.waitForTx(providerChainCfg.ChainId, bz, 10*time.Second) + + consumerId := "" + for _, event := range txResponse.Events { + if event.Type != "create_consumer" { + 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", + consumerChainCfg.ChainId, txResponse.Events) + } + if verbose { fmt.Println("Created consumer chain", string(consumerChainCfg.ChainId), " with consumer-id", string(consumerId)) } // Set the new created consumer-id on the chain's config - consumerChainCfg.ConsumerId = consumerId - tr.testConfig.chainConfigs[action.ConsumerChain] = consumerChainCfg + consumerChainCfg.ConsumerId = ConsumerID(consumerId) + tr.testConfig.ChainConfigs[action.ConsumerChain] = consumerChainCfg } type RemoveConsumerChainAction struct { @@ -405,7 +424,7 @@ type RemoveConsumerChainAction struct { } func (tr Chain) removeConsumerChain(action RemoveConsumerChainAction, verbose bool) { - consumerId := tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId + consumerId := tr.testConfig.ChainConfigs[action.ConsumerChain].ConsumerId if consumerId == "" { log.Fatal("failed removing consumer chain. no consumer-id found for chain: ", action.ConsumerChain) @@ -413,10 +432,10 @@ func (tr Chain) removeConsumerChain(action RemoveConsumerChainAction, verbose bo // Send consumer chain removal cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "provider", "remove-consumer", string(consumerId), `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), @@ -450,175 +469,16 @@ func (tr Chain) removeConsumerChain(action RemoveConsumerChainAction, verbose bo } -type SubmitConsumerAdditionProposalAction struct { - PreCCV bool - Chain ChainID - From ValidatorID - Deposit uint - ConsumerChain ChainID - SpawnTime uint - InitialHeight clienttypes.Height - DistributionChannel string - TopN uint32 - ValidatorsPowerCap uint32 - ValidatorSetCap uint32 - Allowlist []string - Denylist []string - MinStake uint64 - AllowInactiveVals bool - Prioritylist []string -} - -func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer, verbose bool) { - 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 { - fmt.Println("command failed: ", cmd) - log.Fatalf("update consumer failed error: %s, output: %s", err, 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) - } - - if verbose { - fmt.Println("running 'update-consumer' returned: ", txResponse) - } - - 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 { - - msg := types.MsgCreateConsumer{ - ChainId: string(consumerChain), - 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 != "create_consumer" { - 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) - } - - return ConsumerID(consumerId) -} - // submitConsumerAdditionProposal initializes a consumer chain and submits a governance proposal -func (tr Chain) submitConsumerAdditionProposal( - action SubmitConsumerAdditionProposalAction, +func (tr Chain) SubmitConsumerAdditionProposal( + action e2e.SubmitConsumerAdditionProposalAction, verbose bool, ) { params := ccvtypes.DefaultParams() - spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) - consumerChainCfg := tr.testConfig.chainConfigs[action.ConsumerChain] - providerChainCfg := tr.testConfig.chainConfigs[action.Chain] + testCfg := tr.testConfig //tr.GetTestConfig() + spawnTime := testCfg.ContainerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) + consumerChainCfg := tr.testConfig.ChainConfigs[action.ConsumerChain] + providerChainCfg := tr.testConfig.ChainConfigs[action.Chain] Metadata := types.ConsumerMetadata{ Name: "chain " + string(action.Chain), @@ -641,11 +501,34 @@ func (tr Chain) submitConsumerAdditionProposal( DistributionTransmissionChannel: action.DistributionChannel, } - consumerId := tr.CreateConsumer(providerChainCfg.ChainId, consumerChainCfg.ChainId, action.From, Metadata, &initializationParameters, nil) + bz, err := tr.target.CreateConsumer(providerChainCfg.ChainId, consumerChainCfg.ChainId, action.From, Metadata, &initializationParameters, nil) + if err != nil { + log.Fatalf("create consumer failed error: '%s'\n%s", err.Error(), string(bz)) + } + + txResponse := tr.waitForTx(providerChainCfg.ChainId, bz, 10*time.Second) + + consumerId := "" + for _, event := range txResponse.Events { + if event.Type != "create_consumer" { + 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", + consumerChainCfg.ChainId, txResponse.Events) + } + authority := "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" // Set the new created consumer-id on the chain's config - consumerChainCfg.ConsumerId = consumerId - tr.testConfig.chainConfigs[action.ConsumerChain] = consumerChainCfg + consumerChainCfg.ConsumerId = ConsumerID(consumerId) + tr.testConfig.ChainConfigs[action.ConsumerChain] = consumerChainCfg // Update consumer to change owner to governance before submitting the proposal update := &types.MsgUpdateConsumer{ @@ -664,7 +547,12 @@ func (tr Chain) submitConsumerAdditionProposal( Prioritylist: action.Prioritylist, } update.PowerShapingParameters = &powerShapingParameters - tr.UpdateConsumer(action.Chain, action.From, *update, verbose) + + bz, err = tr.target.UpdateConsumer(action.Chain, action.From, *update, verbose) + if err != nil { + log.Fatalf("error updating consumer '%s': err=%s, out=%s", consumerId, err.Error(), string(bz)) + } + tr.waitForTx(action.Chain, bz, 10*time.Second) // - set PowerShaping params TopN > 0 for consumer chain update.PowerShapingParameters.Top_N = action.TopN @@ -682,131 +570,14 @@ func (tr Chain) submitConsumerAdditionProposal( 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() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // CONSUMER ADDITION PROPOSAL - cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, - "tx", "gov", "submit-proposal", proposalFile, - `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(providerChainCfg.ChainId), - `--home`, tr.getValidatorHome(providerChainCfg.ChainId, action.From), - `--gas`, `900000`, - `--node`, tr.getValidatorNode(providerChainCfg.ChainId, action.From), - `--keyring-backend`, `test`, - `-o json`, - `-y`, - ) - - if verbose { - fmt.Println("submitConsumerAdditionProposal cmd:", cmd.String()) - fmt.Println("submitConsumerAdditionProposal json:", jsonStr) - } - bz, err = cmd.CombinedOutput() + bz, err = tr.target.SubmitGovProposal(providerChainCfg.ChainId, action.From, "", jsonStr, verbose) if err != nil { - log.Fatal("executing submit-proposal failed:", err, "\n", string(bz)) + log.Fatalf("gov submit consumer addition command failed with error: '%s', out:'%s'", + err.Error(), 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)) - } - + txResponse = e2e.GetTxResponse(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 { - fmt.Println("submitConsumerAdditionProposal output:", string(bz)) - } - - // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(providerChainCfg.ChainId, 2, 10*time.Second) -} - -func (tr Chain) submitConsumerAdditionLegacyProposal( - action SubmitConsumerAdditionProposalAction, - verbose bool, -) { - spawnTime := tr.testConfig.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) - params := ccvtypes.DefaultParams() - prop := client.ConsumerAdditionProposalJSON{ - Title: "Propose the addition of a new chain", - Summary: "Gonna be a great chain", - ChainId: string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), - InitialHeight: action.InitialHeight, - GenesisHash: []byte("gen_hash"), - BinaryHash: []byte("bin_hash"), - SpawnTime: spawnTime, - ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, - BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, - HistoricalEntries: params.HistoricalEntries, - CcvTimeoutPeriod: params.CcvTimeoutPeriod, - TransferTimeoutPeriod: params.TransferTimeoutPeriod, - UnbondingPeriod: params.UnbondingPeriod, - Deposit: fmt.Sprint(action.Deposit) + `stake`, - DistributionTransmissionChannel: action.DistributionChannel, - TopN: action.TopN, - ValidatorsPowerCap: action.ValidatorsPowerCap, - ValidatorSetCap: action.ValidatorSetCap, - Allowlist: action.Allowlist, - Denylist: action.Denylist, - MinStake: action.MinStake, - AllowInactiveVals: action.AllowInactiveVals, - Prioritylist: action.Prioritylist, - } - - bz, err := json.Marshal(prop) - if err != nil { - log.Fatal(err) - } - - jsonStr := string(bz) - if strings.Contains(jsonStr, "'") { - log.Fatal("prop json contains single quote") - } - - //#nosec G204 -- bypass unsafe quoting warning (no production code) - cmd := tr.target.ExecCommand( - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json")) - bz, err = cmd.CombinedOutput() - if verbose { - log.Println("submitConsumerAdditionProposal cmd: ", cmd.String()) - } - - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // CONSUMER ADDITION PROPOSAL - cmd = tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, - "tx", "gov", "submit-legacy-proposal", "consumer-addition", "/temp-proposal.json", - `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), - `--home`, tr.getValidatorHome(action.Chain, action.From), - `--gas`, `900000`, - `--node`, tr.getValidatorNode(action.Chain, action.From), - `--keyring-backend`, `test`, - `-y`, - ) - - if verbose { - fmt.Println("submitConsumerAdditionProposal cmd:", cmd.String()) - fmt.Println("submitConsumerAdditionProposal json:", jsonStr) - } - bz, err = cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) + log.Fatalf("gov submit consumer addition transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) } if verbose { @@ -814,21 +585,14 @@ func (tr Chain) submitConsumerAdditionLegacyProposal( } // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(ChainID("provi"), 2, 10*time.Second) + tr.waitForTx(providerChainCfg.ChainId, bz, 20*time.Second) } -type SubmitConsumerRemovalProposalAction struct { - Chain ChainID - From ValidatorID - Deposit uint - ConsumerChain ChainID -} - -func (tr Chain) submitConsumerRemovalProposal( +func (tr Chain) SubmitConsumerRemovalProposal( action SubmitConsumerRemovalProposalAction, verbose bool, ) { - consumerId := string(tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId) + consumerId := string(tr.testConfig.ChainConfigs[action.ConsumerChain].ConsumerId) title := fmt.Sprintf("Stop the %v chain", action.ConsumerChain) description := "stop consumer chain" summary := "It was a great chain" @@ -855,20 +619,21 @@ func (tr Chain) submitConsumerRemovalProposal( // CONSUMER REMOVAL PROPOSAL cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-proposal", proposalFile, `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, + `-o`, `json`, `-y`, ) if verbose { - fmt.Println("submitConsumerRemovalProposal cmd:", cmd.String()) - fmt.Println("submitConsumerRemovalProposal json:", jsonStr) + fmt.Println("SubmitConsumerRemovalProposal cmd:", cmd.String()) + fmt.Println("SubmitConsumerRemovalProposal json:", jsonStr) } bz, err = cmd.CombinedOutput() if err != nil { @@ -876,11 +641,11 @@ func (tr Chain) submitConsumerRemovalProposal( } if verbose { - fmt.Println("submitConsumerRemovalProposal output:", string(bz)) + fmt.Println("SubmitConsumerRemovalProposal output:", string(bz)) } // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(ChainID("provi"), 2, 20*time.Second) + tr.waitForTx(ChainID("provi"), bz, 30*time.Second) } func (tr Chain) submitConsumerRemovalLegacyProposal( @@ -890,7 +655,7 @@ func (tr Chain) submitConsumerRemovalLegacyProposal( prop := client.ConsumerRemovalProposalJSON{ Title: fmt.Sprintf("Stop the %v chain", action.ConsumerChain), Summary: "It was a great chain", - ChainId: string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), + ChainId: string(tr.testConfig.ChainConfigs[action.ConsumerChain].ChainId), Deposit: fmt.Sprint(action.Deposit) + `stake`, } @@ -911,11 +676,11 @@ func (tr Chain) submitConsumerRemovalLegacyProposal( } bz, err = tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", "consumer-removal", "/temp-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--node`, tr.getValidatorNode(action.Chain, action.From), `--gas`, "900000", @@ -950,7 +715,7 @@ func (tr Chain) submitConsumerModificationProposal( action SubmitConsumerModificationProposalAction, verbose bool, ) { - consumerId := string(tr.testConfig.chainConfigs[action.ConsumerChain].ConsumerId) + 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" @@ -985,10 +750,10 @@ func (tr Chain) submitConsumerModificationProposal( // CONSUMER MODIFICATION PROPOSAL cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-proposal", proposalFile, `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), @@ -1020,7 +785,7 @@ func (tr Chain) submitConsumerModificationLegacyProposal( prop := client.ConsumerModificationProposalJSON{ Title: "Propose the modification of the PSS parameters of a chain", Summary: "summary of a modification proposal", - ChainId: string(tr.testConfig.chainConfigs[action.ConsumerChain].ChainId), + ChainId: string(tr.testConfig.ChainConfigs[action.ConsumerChain].ChainId), Deposit: fmt.Sprint(action.Deposit) + `stake`, TopN: action.TopN, ValidatorsPowerCap: action.ValidatorsPowerCap, @@ -1050,10 +815,10 @@ func (tr Chain) submitConsumerModificationLegacyProposal( // CONSUMER MODIFICATION PROPOSAL cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", "consumer-modification", "/temp-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), @@ -1116,10 +881,10 @@ func (tr Chain) submitEnableTransfersProposalAction( } cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-proposal", "/params-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--node`, tr.getValidatorNode(action.Chain, action.From), `--gas`, "900000", @@ -1154,13 +919,13 @@ func (tr *Chain) voteGovProposal( go func(val ValidatorID, vote string) { defer wg.Done() bz, err := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "vote", fmt.Sprint(action.PropNumber), vote, `--from`, `validator`+fmt.Sprint(val), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, val), `--node`, tr.getValidatorNode(action.Chain, val), `--keyring-backend`, `test`, @@ -1176,23 +941,16 @@ func (tr *Chain) voteGovProposal( wg.Wait() // wait for inclusion in a block -> '--broadcast-mode block' is deprecated tr.waitBlocks(action.Chain, 1, 10*time.Second) - tr.WaitTime(time.Duration(tr.testConfig.chainConfigs[action.Chain].VotingWaitTime) * time.Second) + tr.WaitTime(time.Duration(tr.testConfig.ChainConfigs[action.Chain].VotingWaitTime) * time.Second) } -type StartConsumerChainAction struct { - ConsumerChain ChainID - ProviderChain ChainID - Validators []StartChainValidator - GenesisChanges string -} - -func (tr *Chain) startConsumerChain( +func (tr *Chain) StartConsumerChain( action StartConsumerChainAction, verbose bool, ) { fmt.Println("Starting consumer chain ", action.ConsumerChain) consumerGenesis := ".app_state.ccvconsumer = " + tr.getConsumerGenesis(action.ProviderChain, action.ConsumerChain) - consumerGenesisChanges := tr.testConfig.chainConfigs[action.ConsumerChain].GenesisChanges + consumerGenesisChanges := tr.testConfig.ChainConfigs[action.ConsumerChain].GenesisChanges if consumerGenesisChanges != "" { consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges } @@ -1200,7 +958,7 @@ func (tr *Chain) startConsumerChain( consumerGenesis = consumerGenesis + " | " + action.GenesisChanges } - tr.startChain(StartChainAction{ + tr.StartChain(e2e.StartChainAction{ Chain: action.ConsumerChain, Validators: action.Validators, GenesisChanges: consumerGenesis, @@ -1211,8 +969,8 @@ func (tr *Chain) startConsumerChain( // Get consumer genesis from provider 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) + providerBinaryName := tr.testConfig.ChainConfigs[providerChain].BinaryName + consumerId := string(tr.testConfig.ChainConfigs[consumerChain].ConsumerId) now := time.Now() timeout := now.Add(30 * time.Second) @@ -1239,8 +997,10 @@ func (tr *Chain) getConsumerGenesis(providerChain, consumerChain ChainID) string time.Sleep(2 * time.Second) } - if tr.testConfig.transformGenesis || needsGenesisTransform(*tr.testConfig) { - return string(tr.transformConsumerGenesis(consumerChain, bz)) + targetVersion := "v5.x" // default to v5.x in case transformation is required + needsTransform, targetVersion := needsGenesisTransform(*tr.testConfig) + if tr.testConfig.TransformGenesis || needsTransform { + return string(tr.transformConsumerGenesis(targetVersion, bz)) } else { fmt.Println("No genesis transformation performed") } @@ -1248,112 +1008,52 @@ func (tr *Chain) getConsumerGenesis(providerChain, consumerChain ChainID) string } // needsGenesisTransform tries to identify if a genesis transformation should be performed -func needsGenesisTransform(cfg TestConfig) bool { +func needsGenesisTransform(cfg TestConfig) (bool, string) { // no genesis transformation needed for same versions - if cfg.consumerVersion == cfg.providerVersion { - return false + if cfg.ConsumerVersion == cfg.ProviderVersion { + return false, "" } - // use v4.0.0 (after genesis transform breakages) for the checks if 'latest' is used - consumerVersion := cfg.consumerVersion - if cfg.consumerVersion == VLatest { - consumerVersion = V400 + // use v6.3.0 as reference for latest versions + consumerVersion := cfg.ConsumerVersion + if cfg.ConsumerVersion == VLatest { + consumerVersion = V630 } - providerVersion := cfg.providerVersion - if cfg.providerVersion == VLatest { - providerVersion = V400 + providerVersion := cfg.ProviderVersion + if cfg.ProviderVersion == VLatest { + providerVersion = V630 } - if !semver.IsValid(consumerVersion) || !semver.IsValid(providerVersion) { - fmt.Printf("unable to identify the need for genesis transformation: invalid sem-version: consumer='%s', provider='%s'", - consumerVersion, providerVersion) - return false + if !semver.IsValid(consumerVersion) { + fmt.Printf("unable to identify the need for genesis transformation: invalid sem-version: consumer='%s'\n", + consumerVersion) + return false, "" + } + if !semver.IsValid(providerVersion) { + fmt.Printf("unable to identify the need for genesis transformation: invalid sem-version: provider='%s'\n", + providerVersion) + return false, "" } - breakages := []string{V300, V330, V400} - for _, breakage := range breakages { - if (semver.Compare(consumerVersion, breakage) < 0 && semver.Compare(providerVersion, breakage) >= 0) || - (semver.Compare(providerVersion, breakage) < 0 && semver.Compare(consumerVersion, breakage) >= 0) { - fmt.Println("genesis transformation needed for versions:", providerVersion, consumerVersion) - return true - } + if semver.Compare(providerVersion, "v6.2") < 0 { + return false, "" + } + // genesis transformation needed for provider >= v6.2.0 and consumer < v4.5.0 or >= v5.0.0 and < v6.2.0 + if semver.Compare(consumerVersion, "v4.5.0") < 0 { + return true, "v4.x" } - fmt.Println("NO genesis transformation needed for versions:", providerVersion, consumerVersion) - return false -} - -// getTransformParameter identifies the needed transformation parameter for current `transformGenesis` implementation -// based on consumer and provider versions. -func getTransformParameter(consumerVersion string) (string, error) { - switch consumerVersion { - case "": - // For "" (default: local workspace) use HEAD as reference point - consumerVersion = "HEAD" - case VLatest: - // For 'latest' originated from latest-image use "origin/main" as ref point - consumerVersion = "origin/main" - } - - // Hash of breakage due to preHashKey release in version 2.x - // ics23/go v.0.10.0 adding 'prehash_key_before_comparison' in ProofSpec - breakage_prehash := "d4dde74b062c2fded0d3b3dbef4b3b0229e317f3" // first released in v3.2.0-consumer - - // breakage 2: split of genesis - breakage_splitgenesisMain := "946f6ec626d3de3fe2e00cbb386ccf9c2f05d94d" - breakage_splitgenesisV33x := "1d2641a3b2ba706ae0a307d9019b48c62d86133b" - - // breakage 3: split of genesis + delay_period - breakage_retry_delay := "88499b7c650ea0fb2c448af2b182ad5fee94d795" - - // mapping of the accepted parameter values of the `genesis transform` command - // to the related git refs introducing a breakage - transformParams := map[string][]string{ - "v2.x": {breakage_prehash}, - "v3.3.x": {breakage_splitgenesisMain, breakage_splitgenesisV33x}, - "v4.x": {breakage_retry_delay}, - } - - // set default consumer target version to "v4.x" - // and iterate in order of breakage history [oldest first] to identify - // the "--to" target for consumer version used - targetVersion := "v4.x" - keys := make([]string, 0, len(transformParams)) - for k := range transformParams { - keys = append(keys, k) - } - sort.Slice(keys, func(k, l int) bool { return keys[k] < keys[l] }) - - for _, version := range keys { - for _, breakageHash := range transformParams[version] { - // Check if the 'breakage' is an ancestor of the 'consumerVersion' - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments - cmd := exec.Command("git", "merge-base", "--is-ancestor", breakageHash, consumerVersion) - fmt.Println("running ", cmd) - out, err := cmd.CombinedOutput() - if err == nil { - // breakage is already part of the consumer version -> goto next breakage - fmt.Println(" consumer >= breakage ", transformParams[version], " ... going to next one") - targetVersion = version - break - } - if rc, ok := err.(*exec.ExitError); ok { - if rc.ExitCode() != 1 { - return "", fmt.Errorf("error identifying transform parameter '%v': %s", err, string(out)) - } - // not an ancestor -- ignore this breakage - fmt.Println("breakage :", transformParams[version], " is not an ancestor of version ", version) - continue - } - return "", fmt.Errorf("unexpected error when running '%v': %v", cmd, err) // unable to get return code - } + if semver.Compare(consumerVersion, "v5.0.0") >= 0 && semver.Compare(consumerVersion, "v6.2.0") < 0 { + fmt.Println("genesis transformation needed for versions:", providerVersion, consumerVersion) + return true, "v5.x" } - // consumer > latest known breakage (use default target version 'v4.x') - return fmt.Sprintf("--to=%s", targetVersion), nil + + fmt.Println("NO genesis transformation needed for versions:", providerVersion, consumerVersion) + return false, "" } // Transform consumer genesis content from older version -func (tr *Chain) transformConsumerGenesis(consumerChain ChainID, genesis []byte) []byte { +func (tr *Chain) transformConsumerGenesis(targetVersion string, genesis []byte) []byte { fmt.Println("Transforming consumer genesis") fileName := "consumer_genesis.json" @@ -1367,7 +1067,7 @@ func (tr *Chain) transformConsumerGenesis(consumerChain ChainID, genesis []byte) log.Panicf("Failed writing consumer genesis to file: %v", err) } - containerInstance := tr.testConfig.containerConfig.InstanceName + containerInstance := tr.testConfig.ContainerConfig.ContainerName targetFile := fmt.Sprintf("/tmp/%s", fileName) sourceFile := file.Name() //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -1379,14 +1079,11 @@ func (tr *Chain) transformConsumerGenesis(consumerChain ChainID, genesis []byte) } // check if genesis transform supports --to target + targetVersion = fmt.Sprintf("--to=%s", targetVersion) bz, err := tr.target.ExecCommand( "interchain-security-transformer", "genesis", "transform", "--to").CombinedOutput() if err != nil && !strings.Contains(string(bz), "unknown flag: --to") { - targetVersion, err := getTransformParameter(tr.testConfig.consumerVersion) - if err != nil { - log.Panic("Failed getting genesis transformation parameter: ", err) - } cmd = tr.target.ExecCommand( "interchain-security-transformer", "genesis", "transform", targetVersion, targetFile) @@ -1403,20 +1100,13 @@ func (tr *Chain) transformConsumerGenesis(consumerChain ChainID, genesis []byte) return result } -type ChangeoverChainAction struct { - SovereignChain ChainID - ProviderChain ChainID - Validators []StartChainValidator - GenesisChanges string -} - func (tr Chain) changeoverChain( - action ChangeoverChainAction, + action e2e.ChangeoverChainAction, verbose bool, ) { consumerGenesis := ".app_state.ccvconsumer = " + tr.getConsumerGenesis(action.ProviderChain, action.SovereignChain) - consumerGenesisChanges := tr.testConfig.chainConfigs[action.SovereignChain].GenesisChanges + consumerGenesisChanges := tr.testConfig.ChainConfigs[action.SovereignChain].GenesisChanges if consumerGenesisChanges != "" { consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges } @@ -1424,17 +1114,17 @@ func (tr Chain) changeoverChain( consumerGenesis = consumerGenesis + " | " + action.GenesisChanges } - tr.startChangeover(ChangeoverChainAction{ + tr.startChangeover(e2e.ChangeoverChainAction{ Validators: action.Validators, GenesisChanges: consumerGenesis, }, verbose) } func (tr Chain) startChangeover( - action ChangeoverChainAction, + action e2e.ChangeoverChainAction, verbose bool, ) { - chainConfig := tr.testConfig.chainConfigs[ChainID("sover")] + chainConfig := tr.testConfig.ChainConfigs[ChainID("sover")] type jsonValAttrs struct { Mnemonic string `json:"mnemonic"` Allocation string `json:"allocation"` @@ -1452,18 +1142,18 @@ func (tr Chain) startChangeover( var validators []jsonValAttrs for _, val := range action.Validators { validators = append(validators, jsonValAttrs{ - Mnemonic: tr.testConfig.validatorConfigs[val.Id].Mnemonic, - NodeKey: tr.testConfig.validatorConfigs[val.Id].NodeKey, + Mnemonic: tr.testConfig.ValidatorConfigs[val.Id].Mnemonic, + NodeKey: tr.testConfig.ValidatorConfigs[val.Id].NodeKey, ValId: fmt.Sprint(val.Id), - PrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].PrivValidatorKey, + PrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].PrivValidatorKey, Allocation: fmt.Sprint(val.Allocation) + "stake", Stake: fmt.Sprint(val.Stake) + "stake", - IpSuffix: tr.testConfig.validatorConfigs[val.Id].IpSuffix, + IpSuffix: tr.testConfig.ValidatorConfigs[val.Id].IpSuffix, - ConsumerMnemonic: tr.testConfig.validatorConfigs[val.Id].ConsumerMnemonic, - ConsumerPrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].ConsumerPrivValidatorKey, + ConsumerMnemonic: tr.testConfig.ValidatorConfigs[val.Id].ConsumerMnemonic, + ConsumerPrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].ConsumerPrivValidatorKey, // if true node will be started with consumer key for each consumer chain - StartWithConsumerKey: tr.testConfig.validatorConfigs[val.Id].UseConsumerKey, + StartWithConsumerKey: tr.testConfig.ValidatorConfigs[val.Id].UseConsumerKey, }) } @@ -1486,7 +1176,7 @@ func (tr Chain) startChangeover( "/bin/bash", changeoverScript, chainConfig.UpgradeBinary, string(vals), "sover", chainConfig.IpPrefix, genesisChanges, - tr.testConfig.tendermintConfigOverride, + tr.testConfig.TendermintConfigOverride, ) cmdReader, err := cmd.StdoutPipe() @@ -1547,7 +1237,7 @@ func (tr Chain) addChainToRelayer( action AddChainToRelayerAction, verbose bool, ) { - if !tr.testConfig.useGorelayer { + if !tr.testConfig.UseGorelayer { tr.addChainToHermes(action, verbose) } else { tr.addChainToGorelayer(action, verbose) @@ -1559,13 +1249,13 @@ func (tr Chain) addChainToGorelayer( verbose bool, ) { queryNodeIP := tr.target.GetQueryNodeIP(action.Chain) - ChainId := tr.testConfig.chainConfigs[action.Chain].ChainId + ChainId := tr.testConfig.ChainConfigs[action.Chain].ChainId rpcAddr := "http://" + queryNodeIP + ":26658" chainConfig := fmt.Sprintf(gorelayerChainConfigTemplate, ChainId, rpcAddr, - tr.testConfig.chainConfigs[action.Chain].AccountPrefix, + tr.testConfig.ChainConfigs[action.Chain].AccountPrefix, ) bz, err := tr.target.ExecCommand( @@ -1586,7 +1276,7 @@ func (tr Chain) addChainToGorelayer( addChainCommand := tr.target.ExecCommand("rly", "chains", "add", "--file", chainConfigFileName, string(ChainId)) e2e.ExecuteCommand(addChainCommand, "add chain", verbose) - keyRestoreCommand := tr.target.ExecCommand("rly", "keys", "restore", string(ChainId), "default", tr.testConfig.validatorConfigs[action.Validator].Mnemonic) + keyRestoreCommand := tr.target.ExecCommand("rly", "keys", "restore", string(ChainId), "default", tr.testConfig.ValidatorConfigs[action.Validator].Mnemonic) e2e.ExecuteCommand(keyRestoreCommand, "restore keys", verbose) } @@ -1606,7 +1296,7 @@ func (tr Chain) addChainToHermes( hermesVersion := match[1] queryNodeIP := tr.target.GetQueryNodeIP(action.Chain) - hermesConfig := GetHermesConfig(hermesVersion, queryNodeIP, tr.testConfig.chainConfigs[action.Chain], action.IsConsumer) + hermesConfig := e2e.GetHermesConfig(hermesVersion, queryNodeIP, tr.testConfig.ChainConfigs[action.Chain], action.IsConsumer) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, hermesConfig, "/root/.hermes/config.toml") @@ -1617,10 +1307,10 @@ func (tr Chain) addChainToHermes( // Save mnemonic to file within container var mnemonic string - if tr.testConfig.validatorConfigs[action.Validator].UseConsumerKey && action.IsConsumer { - mnemonic = tr.testConfig.validatorConfigs[action.Validator].ConsumerMnemonic + if tr.testConfig.ValidatorConfigs[action.Validator].UseConsumerKey && action.IsConsumer { + mnemonic = tr.testConfig.ValidatorConfigs[action.Validator].ConsumerMnemonic } else { - mnemonic = tr.testConfig.validatorConfigs[action.Validator].Mnemonic + mnemonic = tr.testConfig.ValidatorConfigs[action.Validator].Mnemonic } saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") @@ -1632,7 +1322,7 @@ func (tr Chain) addChainToHermes( bz, err = tr.target.ExecCommand("hermes", "keys", "add", - "--chain", string(tr.testConfig.chainConfigs[action.Chain].ChainId), + "--chain", string(tr.testConfig.ChainConfigs[action.Chain].ChainId), "--mnemonic-file", "/root/.hermes/mnemonic.txt", ).CombinedOutput() if err != nil { @@ -1671,7 +1361,7 @@ func (tr Chain) addIbcConnection( action AddIbcConnectionAction, verbose bool, ) { - if !tr.testConfig.useGorelayer { + if !tr.testConfig.UseGorelayer { tr.addIbcConnectionHermes(action, verbose) } else { tr.addIbcConnectionGorelayer(action, verbose) @@ -1697,8 +1387,8 @@ func (tr Chain) addIbcConnectionGorelayer( //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. newPathCommand := tr.target.ExecCommand("rly", "paths", "add", - string(tr.testConfig.chainConfigs[action.ChainA].ChainId), - string(tr.testConfig.chainConfigs[action.ChainB].ChainId), + string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), + string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), pathName, "--file", pathConfigFileName, ) @@ -1736,8 +1426,8 @@ func (tr Chain) createIbcClientsHermes( ) { cmd := tr.target.ExecCommand("hermes", "create", "connection", - "--a-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), - "--b-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), + "--a-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), + "--b-chain", string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), ) cmdReader, err := cmd.StdoutPipe() @@ -1772,7 +1462,7 @@ func (tr Chain) addIbcConnectionHermes( ) { cmd := tr.target.ExecCommand("hermes", "create", "connection", - "--a-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--a-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), "--a-client", "07-tendermint-"+fmt.Sprint(action.ClientA), "--b-client", "07-tendermint-"+fmt.Sprint(action.ClientB), ) @@ -1819,7 +1509,7 @@ func (tr Chain) startRelayer( action StartRelayerAction, verbose bool, ) { - if tr.testConfig.useGorelayer { + if tr.testConfig.UseGorelayer { tr.startGorelayer(action, verbose) } else { tr.startHermes(action, verbose) @@ -1862,7 +1552,7 @@ func (tr Chain) addIbcChannel( action AddIbcChannelAction, verbose bool, ) { - if tr.testConfig.useGorelayer { + if tr.testConfig.UseGorelayer { tr.addIbcChannelGorelayer(action, verbose) } else { tr.addIbcChannelHermes(action, verbose) @@ -1879,7 +1569,7 @@ func (tr Chain) addIbcChannelGorelayer( pathName, "--src-port", action.PortA, "--dst-port", action.PortB, - "--version", tr.testConfig.containerConfig.CcvVersion, + "--version", tr.testConfig.ContainerConfig.CcvVersion, "--order", action.Order, "--debug", ) @@ -1894,12 +1584,12 @@ func (tr Chain) addIbcChannelHermes( // otherwise, use the provided version schema (usually it is ICS20-1 for IBC transfer) chanVersion := action.Version if chanVersion == "" { - chanVersion = tr.testConfig.containerConfig.CcvVersion + chanVersion = tr.testConfig.ContainerConfig.CcvVersion } cmd := tr.target.ExecCommand("hermes", "create", "channel", - "--a-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--a-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), "--a-connection", "connection-"+fmt.Sprint(action.ConnectionA), "--a-port", action.PortA, "--b-port", action.PortB, @@ -1952,13 +1642,13 @@ func (tr Chain) transferChannelComplete( action TransferChannelCompleteAction, verbose bool, ) { - if tr.testConfig.useGorelayer { + if tr.testConfig.UseGorelayer { log.Fatal("transferChannelComplete is not implemented for rly") } chanOpenTryCmd := tr.target.ExecCommand("hermes", "tx", "chan-open-try", - "--dst-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), - "--src-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--dst-chain", string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), + "--src-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), "--dst-port", action.PortB, "--src-port", action.PortA, @@ -1969,8 +1659,8 @@ func (tr Chain) transferChannelComplete( chanOpenAckCmd := tr.target.ExecCommand("hermes", "tx", "chan-open-ack", - "--dst-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), - "--src-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), + "--dst-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), + "--src-chain", string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), "--dst-port", action.PortA, "--src-port", action.PortB, @@ -1982,8 +1672,8 @@ func (tr Chain) transferChannelComplete( chanOpenConfirmCmd := tr.target.ExecCommand("hermes", "tx", "chan-open-confirm", - "--dst-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), - "--src-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--dst-chain", string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), + "--src-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), "--dst-port", action.PortB, "--src-port", action.PortA, @@ -2004,7 +1694,7 @@ func (tr Chain) relayPackets( action RelayPacketsAction, verbose bool, ) { - if tr.testConfig.useGorelayer { + if tr.testConfig.UseGorelayer { tr.relayPacketsGorelayer(action, verbose) } else { tr.relayPacketsHermes(action, verbose) @@ -2051,7 +1741,7 @@ func (tr Chain) relayPacketsHermes( tr.waitBlocks(action.ChainB, 3, 90*time.Second) // hermes clear packets ibc0 transfer channel-13 cmd := tr.target.ExecCommand("hermes", "clear", "packets", - "--chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), "--port", action.Port, "--channel", "channel-"+fmt.Sprint(action.Channel), ) @@ -2089,18 +1779,11 @@ func (tr Chain) relayRewardPacketsToProvider( tr.waitBlocks(action.ProviderChain, 1, 10*time.Second) } -type DelegateTokensAction struct { - Chain ChainID - From ValidatorID - To ValidatorID - Amount uint -} - -func (tr Chain) delegateTokens( +func (tr Chain) DelegateTokens( action DelegateTokensAction, verbose bool, ) { - toValCfg := tr.testConfig.validatorConfigs[action.To] + toValCfg := tr.testConfig.ValidatorConfigs[action.To] validatorAddress := toValCfg.ValoperAddress if action.Chain != ChainID("provi") { // use binary with Bech32Prefix set to ConsumerAccountPrefix @@ -2112,13 +1795,13 @@ func (tr Chain) delegateTokens( } } - cmd := tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + cmd := tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "staking", "delegate", validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--node`, tr.getValidatorNode(action.Chain, action.From), `--keyring-backend`, `test`, @@ -2138,18 +1821,11 @@ func (tr Chain) delegateTokens( tr.waitBlocks(action.Chain, 2, 10*time.Second) } -type UnbondTokensAction struct { - Chain ChainID - Sender ValidatorID - UnbondFrom ValidatorID - Amount uint -} - -func (tr Chain) unbondTokens( +func (tr Chain) UnbondTokens( action UnbondTokensAction, verbose bool, ) { - unbondFromValCfg := tr.testConfig.validatorConfigs[action.UnbondFrom] + unbondFromValCfg := tr.testConfig.ValidatorConfigs[action.UnbondFrom] validatorAddress := unbondFromValCfg.ValoperAddress if action.Chain != ChainID("provi") { // use binary with Bech32Prefix set to ConsumerAccountPrefix @@ -2161,32 +1837,33 @@ func (tr Chain) unbondTokens( } } - cmd := tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + cmd := tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "staking", "unbond", validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.Sender), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.Sender), `--node`, tr.getValidatorNode(action.Chain, action.Sender), `--gas`, "900000", `--keyring-backend`, `test`, + `-o`, `json`, `-y`, ) - if verbose { - fmt.Println("unbond cmd:", cmd.String()) - } - bz, err := cmd.CombinedOutput() if err != nil { - log.Fatal(err, "\n", string(bz)) + log.Fatal(err, "cmd '%s' failed with:\n", string(bz)) + } + if verbose { + fmt.Printf("unbond cmd: '%s' returned:\n'%s'", cmd.String(), string(bz)) } // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(action.Chain, 2, 20*time.Second) + tr.waitForTx(action.Chain, bz, 20*time.Second) + //tr.waitBlocks(action.Chain, 2, 20*time.Second) } type CancelUnbondTokensAction struct { @@ -2200,8 +1877,8 @@ func (tr Chain) cancelUnbondTokens( action CancelUnbondTokensAction, verbose bool, ) { - valCfg := tr.testConfig.validatorConfigs[action.Validator] - delCfg := tr.testConfig.validatorConfigs[action.Delegator] + valCfg := tr.testConfig.ValidatorConfigs[action.Validator] + delCfg := tr.testConfig.ValidatorConfigs[action.Delegator] validatorAddress := valCfg.ValoperAddress delegatorAddress := delCfg.DelAddress if action.Chain != ChainID("provi") { @@ -2221,7 +1898,7 @@ func (tr Chain) cancelUnbondTokens( } // get creation-height from state - cmd := tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + cmd := tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "q", "staking", "unbonding-delegation", delegatorAddress, validatorAddress, @@ -2242,13 +1919,13 @@ func (tr Chain) cancelUnbondTokens( log.Fatal("invalid creation height") } - cmd = tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + cmd = tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "staking", "cancel-unbond", validatorAddress, fmt.Sprint(action.Amount)+`stake`, fmt.Sprint(creationHeight), `--from`, `validator`+fmt.Sprint(action.Delegator), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.Delegator), `--node`, tr.getValidatorNode(action.Chain, action.Delegator), `--gas`, "900000", @@ -2279,8 +1956,8 @@ type RedelegateTokensAction struct { } func (tr Chain) redelegateTokens(action RedelegateTokensAction, verbose bool) { - srcCfg := tr.testConfig.validatorConfigs[action.Src] - dstCfg := tr.testConfig.validatorConfigs[action.Dst] + srcCfg := tr.testConfig.ValidatorConfigs[action.Src] + dstCfg := tr.testConfig.ValidatorConfigs[action.Dst] redelegateSrc := srcCfg.ValoperAddress redelegateDst := dstCfg.ValoperAddress if action.Chain != ChainID("provi") { @@ -2300,14 +1977,14 @@ func (tr Chain) redelegateTokens(action RedelegateTokensAction, verbose bool) { } cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "staking", "redelegate", redelegateSrc, redelegateDst, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.TxSender), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.TxSender), `--node`, tr.getValidatorNode(action.Chain, action.TxSender), // Need to manually set gas limit past default (200000), since redelegate has a lot of operations @@ -2366,7 +2043,7 @@ func (tr Chain) setValidatorDowntime(chain ChainID, validator ValidatorID, down, lastArg = "up" } - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { // send set_signing_status either to down or up for validator validatorPrivateKeyAddress := tr.GetValidatorPrivateKeyAddress(chain, validator) @@ -2400,13 +2077,13 @@ func (tr Chain) setValidatorDowntime(chain ChainID, validator ValidatorID, down, func (tr Chain) GetValidatorPrivateKeyAddress(chain ChainID, validator ValidatorID) string { var validatorPrivateKeyAddress string if chain == ChainID("provi") { - validatorPrivateKeyAddress = tr.getValidatorKeyAddressFromString(tr.testConfig.validatorConfigs[validator].PrivValidatorKey) + validatorPrivateKeyAddress = tr.getValidatorKeyAddressFromString(tr.testConfig.ValidatorConfigs[validator].PrivValidatorKey) } else { var valAddressString string - if tr.testConfig.validatorConfigs[validator].UseConsumerKey { - valAddressString = tr.testConfig.validatorConfigs[validator].ConsumerPrivValidatorKey + if tr.testConfig.ValidatorConfigs[validator].UseConsumerKey { + valAddressString = tr.testConfig.ValidatorConfigs[validator].ConsumerPrivValidatorKey } else { - valAddressString = tr.testConfig.validatorConfigs[validator].PrivValidatorKey + valAddressString = tr.testConfig.ValidatorConfigs[validator].PrivValidatorKey } validatorPrivateKeyAddress = tr.getValidatorKeyAddressFromString(valAddressString) } @@ -2424,16 +2101,17 @@ func (tr Chain) unjailValidator(action UnjailValidatorAction, verbose bool) { tr.WaitTime(65 * time.Second) cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Provider].BinaryName, + tr.testConfig.ChainConfigs[action.Provider].BinaryName, "tx", "slashing", "unjail", // Validator is sender here `--from`, `validator`+fmt.Sprint(action.Validator), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Provider].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Provider].ChainId), `--home`, tr.getValidatorHome(action.Provider, action.Validator), `--node`, tr.getValidatorNode(action.Provider, action.Validator), `--gas`, "900000", `--keyring-backend`, `test`, `--keyring-dir`, tr.getValidatorHome(action.Provider, action.Validator), + `-o`, `json`, `-y`, ) @@ -2446,9 +2124,10 @@ func (tr Chain) unjailValidator(action UnjailValidatorAction, verbose bool) { log.Fatal(err, "\n", string(bz)) } + tr.waitForTx(action.Provider, bz, time.Minute) // wait for 1 blocks to make sure that tx got included // in a block and packets committed before proceeding - tr.waitBlocks(action.Provider, 2, time.Minute) + tr.waitBlocks(action.Provider, 1, time.Minute) } type RegisterRepresentativeAction struct { @@ -2481,7 +2160,7 @@ func (tr Chain) registerRepresentative( go func(val ValidatorID, stake uint) { defer wg.Done() - pubKeycmd := tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + pubKeycmd := tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tendermint", "show-validator", `--home`, tr.getValidatorHome(action.Chain, val), ) @@ -2503,7 +2182,7 @@ func (tr Chain) registerRepresentative( log.Fatalf("Failed writing consumer genesis to file: %v", err) } - containerInstance := tr.testConfig.containerConfig.InstanceName + containerInstance := tr.testConfig.ContainerConfig.ContainerName targetFile := fmt.Sprintf("/tmp/%s", fileName) sourceFile := file.Name() //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -2514,11 +2193,11 @@ func (tr Chain) registerRepresentative( log.Fatal(err, "\n", string(writeResult)) } - cmd := tr.target.ExecCommand(tr.testConfig.chainConfigs[action.Chain].BinaryName, + cmd := tr.target.ExecCommand(tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "staking", "create-validator", targetFile, `--from`, `validator`+fmt.Sprint(val), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, val), `--node`, tr.getValidatorNode(action.Chain, val), `--keyring-backend`, `test`, @@ -2577,10 +2256,10 @@ func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenoms // CHANGE REWARDS DENOM PROPOSAL cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-proposal", proposalFile, `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--chain-id`, string(tr.testConfig.ChainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, action.From), `--gas`, `900000`, `--node`, tr.getValidatorNode(action.Chain, action.From), @@ -2606,7 +2285,7 @@ func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenoms } func (tr Chain) submitChangeRewardDenomsLegacyProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { - providerChain := tr.testConfig.chainConfigs[action.Chain] + providerChain := tr.testConfig.ChainConfigs[action.Chain] prop := client.ChangeRewardDenomsProposalJSON{ Summary: "Change reward denoms", @@ -2674,8 +2353,8 @@ func (tr Chain) invokeDoublesignSlash( action DoublesignSlashAction, verbose bool, ) { - if !tr.testConfig.useCometmock { - chainConfig := tr.testConfig.chainConfigs[action.Chain] + if !tr.testConfig.UseCometmock { + chainConfig := tr.testConfig.ChainConfigs[action.Chain] doubleSignScript := tr.target.GetTestScriptPath(false, "cause-doublesign.sh") bz, err := tr.target.ExecCommand("/bin/bash", doubleSignScript, chainConfig.BinaryName, string(action.Validator), @@ -2759,7 +2438,7 @@ func (tr Chain) lightClientAttack( chain ChainID, attackType LightClientAttackType, ) { - if !tr.testConfig.useCometmock { + if !tr.testConfig.UseCometmock { log.Fatal("light client attack is only supported with CometMock") } validatorPrivateKeyAddress := tr.GetValidatorPrivateKeyAddress(chain, validator) @@ -2773,27 +2452,36 @@ func (tr Chain) lightClientAttack( tr.waitBlocks(chain, 1, 10*time.Second) } -func (tr Chain) assignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, verbose bool) { - valCfg := tr.testConfig.validatorConfigs[action.Validator] +func (tr Chain) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, verbose bool) { + valCfg := tr.testConfig.ValidatorConfigs[action.Validator] + chainCfg := tr.testConfig.ChainConfigs // 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 - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { gas = "9000000" } - bz, err := tr.target.AssignConsumerPubKey(action, gas, + consumerId := string(chainCfg[action.Chain].ConsumerId) + + bz, err := tr.target.AssignConsumerPubKey( + consumerId, + action.ConsumerPubkey, + action.Validator, + gas, tr.getValidatorHome(ChainID("provi"), action.Validator), tr.getValidatorNode(ChainID("provi"), action.Validator), verbose, ) - if err != nil && !action.ExpectError { + if err == nil { + tr.waitForTx(tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, bz, 30*time.Second) + } else if !action.ExpectError { log.Fatalf("unexpected error during key assignment - output: %s, err: %s", string(bz), err) } - if action.ExpectError && !tr.testConfig.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if action.ExpectError && !tr.testConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore if err == nil || !strings.Contains(string(bz), action.ExpectedError) { log.Fatalf("expected error not raised: expected: '%s', got '%s'", action.ExpectedError, (bz)) } @@ -2806,12 +2494,12 @@ func (tr Chain) assignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, verb // node was started with provider key // we swap the nodes's keys for consumer keys and restart it if action.ReconfigureNode { - isConsumer := tr.testConfig.chainConfigs[action.Chain].BinaryName != "interchain-security-pd" + isConsumer := tr.testConfig.ChainConfigs[action.Chain].BinaryName != "interchain-security-pd" reconfigureScript := tr.target.GetTestScriptPath(isConsumer, "reconfigure-node.sh") configureNodeCmd := tr.target.ExecCommand("/bin/bash", - reconfigureScript, tr.testConfig.chainConfigs[action.Chain].BinaryName, + reconfigureScript, tr.testConfig.ChainConfigs[action.Chain].BinaryName, string(action.Validator), string(action.Chain), - tr.testConfig.chainConfigs[action.Chain].IpPrefix, valCfg.IpSuffix, + tr.testConfig.ChainConfigs[action.Chain].IpPrefix, valCfg.IpSuffix, valCfg.ConsumerMnemonic, valCfg.ConsumerPrivValidatorKey, valCfg.ConsumerNodeKey, ) @@ -2850,11 +2538,13 @@ func (tr Chain) assignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, verb // @POfftermatt I am currently using this for downtime slashing with cometmock // (I need to find the currently used validator key address)Í valCfg.UseConsumerKey = true - tr.testConfig.validatorConfigs[action.Validator] = valCfg + tr.testConfig.ValidatorConfigs[action.Validator] = valCfg } - // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) + if !action.ExpectError { + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitForTx(e2e.ChainID("provi"), bz, 30*time.Second) + } } // SlashMeterReplenishmentAction polls the slash meter on provider until value is achieved @@ -2934,7 +2624,7 @@ func (tr Chain) detectConsumerEvidence( useRelayer bool, verbose bool, ) { - chainConfig := tr.testConfig.chainConfigs[action.Chain] + chainConfig := tr.testConfig.ChainConfigs[action.Chain] // the Hermes relayer doesn't support evidence handling for Permissionless ICS yet // TODO: @Simon refactor once https://github.com/informalsystems/hermes/pull/4182 is merged. if useRelayer { @@ -2947,7 +2637,7 @@ func (tr Chain) detectConsumerEvidence( tr.waitBlocks("provi", 10, 2*time.Minute) } else { // detect the evidence on the consumer chain - consumerBinaryName := tr.testConfig.chainConfigs[action.Chain].BinaryName + consumerBinaryName := tr.testConfig.ChainConfigs[action.Chain].BinaryName // get the infraction height by querying the SDK evidence module of the consumer timeout := time.Now().Add(30 * time.Second) @@ -3045,12 +2735,12 @@ func (tr Chain) detectConsumerEvidence( gas := "auto" submitEquivocation := fmt.Sprintf( `%s tx provider submit-consumer-double-voting %s %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].ConsumerId), + tr.testConfig.ChainConfigs[ChainID("provi")].BinaryName, + string(tr.testConfig.ChainConfigs[action.Chain].ConsumerId), evidencePath, headerPath, action.Submitter, - tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Submitter), tr.getValidatorNode(ChainID("provi"), action.Submitter), gas, @@ -3088,17 +2778,17 @@ 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 - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { gas = "9000000" } // Use: "opt-in [consumer-chain-id] [consumer-pubkey]", 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].ConsumerId), + tr.testConfig.ChainConfigs[ChainID("provi")].BinaryName, + string(tr.testConfig.ChainConfigs[action.Chain].ConsumerId), action.Validator, - tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Validator), tr.getValidatorNode(ChainID("provi"), action.Validator), gas, @@ -3118,7 +2808,7 @@ func (tr Chain) optIn(action OptInAction, verbose bool) { log.Fatal(err, "\n", string(bz)) } - if action.ExpectError && !tr.testConfig.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if action.ExpectError && !tr.testConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore if err != nil && verbose { fmt.Printf("got error during opt in | err: %s | output: %s \n", err, string(bz)) } @@ -3145,17 +2835,17 @@ 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 - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { gas = "9000000" } // Use: "opt-out [consumer-chain-id]", 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].ConsumerId), + tr.testConfig.ChainConfigs[ChainID("provi")].BinaryName, + string(tr.testConfig.ChainConfigs[action.Chain].ConsumerId), action.Validator, - tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Validator), tr.getValidatorNode(ChainID("provi"), action.Validator), gas, @@ -3204,11 +2894,11 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction // 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 - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { gas = "9000000" } - consumerId := string(tr.testConfig.chainConfigs[action.Chain].ConsumerId) + consumerId := string(tr.testConfig.ChainConfigs[action.Chain].ConsumerId) if action.ConsumerID != "" { consumerId = string(action.ConsumerID) } @@ -3216,11 +2906,11 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction // 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, + tr.testConfig.ChainConfigs[ChainID("provi")].BinaryName, consumerId, action.CommissionRate, action.Validator, - tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Validator), tr.getValidatorNode(ChainID("provi"), action.Validator), gas, @@ -3240,7 +2930,7 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction log.Fatalf("unexpected error during commssion rate set - output: %s, err: %s", string(bz), err) } - if action.ExpectError && !tr.testConfig.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if action.ExpectError && !tr.testConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore if err == nil || !strings.Contains(string(bz), action.ExpectedError) { log.Fatalf("expected error not raised: expected: '%s', got '%s'", action.ExpectedError, (bz)) } @@ -3250,7 +2940,7 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction } } - if !tr.testConfig.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if !tr.testConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore if err != nil && verbose { fmt.Printf("got error during commssion rate set | err: %s | output: %s \n", err, string(bz)) } @@ -3267,11 +2957,11 @@ func (tr Chain) setConsumerCommissionRate(action SetConsumerCommissionRateAction // information in the testrun that stores how much each chain has waited, to keep times in sync. // Be careful that all functions calling WaitTime should therefore also take a pointer to the TestConfig. func (tr *Chain) WaitTime(duration time.Duration) { - if !tr.testConfig.useCometmock { + if !tr.testConfig.UseCometmock { time.Sleep(duration) } else { - tr.testConfig.timeOffset += duration - for chain, running := range tr.testConfig.runningChains { + tr.testConfig.TimeOffset += duration + for chain, running := range tr.testConfig.RunningChains { if !running { continue } @@ -3294,31 +2984,6 @@ func (tr Chain) AdvanceTimeForChain(chain ChainID, duration time.Duration) { tr.waitBlocks(chain, 1, time.Minute) } -func (tr Commands) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) { - 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.chainConfigs[ChainID("provi")].BinaryName, - string(tr.chainConfigs[action.Chain].ConsumerId), - action.ConsumerPubkey, - action.Validator, - tr.chainConfigs[ChainID("provi")].ChainId, - home, - node, - gas, - ) - - cmd := tr.target.ExecCommand( - "/bin/bash", "-c", - assignKey, - ) - - if verbose { - fmt.Println("assignConsumerPubKey cmd:", cmd.String()) - } - - return cmd.CombinedOutput() -} - type CreateIbcClientAction struct { ChainA ChainID ChainB ChainID @@ -3330,8 +2995,8 @@ func (tr Chain) createIbcClientHermes( ) { cmd := tr.target.ExecCommand("hermes", "create", "client", - "--host-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), - "--reference-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), + "--host-chain", string(tr.testConfig.ChainConfigs[action.ChainA].ChainId), + "--reference-chain", string(tr.testConfig.ChainConfigs[action.ChainB].ChainId), "--trusting-period", "1200000s", ) @@ -3381,13 +3046,13 @@ func (tr Chain) transferIbcToken( `%s tx ibc-transfer transfer transfer \ %s %s %s --memo %q --from validator%s --chain-id %s \ --home %s --node %s --gas %s --keyring-backend test -y -o json`, - tr.testConfig.chainConfigs[action.Chain].BinaryName, + tr.testConfig.ChainConfigs[action.Chain].BinaryName, "channel-"+fmt.Sprint(action.Channel), action.DstAddr, fmt.Sprint(action.Amount)+`stake`, action.Memo, action.From, - string(tr.testConfig.chainConfigs[action.Chain].ChainId), + string(tr.testConfig.ChainConfigs[action.Chain].ChainId), tr.getValidatorHome(action.Chain, action.From), tr.getValidatorNode(action.Chain, action.From), gas, diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go index 034875deb9..bb4ce5912b 100644 --- a/tests/e2e/actions_consumer_misbehaviour.go +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -19,12 +19,12 @@ type ForkConsumerChainAction struct { } func (tc Chain) forkConsumerChain(action ForkConsumerChainAction, verbose bool) { - valCfg := tc.testConfig.validatorConfigs[action.Validator] + valCfg := tc.testConfig.ValidatorConfigs[action.Validator] configureNodeCmd := tc.target.ExecCommand("/bin/bash", - "/testnet-scripts/fork-consumer.sh", tc.testConfig.chainConfigs[action.ConsumerChain].BinaryName, + "/testnet-scripts/fork-consumer.sh", tc.testConfig.ChainConfigs[action.ConsumerChain].BinaryName, string(action.Validator), string(action.ConsumerChain), - tc.testConfig.chainConfigs[action.ConsumerChain].IpPrefix, - tc.testConfig.chainConfigs[action.ProviderChain].IpPrefix, + tc.testConfig.ChainConfigs[action.ConsumerChain].IpPrefix, + tc.testConfig.ChainConfigs[action.ProviderChain].IpPrefix, valCfg.Mnemonic, action.RelayerConfig, ) @@ -103,7 +103,7 @@ type SubmitConsumerMisbehaviourAction struct { } func (tr Chain) submitConsumerMisbehaviour(action SubmitConsumerMisbehaviourAction, verbose bool) { - consumerBinaryName := tr.testConfig.chainConfigs[action.FromChain].BinaryName + consumerBinaryName := tr.testConfig.ChainConfigs[action.FromChain].BinaryName // retrieve a trusted height of the consumer client from the provider trustedHeight, _ := tr.target.GetTrustedHeight(action.ToChain, action.ClientID, 2) @@ -134,7 +134,7 @@ func (tr Chain) submitConsumerMisbehaviour(action SubmitConsumerMisbehaviourActi cmd = tr.target.ExecCommand( consumerBinaryName, "query", "ibc", "client", "header", "--height", strconv.Itoa(int(currHeight)), - `--node`, fmt.Sprintf("tcp://%s.252:26658", tr.testConfig.chainConfigs[action.FromChain].IpPrefix), + `--node`, fmt.Sprintf("tcp://%s.252:26658", tr.testConfig.ChainConfigs[action.FromChain].IpPrefix), `-o`, `json`, ) @@ -148,7 +148,7 @@ func (tr Chain) submitConsumerMisbehaviour(action SubmitConsumerMisbehaviourActi cmd = tr.target.ExecCommand( consumerBinaryName, "query", "ibc", "client", "header", "--height", strconv.Itoa(int(trustedHeight+1)), - `--node`, fmt.Sprintf("tcp://%s.252:26658", tr.testConfig.chainConfigs[action.FromChain].IpPrefix), + `--node`, fmt.Sprintf("tcp://%s.252:26658", tr.testConfig.ChainConfigs[action.FromChain].IpPrefix), `-o`, `json`, ) @@ -178,11 +178,11 @@ func (tr Chain) submitConsumerMisbehaviour(action SubmitConsumerMisbehaviourActi gas := "auto" submitMisb := fmt.Sprintf( `%s tx provider submit-consumer-misbehaviour %s %s --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y`, - tr.testConfig.chainConfigs[ChainID("provi")].BinaryName, - string(tr.testConfig.chainConfigs[action.FromChain].ConsumerId), + tr.testConfig.ChainConfigs[ChainID("provi")].BinaryName, + string(tr.testConfig.ChainConfigs[action.FromChain].ConsumerId), misbPath, action.Submitter, - tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.testConfig.ChainConfigs[ChainID("provi")].ChainId, tr.getValidatorHome(ChainID("provi"), action.Submitter), tr.getValidatorNode(ChainID("provi"), action.Submitter), gas, diff --git a/tests/e2e/actions_sovereign_chain.go b/tests/e2e/actions_sovereign_chain.go index c789f72ec5..a9d9f861e0 100644 --- a/tests/e2e/actions_sovereign_chain.go +++ b/tests/e2e/actions_sovereign_chain.go @@ -6,22 +6,17 @@ import ( "fmt" "log" "time" -) -type StartSovereignChainAction struct { - Chain ChainID - Validators []StartChainValidator - // Genesis changes specific to this action, appended to genesis changes defined in chain config - GenesisChanges string -} + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" +) // calls a simplified startup script (start-sovereign.sh) and runs a validator node // upgrades are simpler with a single validator node since only one node needs to be upgraded func (tr Chain) startSovereignChain( - action StartSovereignChainAction, + action e2e.StartSovereignChainAction, verbose bool, ) { - chainConfig := tr.testConfig.chainConfigs["sover"] + chainConfig := tr.testConfig.ChainConfigs["sover"] type jsonValAttrs struct { Mnemonic string `json:"mnemonic"` Allocation string `json:"allocation"` @@ -39,18 +34,18 @@ func (tr Chain) startSovereignChain( var validators []jsonValAttrs for _, val := range action.Validators { validators = append(validators, jsonValAttrs{ - Mnemonic: tr.testConfig.validatorConfigs[val.Id].Mnemonic, - NodeKey: tr.testConfig.validatorConfigs[val.Id].NodeKey, + Mnemonic: tr.testConfig.ValidatorConfigs[val.Id].Mnemonic, + NodeKey: tr.testConfig.ValidatorConfigs[val.Id].NodeKey, ValId: fmt.Sprint(val.Id), - PrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].PrivValidatorKey, + PrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].PrivValidatorKey, Allocation: fmt.Sprint(val.Allocation) + "stake", Stake: fmt.Sprint(val.Stake) + "stake", - IpSuffix: tr.testConfig.validatorConfigs[val.Id].IpSuffix, + IpSuffix: tr.testConfig.ValidatorConfigs[val.Id].IpSuffix, - ConsumerMnemonic: tr.testConfig.validatorConfigs[val.Id].ConsumerMnemonic, - ConsumerPrivValidatorKey: tr.testConfig.validatorConfigs[val.Id].ConsumerPrivValidatorKey, + ConsumerMnemonic: tr.testConfig.ValidatorConfigs[val.Id].ConsumerMnemonic, + ConsumerPrivValidatorKey: tr.testConfig.ValidatorConfigs[val.Id].ConsumerPrivValidatorKey, // if true node will be started with consumer key for each consumer chain - StartWithConsumerKey: tr.testConfig.validatorConfigs[val.Id].UseConsumerKey, + StartWithConsumerKey: tr.testConfig.ValidatorConfigs[val.Id].UseConsumerKey, }) } @@ -71,7 +66,7 @@ func (tr Chain) startSovereignChain( testScriptPath := tr.target.GetTestScriptPath(isConsumer, "start-sovereign.sh") cmd := tr.target.ExecCommand("/bin/bash", testScriptPath, chainConfig.BinaryName, string(vals), string(chainConfig.ChainId), chainConfig.IpPrefix, genesisChanges, - tr.testConfig.tendermintConfigOverride) + tr.testConfig.TendermintConfigOverride) cmdReader, err := cmd.StdoutPipe() if err != nil { @@ -113,7 +108,7 @@ type UpgradeProposalAction struct { func (tr *Chain) submitUpgradeProposal(action UpgradeProposalAction, verbose bool) { // Get authority address - binary := tr.testConfig.chainConfigs[ChainID("sover")].BinaryName + binary := tr.testConfig.ChainConfigs[ChainID("sover")].BinaryName cmd := tr.target.ExecCommand(binary, "query", "upgrade", "authority", "--node", tr.getValidatorNode(ChainID("sover"), action.Proposer), @@ -171,7 +166,7 @@ func (tr *Chain) submitUpgradeProposal(action UpgradeProposalAction, verbose boo "--gas", "900000", "--from", "validator"+string(action.Proposer), "--keyring-backend", "test", - "--chain-id", string(tr.testConfig.chainConfigs[ChainID("sover")].ChainId), + "--chain-id", string(tr.testConfig.ChainConfigs[ChainID("sover")].ChainId), "--home", tr.getValidatorHome(ChainID("sover"), action.Proposer), "--node", tr.getValidatorNode(ChainID("sover"), action.Proposer), "-y") diff --git a/tests/e2e/builder.go b/tests/e2e/builder.go index e9e0f00d5a..212c8766e0 100644 --- a/tests/e2e/builder.go +++ b/tests/e2e/builder.go @@ -245,7 +245,8 @@ func buildDockerImage(version string, targetCfg TargetConfig, noCache bool) (str out, err := cmd.CombinedOutput() if err != nil && !noCache { // Retry image creation from pristine state by enforcing --no-cache - log.Printf("Image creation failed '%v'. Re-trying without cache!", err) + log.Printf("Image creation failed '%v'.\n%s\nRe-trying without cache!", + err, string(out)) return buildDockerImage(version, targetCfg, true) } if err != nil { diff --git a/tests/e2e/commands.go b/tests/e2e/commands.go new file mode 100644 index 0000000000..4c9b54ab2b --- /dev/null +++ b/tests/e2e/commands.go @@ -0,0 +1,948 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "log" + "os/exec" + "regexp" + "sort" + "strconv" + "strings" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/kylelemons/godebug/pretty" + "github.com/tidwall/gjson" + "gopkg.in/yaml.v2" + + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" +) + +// Commands contains a collection of commands which can executed +// on the current implementation of the provider/consumer +// Note: for different versions see e2e/v* +type Commands struct { + Verbose bool + ContainerConfig *e2e.ContainerConfig + ValidatorConfigs map[ValidatorID]ValidatorConfig + ChainConfigs map[ChainID]ChainConfig + Target e2e.PlatformDriver +} + +type TmValidatorSetYaml struct { + BlockHeight string `yaml:"block_height"` + Pagination struct { + NextKey string `yaml:"next_key"` + Total string `yaml:"total"` + } `yaml:"pagination"` + Validators []struct { + Address string `yaml:"address"` + VotingPower string `yaml:"voting_power"` + PubKey ValPubKey `yaml:"pub_key"` + } +} + +type ValPubKey struct { + Value string `yaml:"value"` +} + +func (tr Commands) UseCometMock() bool { + return tr.Target.UseCometMock() +} + +func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { + return tr.Target.ExecCommand(name, arg...) +} + +func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { + return tr.Target.ExecDetachedCommand(name, args...) +} + +func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { + return tr.Target.GetTestScriptPath(isConsumer, script) +} + +func (tr Commands) GetBlockHeight(chain ChainID) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + + "query", "tendermint-validator-set", + + `--node`, tr.GetQueryNode(chain), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) + blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) + if err != nil { + log.Fatal(err) + } + + return uint(blockHeight) +} + +func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { + valCfg := tr.ValidatorConfigs[validator] + delAddresss := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + delAddresss = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delAddresss = valCfg.DelAddressOnConsumer + } + } + + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "distribution", "rewards", + delAddresss, + `--height`, fmt.Sprint(blockHeight), + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + + if *verbose { + log.Println("getting rewards for chain: ", chain, " validator: ", validator, " blockHeight: ", blockHeight) + log.Println(cmd) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("running cmd: ", cmd) + log.Fatal("failed getting rewards: ", err, "\n", string(bz)) + } + + denomCondition := fmt.Sprintf(`total.#(%%"*%s*")`, denom) + amount := strings.Split(gjson.Get(string(bz), denomCondition).String(), denom)[0] + + res := float64(0) + if amount != "" { + res, err = strconv.ParseFloat(amount, 64) + if err != nil { + log.Fatal("failed parsing consumer reward:", err) + } + } + + return res +} + +// interchain-securityd query gov proposals +func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { + noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) + + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "gov", "proposal", + fmt.Sprint(proposal), + `--node`, tr.GetQueryNode(chain), + `-o`, `json`) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("Error getting governance proposal", proposal, "\n\t cmd: ", cmd, "\n\t err:", err, "\n\t out: ", string(bz)) + } + + prop := TextProposal{} + propRaw := string(bz) + if err != nil { + if noProposalRegex.Match(bz) { + return prop + } + + log.Fatal(err, "\n", propRaw) + } + + // 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`) + + // for current (>= v47) prop types submitted using "tx submit-proposal" + if propType == "" { + propType = gjson.Get(propRaw, `proposal.messages.0.type`).String() + rawContent = gjson.Get(propRaw, `proposal.messages.0.value`) + } + + title := gjson.Get(propRaw, `proposal.title`).String() + deposit := gjson.Get(propRaw, `proposal.total_deposit.#(denom=="stake").amount`).Uint() + status := gjson.Get(propRaw, `proposal.status`).String() + + switch propType { + case "/cosmos.gov.v1beta1.TextProposal": + title := rawContent.Get("title").String() + description := rawContent.Get("description").String() + + return TextProposal{ + Deposit: uint(deposit), + Status: status, + 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) + + var chain ChainID + for i, conf := range tr.ChainConfigs { + if string(conf.ChainId) == chainId { + chain = i + break + } + } + + return ConsumerAdditionProposal{ + Deposit: uint(deposit), + Status: status, + Chain: chain, + SpawnTime: int(spawnTime.Milliseconds()), + InitialHeight: clienttypes.Height{ + RevisionNumber: rawContent.Get("initial_height.revision_number").Uint(), + RevisionHeight: rawContent.Get("initial_height.revision_height").Uint(), + }, + } + case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": + case "cosmos-sdk/MsgSoftwareUpgrade": + height := rawContent.Get("plan.height").Uint() + title := rawContent.Get("plan.name").String() + return UpgradeProposal{ + Deposit: uint(deposit), + Status: status, + UpgradeHeight: height, + Title: title, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + } + case "/interchain_security.ccv.provider.v1.MsgRemoveConsumer": + consumerId := rawContent.Get("consumer_id").String() + + var chain ChainID + for i, conf := range tr.ChainConfigs { + if string(conf.ConsumerId) == consumerId { + chain = i + break + } + } + + return ConsumerRemovalProposal{ + Deposit: uint(deposit), + Status: status, + Chain: chain, + } + case "/ibc.applications.transfer.v1.MsgUpdateParams": + var params IBCTransferParams + if err := json.Unmarshal([]byte(rawContent.Get("params").String()), ¶ms); err != nil { + log.Fatal("cannot unmarshal ibc-transfer params: ", err, "\n", propRaw) + } + + return IBCTransferParamsProposal{ + Deposit: uint(deposit), + Status: status, + Title: title, + Params: params, + } + + case "/interchain_security.ccv.provider.v1.MsgConsumerModification": + chainId := rawContent.Get("chain_id").String() + + var chain ChainID + for i, conf := range tr.ChainConfigs { + if string(conf.ChainId) == chainId { + chain = i + break + } + } + + return ConsumerModificationProposal{ + Deposit: uint(deposit), + Status: status, + Chain: chain, + } + case "/cosmos.params.v1beta1.ParameterChangeProposal": + return ParamsProposal{ + Deposit: uint(deposit), + Status: status, + Subspace: gjson.Get(propRaw, `messages.0.content.changes.0.subspace`).String(), + Key: gjson.Get(propRaw, `messages.0.content.changes.0.key`).String(), + Value: gjson.Get(propRaw, `messages.0.content.changes.0.value`).String(), + } + } + + log.Fatal("received unknown proposal type: '", propType, "', proposal JSON:", propRaw) + + return nil +} + +// TODO (mpoke) Return powers for multiple validators +func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { + if *verbose { + log.Println("getting validator power for chain: ", chain, " validator: ", validator) + } + binaryName := tr.ChainConfigs[chain].BinaryName + command := tr.Target.ExecCommand(binaryName, + + "query", "tendermint-validator-set", + + `--node`, tr.GetQueryNode(chain), + ) + bz, err := command.CombinedOutput() + if err != nil { + log.Fatalf("encountered an error when executing command '%s': %v, output: %s", command.String(), err, string(bz)) + } + + valset := TmValidatorSetYaml{} + + err = yaml.Unmarshal(bz, &valset) + if err != nil { + log.Fatalf("yaml.Unmarshal returned an error while unmarshalling validator set: %v, input: %s", err, string(bz)) + } + + total, err := strconv.Atoi(valset.Pagination.Total) + if err != nil { + log.Fatalf("strconv.Atoi returned an error while converting total for validator set: %v, input: %s, validator set: %s, src:%s", + err, valset.Pagination.Total, pretty.Sprint(valset), string(bz)) + } + + if total != len(valset.Validators) { + log.Fatalf("Total number of validators %v does not match number of validators in list %v. Probably a query pagination issue. Validator set: %v", + valset.Pagination.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) + } + + for _, val := range valset.Validators { + if chain == ChainID("provi") { + // use binary with Bech32Prefix set to ProviderAccountPrefix + if val.Address != tr.ValidatorConfigs[validator].ValconsAddress { + continue + } + } else { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if val.Address != tr.ValidatorConfigs[validator].ValconsAddressOnConsumer && + val.Address != tr.ValidatorConfigs[validator].ConsumerValconsAddress { + continue + } + } + + votingPower, err := strconv.Atoi(val.VotingPower) + if err != nil { + log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) + } + + return uint(votingPower) + } + + // Validator not in set, its validator power is zero. + return 0 +} + +func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { + valCfg := tr.ValidatorConfigs[validator] + valDelAddress := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + valDelAddress = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + valDelAddress = valCfg.DelAddressOnConsumer + } + } + + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "bank", "balances", + valDelAddress, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal("getBalance() failed: ", cmd, ": ", err, "\n", string(bz)) + } + + amount := gjson.Get(string(bz), `balances.#(denom=="stake").amount`) + + return uint(amount.Uint()) +} + +func (tr Commands) GetValStakedTokens(chain ChainID, valoperAddress string) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + + "query", "staking", "validator", + valoperAddress, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + amount := gjson.Get(string(bz), `validator.tokens`) + + return uint(amount.Uint()) +} + +func (tr Commands) GetParam(chain ChainID, param Param) string { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "params", "subspace", + param.Subspace, + param.Key, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + value := gjson.Get(string(bz), `value`) + + return value.String() +} + +func (tr Commands) GetIBCTransferParams(chain ChainID) IBCTransferParams { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "ibc-transfer", "params", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + var params IBCTransferParams + if err := json.Unmarshal(bz, ¶ms); err != nil { + log.Fatal("cannot unmarshal ibc-transfer params: ", err, "\n", string(bz)) + } + + return params +} + +// GetConsumerChains returns a list of consumer chains that're being secured by the provider chain, +// determined by querying the provider chain. +func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "provider", "list-consumer-chains", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "chains").Array() + chains := make(map[ChainID]bool) + for _, c := range arr { + phase := c.Get("phase").String() + if phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_INITIALIZED)] || + phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_REGISTERED)] || + phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_LAUNCHED)] { + id := c.Get("consumer_id").String() + for chainRef, cfg := range tr.ChainConfigs { + if cfg.ConsumerId == ConsumerID(id) { + // note: 'chainRef' is the reference the test uses and not necessarily matching chain id + chains[chainRef] = true + } + } + } + } + + return chains +} + +func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + consumerId := tr.ChainConfigs[consumerChain].ConsumerId + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "validator-consumer-key", + string(consumerId), tr.ValidatorConfigs[validator].ValconsAddress, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + addr := gjson.Get(string(bz), "consumer_address").String() + return addr +} + +func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + consumerId := tr.ChainConfigs[consumerChain].ConsumerId + + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "validator-provider-key", + string(consumerId), tr.ValidatorConfigs[validator].ConsumerValconsAddressOnProvider, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("error running ", cmd) + log.Fatal(err, "\n", string(bz)) + } + + addr := gjson.Get(string(bz), "provider_address").String() + return addr +} + +func (tr Commands) GetSlashMeter() int64 { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "throttle-state", + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + slashMeter := gjson.Get(string(bz), "slash_meter") + return slashMeter.Int() +} + +func (tr Commands) GetRegisteredConsumerRewardDenoms(chain ChainID) []string { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "registered-consumer-reward-denoms", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + denoms := gjson.Get(string(bz), "denoms").Array() + rewardDenoms := make([]string, len(denoms)) + for i, d := range denoms { + rewardDenoms[i] = d.String() + } + + return rewardDenoms +} + +func (tr Commands) GetPendingPacketQueueSize(chain ChainID) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "ccvconsumer", "throttle-state", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if !gjson.ValidBytes(bz) { + panic("invalid json response from query ccvconsumer throttle-state: " + string(bz)) + } + + packetData := gjson.Get(string(bz), "packet_data_queue").Array() + return uint(len(packetData)) +} + +// GetClientFrozenHeight returns the frozen height for a client with the given client ID +// by querying the hosting chain with the given chainID +func (tr Commands) GetClientFrozenHeight(chain ChainID, clientID string) (uint64, uint64) { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "ibc", "client", "state", clientID, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + frozenHeight := gjson.Get(string(bz), "client_state.frozen_height") + + revHeight, err := strconv.Atoi(frozenHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + revNumber, err := strconv.Atoi(frozenHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + return uint64(revNumber), uint64(revHeight) +} + +func (tr Commands) GetHasToValidate( + validatorId ValidatorID, +) []ChainID { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "provider", "has-to-validate", + tr.ValidatorConfigs[validatorId].ValconsAddress, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "consumer_ids").Array() + chains := []ChainID{} + for _, c := range arr { + for chainRef, chain := range tr.ChainConfigs { + if chain.ConsumerId == ConsumerID(c.String()) { + // we report the test chain reference which might not match the chain ID + // to support testing consumer chains with same chain ID + chains = append(chains, chainRef) + break + } + } + } + + return chains +} + +func (tr Commands) GetInflationRate( + chain ChainID, +) float64 { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "mint", "inflation", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + inflationRate := gjson.Get(string(bz), "inflation").Float() + return inflationRate +} + +func (tr Commands) GetTrustedHeight( + chain ChainID, + clientID string, + index int, +) (uint64, uint64) { + configureNodeCmd := tr.Target.ExecCommand("hermes", + "--json", "query", "client", "consensus", "--chain", string(chain), + `--client`, clientID, + ) + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + var trustedHeight gjson.Result + // iterate on the relayer's response + // and parse the command "result" + for scanner.Scan() { + out := scanner.Text() + if len(gjson.Get(out, "result").Array()) > 0 { + trustedHeight = gjson.Get(out, "result").Array()[index] + break + } + } + + revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err) + } + + revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err) + } + return uint64(revHeight), uint64(revNumber) +} + +func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "provider", "list-consumer-chains", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "chains").Array() + chains := []string{} + for _, c := range arr { + phase := c.Get("phase").String() + if phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_INITIALIZED)] || + phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_REGISTERED)] { + cid := ConsumerID(c.Get("consumer_id").String()) + for chainRef, chainCfg := range tr.ChainConfigs { + if chainCfg.ConsumerId == cid { + chains = append(chains, string(chainRef)) + } + } + } + } + + sort.Slice(chains, func(i, j int) bool { + return chains[i] < chains[j] + }) + return chains +} + +// getQueryNode returns query node tcp address on chain. +func (tr Commands) GetQueryNode(chain ChainID) string { + return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) +} + +func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { + return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) +} + +// getQueryNodeIP returns query node IP for chain, +// ipSuffix is hardcoded to be 253 on all query nodes +// except for "sover" chain where there's only one node +func (tr Commands) GetQueryNodeIP(chain ChainID) string { + if chain == ChainID("sover") { + // return address of first and only validator + return fmt.Sprintf("%s.%s", + tr.ChainConfigs[chain].IpPrefix, + tr.ValidatorConfigs[ValidatorID("alice")].IpSuffix) + } + return fmt.Sprintf("%s.253", tr.ChainConfigs[chain].IpPrefix) +} + +// 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(consumerId), tr.ValidatorConfigs[validator].ValconsAddress, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + rate, err := strconv.ParseFloat(gjson.Get(string(bz), "rate").String(), 64) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + return rate +} + +// QueryTransaction returns the content of the transaction or an error e.g. when a transaction coudl +func (tr Commands) QueryTransaction(chain ChainID, txhash string) ([]byte, error) { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "tx", txhash, + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + return cmd.CombinedOutput() +} + +// SubmitGovProposal sends a gov proposal transaction with given command and proposal content +func (tr Commands) SubmitGovProposal(chain ChainID, from ValidatorID, command string, proposal string, verbose bool) ([]byte, error) { + // #nosec G204 -- bypass unsafe quoting warning (no production code) + proposalFile := "/temp-proposal.json" + bz, err := tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, proposal, proposalFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + providerCfg := tr.ChainConfigs[chain] + cmd := tr.Target.ExecCommand( + tr.ChainConfigs[chain].BinaryName, + "tx", "gov", "submit-proposal", proposalFile, + `--from`, `validator`+fmt.Sprint(from), + `--chain-id`, string(providerCfg.ChainId), + `--home`, tr.GetValidatorHome(providerCfg.ChainId, from), + `--gas`, `900000`, + `--node`, tr.GetValidatorNode(providerCfg.ChainId, from), + `--keyring-backend`, `test`, + `-o json`, + `-y`, + ) + + if verbose { + fmt.Printf("submit gov proposal \n\tcmd: %s\n\tcontent: %s", cmd.String(), proposal) + } + return cmd.CombinedOutput() +} + +// CreateConsumer creates a consumer chain and returns its consumer-id +func (tr Commands) CreateConsumer( + providerChain, + consumerChain ChainID, + validator ValidatorID, + metadata types.ConsumerMetadata, + initParams *types.ConsumerInitializationParameters, + powerShapingParams *types.PowerShapingParameters, +) ([]byte, error) { + + msg := types.MsgCreateConsumer{ + ChainId: string(consumerChain), + 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.ChainConfigs[providerChain].BinaryName, + "tx", "provider", "create-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.ChainConfigs[providerChain].ChainId), + `--home`, tr.GetValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.GetValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + return cmd.CombinedOutput() +} + +func (tr Commands) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer, verbose bool) ([]byte, error) { + 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.ChainConfigs[providerChain].BinaryName, + "tx", "provider", "update-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.ChainConfigs[providerChain].ChainId), + `--home`, tr.GetValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.GetValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + return cmd.CombinedOutput() +} + +func (tr Commands) AssignConsumerPubKey(identifier string, pubKey string, from ValidatorID, gas, home, node string, verbose bool) ([]byte, error) { + consumerId := identifier + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand( + binaryName, + + "tx", "provider", "assign-consensus-key", + consumerId, + pubKey, + `--from`, fmt.Sprintf("validator%s", from), + `--chain-id`, string(tr.ChainConfigs[ChainID("provi")].ChainId), + `--home`, home, + `--node`, node, + `--gas`, gas, + `--keyring-backend`, `test`, + `-y`, + `-o`, `json`, + ) + + if verbose { + fmt.Println("assignConsumerPubKey cmd:", cmd.String()) + } + + return cmd.CombinedOutput() +} + +// TODO: refactor the following APIs as they are not version dependent commands on the target +func (tr Commands) GetValidatorHome(chain ChainID, validator ValidatorID) string { + return `/` + string(chain) + `/validator` + fmt.Sprint(validator) +} + +func (tr Commands) GetValidatorIP(chain ChainID, validator ValidatorID) string { + return tr.ChainConfigs[chain].IpPrefix + "." + tr.ValidatorConfigs[validator].IpSuffix +} + +func (tr Commands) GetValidatorNode(chain ChainID, validator ValidatorID) string { + // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) + if tr.UseCometMock() { + return tr.GetQueryNode(chain) + } + return "tcp://" + tr.GetValidatorIP(chain, validator) + ":26658" +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 9ff8d54787..e7c7974457 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os/exec" - "strconv" "strings" "time" @@ -13,65 +12,15 @@ import ( e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" ) +type ( + TestConfig = e2e.TestConfig +) + var ( ProviderAccountPrefix = "cosmos" ConsumerAccountPrefix = "consumer" ) -// hermesTemplates maps hermes configuration templates to hermes versions -var hermesTemplates = map[string]string{ - "v1.4": ` - - [[chains]] - account_prefix = "%s" - clock_drift = "5s" - gas_multiplier = 1.1 - grpc_addr = "%s" - id = "%s" - key_name = "%s" - max_gas = 20000000 - rpc_addr = "%s" - rpc_timeout = "10s" - store_prefix = "ibc" - trusting_period = "14days" - websocket_addr = "%s" - - [chains.gas_price] - denom = "stake" - price = 0.000 - - [chains.trust_threshold] - denominator = "3" - numerator = "1" - `, - // introduction of event_source - "v1.6": ` - - [[chains]] - account_prefix = "%s" - clock_drift = "5s" - gas_multiplier = 1.1 - grpc_addr = "%s" - id = "%s" - key_name = "%s" - max_gas = 20000000 - rpc_addr = "%s" - rpc_timeout = "10s" - store_prefix = "ibc" - trusting_period = "14days" - event_source = { mode = "push", url = "%s", batch_delay = "50ms" } - ccv_consumer_chain = %v - - [chains.gas_price] - denom = "stake" - price = 0.000 - - [chains.trust_threshold] - denominator = "3" - numerator = "1" - `, -} - // type aliases for shared types from e2e package type ( ChainID = e2e.ChainID @@ -104,35 +53,6 @@ const ( PermissionlessTestCfg TestConfigType = "permissionless-ics" ) -type TestConfig struct { - // These are the non altered values during a typical test run, where multiple test runs can exist - // to validate different action sequences and corresponding state checks. - containerConfig ContainerConfig - validatorConfigs map[ValidatorID]ValidatorConfig - chainConfigs map[ChainID]ChainConfig - consumerChains map[ConsumerID]ChainConfig - providerVersion string - consumerVersion string - // override config.toml parameters - // usually used to override timeout_commit - // having shorter timeout_commit reduces the test runtime because blocks are produced faster - // lengthening the timeout_commit increases the test runtime because blocks are produced slower but the test is more reliable - tendermintConfigOverride string - useCometmock bool // if false, nodes run CometBFT - useGorelayer bool // if false, Hermes is used as the relayer - // chains which are running, i.e. producing blocks, at the moment - runningChains map[ChainID]bool - // Used with CometMock. The time by which chains have been advanced. Used to keep chains in sync: when a new chain is started, advance its time by this value to keep chains in sync. - timeOffset time.Duration - transformGenesis bool - name string -} - -// Initialize initializes the TestConfig instance by setting the runningChains field to an empty map. -func (tr *TestConfig) Initialize() { - tr.runningChains = make(map[ChainID]bool) -} - // getIcsVersion returns earliest ICS version (relevant to config) a git reference is part of // This is needed for version dependent configs as CompatibilityConfig. // Note: if no matching version is found an empty string is returned @@ -217,8 +137,8 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri default: panic(fmt.Sprintf("Invalid test config: %s", cfgType)) } - testCfg.consumerVersion = consumerVersion - testCfg.providerVersion = providerVersion + testCfg.ConsumerVersion = consumerVersion + testCfg.ProviderVersion = providerVersion return testCfg } @@ -304,15 +224,14 @@ func getDefaultValidators() map[ValidatorID]ValidatorConfig { func SlashThrottleTestConfig() TestConfig { tr := TestConfig{ - name: string(SlashThrottleTestCfg), - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-slash-container", - InstanceName: "interchain-security-slash-instance", + Name: string(SlashThrottleTestCfg), + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-slash-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -345,7 +264,7 @@ func SlashThrottleTestConfig() TestConfig { ".app_state.ccvconsumer.params.retry_delay_period = \"30s\"", }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, } tr.Initialize() @@ -358,12 +277,12 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig testCfg := DefaultTestConfig() // get version dependent validator configs - testCfg.validatorConfigs = getValidatorConfigFromVersion(providerVersion, consumerVersion) + testCfg.ValidatorConfigs = getValidatorConfigFromVersion(providerVersion, consumerVersion) var providerConfig, consumerConfig ChainConfig if !semver.IsValid(consumerVersion) { fmt.Printf("Invalid sem-version '%s' for provider.Using default provider chain config\n", consumerVersion) - consumerConfig = testCfg.chainConfigs[ChainID("consu")] + consumerConfig = testCfg.ChainConfigs[ChainID("consu")] } else if semver.Compare(consumerVersion, "v3.0.0") < 0 { fmt.Println("Using consumer chain config for v2.0.0") consumerConfig = ChainConfig{ @@ -394,13 +313,13 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig } } else { fmt.Println("Using default consumer chain config") - consumerConfig = testCfg.chainConfigs[ChainID("consu")] + consumerConfig = testCfg.ChainConfigs[ChainID("consu")] } // Get the provider chain config for a specific version if !semver.IsValid(providerVersion) { fmt.Printf("Invalid sem-version '%s' for provider. Using default provider chain config\n", providerVersion) - providerConfig = testCfg.chainConfigs[ChainID("provi")] + providerConfig = testCfg.ChainConfigs[ChainID("provi")] } else if semver.Compare(providerVersion, "v3.0.0") < 0 { fmt.Println("Using provider chain config for v2.x.x") providerConfig = ChainConfig{ @@ -479,29 +398,28 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig } } else { fmt.Println("Using default provider chain config") - providerConfig = testCfg.chainConfigs[ChainID("provi")] + providerConfig = testCfg.ChainConfigs[ChainID("provi")] } - testCfg.chainConfigs[ChainID("consu")] = consumerConfig - testCfg.chainConfigs[ChainID("provi")] = providerConfig - testCfg.name = string(CompatibilityTestCfg) - testCfg.containerConfig.InstanceName = fmt.Sprintf("%s_%s-%s", - testCfg.containerConfig.InstanceName, + testCfg.ChainConfigs[ChainID("consu")] = consumerConfig + testCfg.ChainConfigs[ChainID("provi")] = providerConfig + testCfg.Name = string(CompatibilityTestCfg) + testCfg.ContainerConfig.ContainerName = fmt.Sprintf("%s_%s-%s", + testCfg.ContainerConfig.ContainerName, consumerVersion, providerVersion) return testCfg } func DefaultTestConfig() TestConfig { tr := TestConfig{ - name: string(DefaultTestCfg), - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-container", - InstanceName: "interchain-security-instance", + Name: string(DefaultTestCfg), + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -533,7 +451,7 @@ func DefaultTestConfig() TestConfig { ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, } tr.Initialize() @@ -556,15 +474,14 @@ func DemocracyTestConfig(allowReward bool) TestConfig { } tr := TestConfig{ - name: name, - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-democ-container", - InstanceName: "interchain-security-democ-instance", + Name: name, + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-democ-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -591,7 +508,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { GenesisChanges: consumerGenChanges, }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, } tr.Initialize() @@ -601,15 +518,14 @@ func DemocracyTestConfig(allowReward bool) TestConfig { // PermissionlessTestConfig contains a provider chain and 2 cosumer chains with the same chain identifier func PermissionlessTestConfig() TestConfig { tr := TestConfig{ - name: string(PermissionlessTestCfg), - containerConfig: e2e.ContainerConfig{ - ContainerName: "interchain-security-container", - InstanceName: "interchain-security-instance", + Name: string(PermissionlessTestCfg), + ContainerConfig: e2e.ContainerConfig{ + ContainerName: "interchain-security-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]e2e.ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]e2e.ChainConfig{ "provi": { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -654,7 +570,7 @@ func PermissionlessTestConfig() TestConfig { ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, } tr.Initialize() @@ -662,21 +578,21 @@ func PermissionlessTestConfig() TestConfig { } func InactiveProviderValsTestConfig() TestConfig { tr := DefaultTestConfig() - tr.name = "InactiveValsConfig" + tr.Name = "InactiveValsConfig" // set the MaxProviderConsensusValidators param to 2 - proviConfig := tr.chainConfigs[ChainID("provi")] + proviConfig := tr.ChainConfigs[ChainID("provi")] proviConfig.GenesisChanges += " | .app_state.provider.params.max_provider_consensus_validators = \"2\"" - consuConfig := tr.chainConfigs[ChainID("consu")] + consuConfig := tr.ChainConfigs[ChainID("consu")] // set the soft_opt_out threshold to 0% to make sure all validators are slashed for downtime consuConfig.GenesisChanges += " | .app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.0\"" - tr.chainConfigs[ChainID("provi")] = proviConfig - tr.chainConfigs[ChainID("consu")] = consuConfig + tr.ChainConfigs[ChainID("provi")] = proviConfig + tr.ChainConfigs[ChainID("consu")] = consuConfig // make it so that carol does not use a consumer key - carolConfig := tr.validatorConfigs[ValidatorID("carol")] + carolConfig := tr.ValidatorConfigs[ValidatorID("carol")] carolConfig.UseConsumerKey = false - tr.validatorConfigs[ValidatorID("carol")] = carolConfig + tr.ValidatorConfigs[ValidatorID("carol")] = carolConfig return tr } @@ -685,16 +601,16 @@ func InactiveValsExtraValsTestConfig() TestConfig { tr := InactiveProviderValsTestConfig() // set the MaxProviderConsensusValidators param to 4 - proviConfig := tr.chainConfigs[ChainID("provi")] + proviConfig := tr.ChainConfigs[ChainID("provi")] proviConfig.GenesisChanges += " | .app_state.provider.params.max_provider_consensus_validators = \"4\"" // set max validators to 5 proviConfig.GenesisChanges += " | .app_state.staking.params.max_validators = \"5\"" - tr.chainConfigs[ChainID("provi")] = proviConfig + tr.ChainConfigs[ChainID("provi")] = proviConfig // add the extra validators to the validator config extraVals := GetExtraValidatorConfigs() for valId, val := range extraVals { - tr.validatorConfigs[valId] = val + tr.ValidatorConfigs[valId] = val } return tr @@ -704,14 +620,14 @@ func SmallMaxValidatorsTestConfig() TestConfig { cfg := DefaultTestConfig() // set the MaxValidators to 2 - proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig := cfg.ChainConfigs[ChainID("provi")] proviConfig.GenesisChanges += "| .app_state.staking.params.max_validators = 2" - cfg.chainConfigs[ChainID("provi")] = proviConfig + cfg.ChainConfigs[ChainID("provi")] = proviConfig - carolConfig := cfg.validatorConfigs["carol"] + carolConfig := cfg.ValidatorConfigs["carol"] // make carol use her own key carolConfig.UseConsumerKey = false - cfg.validatorConfigs["carol"] = carolConfig + cfg.ValidatorConfigs["carol"] = carolConfig return cfg } @@ -720,14 +636,14 @@ func GovTestConfig() TestConfig { cfg := DefaultTestConfig() // set the quorum to 50% - proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig := cfg.ChainConfigs[ChainID("provi")] proviConfig.GenesisChanges += "| .app_state.gov.params.quorum = \"0.5\"" - cfg.chainConfigs[ChainID("provi")] = proviConfig + cfg.ChainConfigs[ChainID("provi")] = proviConfig - carolConfig := cfg.validatorConfigs["carol"] + carolConfig := cfg.ValidatorConfigs["carol"] // make carol use her own key carolConfig.UseConsumerKey = false - cfg.validatorConfigs["carol"] = carolConfig + cfg.ValidatorConfigs["carol"] = carolConfig return cfg } @@ -736,9 +652,9 @@ func InactiveValsGovTestConfig() TestConfig { cfg := GovTestConfig() // set the MaxValidators to 1 - proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig := cfg.ChainConfigs[ChainID("provi")] proviConfig.GenesisChanges += "| .app_state.staking.params.max_validators = 1" - cfg.chainConfigs[ChainID("provi")] = proviConfig + cfg.ChainConfigs[ChainID("provi")] = proviConfig return cfg } @@ -760,27 +676,26 @@ func InactiveValsMintTestConfig() TestConfig { // AdjustMint adjusts the mint parameters to have a very low goal bonded amount // and a high inflation rate change func AdjustMint(cfg TestConfig) { - proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig := cfg.ChainConfigs[ChainID("provi")] // total supply is 30000000000stake; we want to set the mint bonded goal to // a small fraction of that proviConfig.GenesisChanges += "| .app_state.mint.params.goal_bonded = \"0.001\"" + "| .app_state.mint.params.inflation_rate_change = \"1\"" + "| .app_state.mint.params.inflation_max = \"0.5\"" + "| .app_state.mint.params.inflation_min = \"0.1\"" - cfg.chainConfigs[ChainID("provi")] = proviConfig + cfg.ChainConfigs[ChainID("provi")] = proviConfig } func MultiConsumerTestConfig() TestConfig { tr := TestConfig{ - name: string(MulticonsumerTestCfg), - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-multic-container", - InstanceName: "interchain-security-multic-instance", + Name: string(MulticonsumerTestCfg), + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-multic-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -823,7 +738,7 @@ func MultiConsumerTestConfig() TestConfig { ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "3s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "3s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "100ms"/;`, } tr.Initialize() @@ -832,15 +747,14 @@ func MultiConsumerTestConfig() TestConfig { func ChangeoverTestConfig() TestConfig { tr := TestConfig{ - name: string(ChangeoverTestCfg), - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-changeover-container", - InstanceName: "interchain-security-changeover-instance", + Name: string(ChangeoverTestCfg), + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-changeover-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: getDefaultValidators(), - chainConfigs: map[ChainID]ChainConfig{ + ValidatorConfigs: getDefaultValidators(), + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -874,7 +788,7 @@ func ChangeoverTestConfig() TestConfig { ".app_state.staking.params.unbonding_time = \"1728000s\"", // making the genesis unbonding time equal to unbonding time in the consumer addition proposal }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, } tr.Initialize() @@ -883,14 +797,13 @@ func ChangeoverTestConfig() TestConfig { func ConsumerMisbehaviourTestConfig() TestConfig { tc := TestConfig{ - name: string(ConsumerMisbehaviourTestCfg), - containerConfig: ContainerConfig{ - ContainerName: "interchain-security-container", - InstanceName: "interchain-security-instance", + Name: string(ConsumerMisbehaviourTestCfg), + ContainerConfig: ContainerConfig{ + ContainerName: "interchain-security-instance", CcvVersion: "1", Now: time.Now(), }, - validatorConfigs: map[ValidatorID]ValidatorConfig{ + ValidatorConfigs: map[ValidatorID]ValidatorConfig{ ValidatorID("alice"): { Mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", DelAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", @@ -942,7 +855,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig { UseConsumerKey: false, }, }, - chainConfigs: map[ChainID]ChainConfig{ + ChainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), AccountPrefix: ProviderAccountPrefix, @@ -974,7 +887,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig { ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", }, }, - tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + TendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;` + // Required to start consumer chain by running a single big validator `s/block_sync = true/block_sync = false/;`, @@ -983,56 +896,6 @@ func ConsumerMisbehaviourTestConfig() TestConfig { return tc } -func (s *TestConfig) SetCometMockConfig(useCometmock bool) { - s.useCometmock = useCometmock -} - -func (s *TestConfig) SetRelayerConfig(useRly bool) { - s.useGorelayer = useRly -} - -// validateStringLiterals enforces that configs follow the constraints -// necessary to execute the tests -// -// Note: Network interfaces (name of virtual ethernet interfaces for ip link) -// within the container will be named as "$CHAIN_ID-$VAL_ID-out" etc. -// where this name is constrained to 15 bytes or less. Therefore each string literal -// used as a validatorID or chainID needs to be 5 char or less. -func (s *TestConfig) validateStringLiterals() { - for valID, valConfig := range s.validatorConfigs { - if len(valID) > 5 { - panic("validator id string literal must be 5 char or less") - } - - ipSuffix, err := strconv.Atoi(valConfig.IpSuffix) - if err != nil { - panic(fmt.Sprintf("ip suffix must be an int: %v\n", err)) - } - - if ipSuffix == 253 { - panic("ip suffix 253 is reserved for query node") - } - - if ipSuffix == 252 { - panic("ip suffix 252 is reserved for double signing node") - } - - if ipSuffix < 1 || 251 < ipSuffix { - panic("ip suffix out of range, need to change config") - } - } - - for chainID, chainConfig := range s.chainConfigs { - if len(chainID) > 5 { - panic(fmt.Sprintf("chain id string literal must be 5 char or less: %s", chainID)) - } - - if chainID != chainConfig.ChainId { - log.Println("chain config is mapped to a chain id that is different than what's stored in the config") - } - } -} - // getValidatorConfigFromVersion returns validator configuration based on provider/consumer version used. func getValidatorConfigFromVersion(providerVersion, consumerVersion string) map[ValidatorID]ValidatorConfig { var validatorCfg map[ValidatorID]ValidatorConfig @@ -1326,46 +1189,6 @@ func getValidatorConfigFromVersion(providerVersion, consumerVersion string) map[ return validatorCfg } -// GetHermesConfig returns a configuration string for a given hermes version -// -// Currently templates for Hermes v1.6.0 and v1.4 are supported. -// If provided version is before v1.6.0 then a configuration based on template for v1.4.x is returned -// otherwise the returned configuration is based on template v1.4. -func GetHermesConfig(hermesVersion, queryNodeIP string, chainCfg ChainConfig, isConsumer bool) string { - - ChainId := chainCfg.ChainId - keyName := "query" - rpcAddr := "http://" + queryNodeIP + ":26658" - grpcAddr := "tcp://" + queryNodeIP + ":9091" - wsAddr := "ws://" + queryNodeIP + ":26658/websocket" - - hermesConfig := "" - if semver.Compare(hermesVersion, "1.6.0") < 0 { - fmt.Println("Using hermes config template", "1.4") - template := hermesTemplates["v1.4"] - hermesConfig = fmt.Sprintf(template, - chainCfg.AccountPrefix, - grpcAddr, - ChainId, - keyName, - rpcAddr, - wsAddr) - } else { - // added event_source (v1.6) + ccv_consumer_chain (v1.5) - fmt.Println("Using hermes config template", "1.6") - template := hermesTemplates["v1.6"] - hermesConfig = fmt.Sprintf(template, - chainCfg.AccountPrefix, - grpcAddr, - ChainId, - keyName, - rpcAddr, - wsAddr, - isConsumer) - } - return hermesConfig -} - // GetExtraValidatorConfigs returns a map of extra validator configurations. // These are configurations that are not part of the default configurations, // for cases where more validators are needed. diff --git a/tests/e2e/main.go b/tests/e2e/main.go index e8e3e0640a..bc060ae905 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -50,6 +50,7 @@ var ( "path of a local sdk version to build and reference in integration tests") useCometmock = flag.Bool("use-cometmock", false, "use cometmock instead of CometBFT. see https://github.com/informalsystems/CometMock") useGorelayer = flag.Bool("use-gorelayer", false, "use go relayer instead of Hermes") + useImage = flag.String("docker-image", "", "use existing docker image") ) var ( @@ -444,8 +445,8 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet, providerVe // delete all test targets func deleteTargets(runners []TestRunner) { for _, runner := range runners { - if err := runner.target.Delete(); err != nil { - log.Println("error deleting target: ", err) + if err := runner.CleanUp(); err != nil { + log.Println("error cleaning up target: ", err) } } } @@ -472,8 +473,12 @@ func createTestConfigs(cfgType TestConfigType, providerVersions, consumerVersion config := GetTestConfig(cfgType, provider, consumer) config.SetRelayerConfig(*useGorelayer) config.SetCometMockConfig(*useCometmock) - config.transformGenesis = *transformGenesis - config.useGorelayer = *useGorelayer + config.TransformGenesis = *transformGenesis + config.UseGorelayer = *useGorelayer + + runnerId++ + config.ContainerConfig.ContainerName = config.ContainerConfig.ContainerName + fmt.Sprintf("-%d", runnerId) + configs = append(configs, config) } } @@ -483,16 +488,16 @@ func createTestConfigs(cfgType TestConfigType, providerVersions, consumerVersion // createTestRunners creates test runners to run each test case on each target func createTestRunners(testCases []testStepsWithConfig) []TestRunner { runners := []TestRunner{} - targetCfg := TargetConfig{useGaia: *useGaia, localSdkPath: *localSdkPath, gaiaTag: *gaiaTag} + targetCfg := TargetConfig{useGaia: *useGaia, localSdkPath: *localSdkPath, gaiaTag: *gaiaTag, useCometMock: *useCometmock} for _, tc := range testCases { testConfigs := createTestConfigs(tc.config, providerVersions, consumerVersions) for _, cfg := range testConfigs { - target, err := createTarget(cfg, targetCfg) + target, err := createTarget(cfg, targetCfg, *useImage) tr := CreateTestRunner(cfg, tc.steps, &target, *verbose) if err == nil { - fmt.Println("Created test runner for ", cfg.name, - "with provVers=", cfg.providerVersion, "consVers=", cfg.consumerVersion) + fmt.Printf("Created test runner for '%s' with provider version=%s consumer version=%s\n", + cfg.Name, cfg.ProviderVersion, cfg.ConsumerVersion) runners = append(runners, tr) } else { fmt.Println("No test runner created:", err) @@ -517,7 +522,7 @@ func executeTests(runners []TestRunner) error { defer wg.Done() result := runner.Run() if result != nil { - log.Printf("Test '%s' failed", runner.config.name) + log.Printf("Test '%s' failed", runner.config.Name) err = result } }(&runners[idx]) diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 089c026192..20e110e66c 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -1,33 +1,19 @@ package main import ( - "bufio" - "encoding/json" "fmt" "log" - "os/exec" "regexp" - "sort" - "strconv" - "strings" "time" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/kylelemons/godebug/pretty" - "github.com/tidwall/gjson" - "gopkg.in/yaml.v2" e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" - "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" ) // type aliases type ( ChainState = e2e.ChainState - Proposal = e2e.Proposal - Rewards = e2e.Rewards - TextProposal = e2e.TextProposal - UpgradeProposal = e2e.UpgradeProposal ConsumerAdditionProposal = e2e.ConsumerAdditionProposal ConsumerRemovalProposal = e2e.ConsumerRemovalProposal ConsumerModificationProposal = e2e.ConsumerModificationProposal @@ -35,18 +21,53 @@ type ( IBCTransferParamsProposal = e2e.IBCTransferParamsProposal Param = e2e.Param ParamsProposal = e2e.ParamsProposal + Proposal = e2e.Proposal + Rewards = e2e.Rewards TargetDriver = e2e.TargetDriver + TextProposal = e2e.TextProposal + TxResponse = e2e.TxResponse + UpgradeProposal = e2e.UpgradeProposal ) type State map[ChainID]ChainState type Chain struct { - target e2e.TargetDriver + target TargetDriver testConfig *TestConfig } +// waitForTx waits until a transaction is seen in a block or panics if a timeout occurs +func (tr Chain) waitForTx(chain ChainID, txResponse []byte, timeout time.Duration) TxResponse { + // remove any gas estimate as when command is run with gas=auto the output contains gas estimation mixed with json output + re, err := regexp.Compile("gas estimate: [0-9]+") + if err != nil { + panic(fmt.Sprintf("error compiling regexp: %s", err.Error())) + } + txResponse = re.ReplaceAll(txResponse, []byte{}) + + // check transaction + response := e2e.GetTxResponse(txResponse) + if response.Code != 0 { + log.Fatalf("sending transaction failed with error code %d, Log:'%s'", response.Code, response.RawLog) + } + + // wait for the transaction + start := time.Now() + for { + res, err := tr.target.QueryTransaction(chain, response.TxHash) + if err == nil { + return e2e.GetTxResponse(res) + } + if time.Since(start) > timeout { + log.Printf("query transaction failed with err=%s, resp=%s", err.Error(), res) + panic(fmt.Sprintf("\n\nwaitForTx on chain '%s' has timed out after: %s\n\n", chain, timeout)) + } + time.Sleep(1 * time.Second) + } +} + func (tr Chain) waitBlocks(chain ChainID, blocks uint, timeout time.Duration) { - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { // call advance_blocks method on cometmock // curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"advance_blocks","params":{"num_blocks": "36000000"},"id":1}' 127.0.0.1:22331 tcpAddress := tr.target.GetQueryNodeRPCAddress(chain) @@ -105,7 +126,7 @@ func (tr Chain) GetProposals(chain ChainID, modelState map[uint]Proposal) map[ui func (tr Chain) GetValPowers(chain ChainID, modelState map[ValidatorID]uint) map[ValidatorID]uint { actualState := map[ValidatorID]uint{} - validatorConfigs := tr.testConfig.validatorConfigs + validatorConfigs := tr.testConfig.ValidatorConfigs for k := range modelState { valAddresses := map[string]bool{} if chain == ChainID("provi") { @@ -125,7 +146,7 @@ func (tr Chain) GetValPowers(chain ChainID, modelState map[ValidatorID]uint) map func (tr Chain) GetStakedTokens(chain ChainID, modelState map[ValidatorID]uint) map[ValidatorID]uint { actualState := map[ValidatorID]uint{} for validator := range modelState { - validatorConfigs := tr.testConfig.validatorConfigs + validatorConfigs := tr.testConfig.ValidatorConfigs valoperAddress := validatorConfigs[validator].ValoperAddress if chain != ChainID("provi") { // use binary with Bech32Prefix set to ConsumerAccountPrefix @@ -181,7 +202,7 @@ func (tr Chain) GetProviderAddresses(chain ChainID, modelState map[ValidatorID]s func (tr Chain) getValidatorNode(chain ChainID, validator ValidatorID) string { // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) - if tr.testConfig.useCometmock { + if tr.testConfig.UseCometmock { return tr.target.GetQueryNode(chain) } @@ -189,7 +210,7 @@ func (tr Chain) getValidatorNode(chain ChainID, validator ValidatorID) string { } func (tr Chain) getValidatorIP(chain ChainID, validator ValidatorID) string { - return tr.testConfig.chainConfigs[chain].IpPrefix + "." + tr.testConfig.validatorConfigs[validator].IpSuffix + return tr.testConfig.ChainConfigs[chain].IpPrefix + "." + tr.testConfig.ValidatorConfigs[validator].IpSuffix } func (tr Chain) getValidatorHome(chain ChainID, validator ValidatorID) string { @@ -205,772 +226,6 @@ func (tr Chain) curlJsonRPCRequest(method, params, address string) { e2e.ExecuteCommand(cmd, "curlJsonRPCRequest", verbosity) } -func uintPtr(i uint) *uint { - return &i -} - -func intPtr(i int) *int { - return &i -} - -type Commands struct { - containerConfig *ContainerConfig - validatorConfigs map[ValidatorID]ValidatorConfig - chainConfigs map[ChainID]ChainConfig - target e2e.PlatformDriver -} - -func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { - return tr.target.ExecCommand(name, arg...) -} - -func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { - return tr.target.ExecDetachedCommand(name, args...) -} - -func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { - return tr.target.GetTestScriptPath(isConsumer, script) -} - -func (tr Commands) GetBlockHeight(chain ChainID) uint { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - - "query", "tendermint-validator-set", - - `--node`, tr.GetQueryNode(chain), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) - blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) - if err != nil { - log.Fatal(err) - } - - return uint(blockHeight) -} - -func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { - valCfg := tr.validatorConfigs[validator] - delAddresss := valCfg.DelAddress - if chain != ChainID("provi") { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if valCfg.UseConsumerKey { - delAddresss = valCfg.ConsumerDelAddress - } else { - // use the same address as on the provider but with different prefix - delAddresss = valCfg.DelAddressOnConsumer - } - } - - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - "query", "distribution", "rewards", - delAddresss, - `--height`, fmt.Sprint(blockHeight), - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - - if *verbose { - log.Println("getting rewards for chain: ", chain, " validator: ", validator, " blockHeight: ", blockHeight) - log.Println(cmd) - } - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Println("running cmd: ", cmd) - log.Fatal("failed getting rewards: ", err, "\n", string(bz)) - } - - denomCondition := fmt.Sprintf(`total.#(%%"*%s*")`, denom) - amount := strings.Split(gjson.Get(string(bz), denomCondition).String(), denom)[0] - - res := float64(0) - if amount != "" { - res, err = strconv.ParseFloat(amount, 64) - if err != nil { - log.Fatal("failed parsing consumer reward:", err) - } - } - - return res -} - -// interchain-securityd query gov proposals -func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { - noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) - - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - "query", "gov", "proposal", - fmt.Sprint(proposal), - `--node`, tr.GetQueryNode(chain), - `-o`, `json`) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Println("Error getting governance proposal", proposal, "\n\t cmd: ", cmd, "\n\t err:", err, "\n\t out: ", string(bz)) - } - - prop := TextProposal{} - propRaw := string(bz) - if err != nil { - if noProposalRegex.Match(bz) { - return prop - } - - log.Fatal(err, "\n", propRaw) - } - - // 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`) - - // for current (>= v47) prop types submitted using "tx submit-proposal" - if propType == "" { - propType = gjson.Get(propRaw, `proposal.messages.0.type`).String() - rawContent = gjson.Get(propRaw, `proposal.messages.0.value`) - } - - title := gjson.Get(propRaw, `proposal.title`).String() - deposit := gjson.Get(propRaw, `proposal.total_deposit.#(denom=="stake").amount`).Uint() - status := gjson.Get(propRaw, `proposal.status`).String() - - switch propType { - case "/cosmos.gov.v1beta1.TextProposal": - title := rawContent.Get("title").String() - description := rawContent.Get("description").String() - - return TextProposal{ - Deposit: uint(deposit), - Status: status, - 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) - - var chain ChainID - for i, conf := range tr.chainConfigs { - if string(conf.ChainId) == chainId { - chain = i - break - } - } - - return ConsumerAdditionProposal{ - Deposit: uint(deposit), - Status: status, - Chain: chain, - SpawnTime: int(spawnTime.Milliseconds()), - InitialHeight: clienttypes.Height{ - RevisionNumber: rawContent.Get("initial_height.revision_number").Uint(), - RevisionHeight: rawContent.Get("initial_height.revision_height").Uint(), - }, - } - case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": - case "cosmos-sdk/MsgSoftwareUpgrade": - height := rawContent.Get("plan.height").Uint() - title := rawContent.Get("plan.name").String() - return UpgradeProposal{ - Deposit: uint(deposit), - Status: status, - UpgradeHeight: height, - Title: title, - Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", - } - case "/interchain_security.ccv.provider.v1.MsgRemoveConsumer": - consumerId := rawContent.Get("consumer_id").String() - - var chain ChainID - for i, conf := range tr.chainConfigs { - if string(conf.ConsumerId) == consumerId { - chain = i - break - } - } - - return ConsumerRemovalProposal{ - Deposit: uint(deposit), - Status: status, - Chain: chain, - } - case "/ibc.applications.transfer.v1.MsgUpdateParams": - var params IBCTransferParams - if err := json.Unmarshal([]byte(rawContent.Get("params").String()), ¶ms); err != nil { - log.Fatal("cannot unmarshal ibc-transfer params: ", err, "\n", propRaw) - } - - return IBCTransferParamsProposal{ - Deposit: uint(deposit), - Status: status, - Title: title, - Params: params, - } - - case "/interchain_security.ccv.provider.v1.MsgConsumerModification": - chainId := rawContent.Get("chain_id").String() - - var chain ChainID - for i, conf := range tr.chainConfigs { - if string(conf.ChainId) == chainId { - chain = i - break - } - } - - return ConsumerModificationProposal{ - Deposit: uint(deposit), - Status: status, - Chain: chain, - } - case "/cosmos.params.v1beta1.ParameterChangeProposal": - return ParamsProposal{ - Deposit: uint(deposit), - Status: status, - Subspace: gjson.Get(propRaw, `messages.0.content.changes.0.subspace`).String(), - Key: gjson.Get(propRaw, `messages.0.content.changes.0.key`).String(), - Value: gjson.Get(propRaw, `messages.0.content.changes.0.value`).String(), - } - } - - log.Fatal("received unknown proposal type: '", propType, "', proposal JSON:", propRaw) - - return nil -} - -type TmValidatorSetYaml struct { - BlockHeight string `yaml:"block_height"` - Pagination struct { - NextKey string `yaml:"next_key"` - Total string `yaml:"total"` - } `yaml:"pagination"` - Validators []struct { - Address string `yaml:"address"` - VotingPower string `yaml:"voting_power"` - PubKey ValPubKey `yaml:"pub_key"` - } -} - -type ValPubKey struct { - Value string `yaml:"value"` -} - -// TODO (mpoke) Return powers for multiple validators -func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { - if *verbose { - log.Println("getting validator power for chain: ", chain, " validator: ", validator) - } - binaryName := tr.chainConfigs[chain].BinaryName - command := tr.target.ExecCommand(binaryName, - - "query", "tendermint-validator-set", - - `--node`, tr.GetQueryNode(chain), - ) - bz, err := command.CombinedOutput() - if err != nil { - log.Fatalf("encountered an error when executing command '%s': %v, output: %s", command.String(), err, string(bz)) - } - - valset := TmValidatorSetYaml{} - - err = yaml.Unmarshal(bz, &valset) - if err != nil { - log.Fatalf("yaml.Unmarshal returned an error while unmarshalling validator set: %v, input: %s", err, string(bz)) - } - - total, err := strconv.Atoi(valset.Pagination.Total) - if err != nil { - log.Fatalf("strconv.Atoi returned an error while converting total for validator set: %v, input: %s, validator set: %s, src:%s", - err, valset.Pagination.Total, pretty.Sprint(valset), string(bz)) - } - - if total != len(valset.Validators) { - log.Fatalf("Total number of validators %v does not match number of validators in list %v. Probably a query pagination issue. Validator set: %v", - valset.Pagination.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) - } - - for _, val := range valset.Validators { - if chain == ChainID("provi") { - // use binary with Bech32Prefix set to ProviderAccountPrefix - if val.Address != tr.validatorConfigs[validator].ValconsAddress { - continue - } - } else { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if val.Address != tr.validatorConfigs[validator].ValconsAddressOnConsumer && - val.Address != tr.validatorConfigs[validator].ConsumerValconsAddress { - continue - } - } - - votingPower, err := strconv.Atoi(val.VotingPower) - if err != nil { - log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) - } - - return uint(votingPower) - } - - // Validator not in set, its validator power is zero. - return 0 -} - -func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { - valCfg := tr.validatorConfigs[validator] - valDelAddress := valCfg.DelAddress - if chain != ChainID("provi") { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if valCfg.UseConsumerKey { - valDelAddress = valCfg.ConsumerDelAddress - } else { - // use the same address as on the provider but with different prefix - valDelAddress = valCfg.DelAddressOnConsumer - } - } - - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - - "query", "bank", "balances", - valDelAddress, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal("getBalance() failed: ", cmd, ": ", err, "\n", string(bz)) - } - - amount := gjson.Get(string(bz), `balances.#(denom=="stake").amount`) - - return uint(amount.Uint()) -} - -func (tr Commands) GetValStakedTokens(chain ChainID, valoperAddress string) uint { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - - "query", "staking", "validator", - valoperAddress, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - amount := gjson.Get(string(bz), `validator.tokens`) - - return uint(amount.Uint()) -} - -func (tr Commands) GetParam(chain ChainID, param Param) string { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - "query", "params", "subspace", - param.Subspace, - param.Key, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - value := gjson.Get(string(bz), `value`) - - return value.String() -} - -func (tr Commands) GetIBCTransferParams(chain ChainID) IBCTransferParams { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - "query", "ibc-transfer", "params", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - var params IBCTransferParams - if err := json.Unmarshal(bz, ¶ms); err != nil { - log.Fatal("cannot unmarshal ibc-transfer params: ", err, "\n", string(bz)) - } - - return params -} - -// GetConsumerChains returns a list of consumer chains that're being secured by the provider chain, -// determined by querying the provider chain. -func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - "query", "provider", "list-consumer-chains", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - arr := gjson.Get(string(bz), "chains").Array() - chains := make(map[ChainID]bool) - for _, c := range arr { - phase := c.Get("phase").String() - if phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_INITIALIZED)] || - phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_REGISTERED)] || - phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_LAUNCHED)] { - id := c.Get("consumer_id").String() - for chainRef, cfg := range tr.chainConfigs { - if cfg.ConsumerId == ConsumerID(id) { - // note: 'chainRef' is the reference the test uses and not necessarily matching chain id - chains[chainRef] = true - } - } - } - } - - return chains -} - -func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { - binaryName := tr.chainConfigs[ChainID("provi")].BinaryName - consumerId := tr.chainConfigs[consumerChain].ConsumerId - cmd := tr.target.ExecCommand(binaryName, - - "query", "provider", "validator-consumer-key", - string(consumerId), tr.validatorConfigs[validator].ValconsAddress, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - addr := gjson.Get(string(bz), "consumer_address").String() - return addr -} - -func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { - binaryName := tr.chainConfigs[ChainID("provi")].BinaryName - consumerId := tr.chainConfigs[consumerChain].ConsumerId - - cmd := tr.target.ExecCommand(binaryName, - - "query", "provider", "validator-provider-key", - string(consumerId), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Println("error running ", cmd) - log.Fatal(err, "\n", string(bz)) - } - - addr := gjson.Get(string(bz), "provider_address").String() - return addr -} - -func (tr Commands) GetSlashMeter() int64 { - binaryName := tr.chainConfigs[ChainID("provi")].BinaryName - cmd := tr.target.ExecCommand(binaryName, - - "query", "provider", "throttle-state", - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - slashMeter := gjson.Get(string(bz), "slash_meter") - return slashMeter.Int() -} - -func (tr Commands) GetRegisteredConsumerRewardDenoms(chain ChainID) []string { - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - - "query", "provider", "registered-consumer-reward-denoms", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - denoms := gjson.Get(string(bz), "denoms").Array() - rewardDenoms := make([]string, len(denoms)) - for i, d := range denoms { - rewardDenoms[i] = d.String() - } - - return rewardDenoms -} - -func (tr Commands) GetPendingPacketQueueSize(chain ChainID) uint { - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - - "query", "ccvconsumer", "throttle-state", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - if !gjson.ValidBytes(bz) { - panic("invalid json response from query ccvconsumer throttle-state: " + string(bz)) - } - - packetData := gjson.Get(string(bz), "packet_data_queue").Array() - return uint(len(packetData)) -} - -// GetClientFrozenHeight returns the frozen height for a client with the given client ID -// by querying the hosting chain with the given chainID -func (tr Commands) GetClientFrozenHeight(chain ChainID, clientID string) (uint64, uint64) { - binaryName := tr.chainConfigs[ChainID("provi")].BinaryName - cmd := tr.target.ExecCommand(binaryName, - "query", "ibc", "client", "state", clientID, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - frozenHeight := gjson.Get(string(bz), "client_state.frozen_height") - - revHeight, err := strconv.Atoi(frozenHeight.Get("revision_height").String()) - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - revNumber, err := strconv.Atoi(frozenHeight.Get("revision_number").String()) - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - return uint64(revNumber), uint64(revHeight) -} - -func (tr Commands) GetHasToValidate( - validatorId ValidatorID, -) []ChainID { - binaryName := tr.chainConfigs[ChainID("provi")].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - "query", "provider", "has-to-validate", - tr.validatorConfigs[validatorId].ValconsAddress, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - arr := gjson.Get(string(bz), "consumer_ids").Array() - chains := []ChainID{} - for _, c := range arr { - for chainRef, chain := range tr.chainConfigs { - if chain.ConsumerId == ConsumerID(c.String()) { - // we report the test chain reference which might not match the chain ID - // to support testing consumer chains with same chain ID - chains = append(chains, chainRef) - break - } - } - } - - return chains -} - -func (tr Commands) GetInflationRate( - chain ChainID, -) float64 { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - "query", "mint", "inflation", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - inflationRate := gjson.Get(string(bz), "inflation").Float() - return inflationRate -} - -func (tr Commands) GetTrustedHeight( - chain ChainID, - clientID string, - index int, -) (uint64, uint64) { - configureNodeCmd := tr.target.ExecCommand("hermes", - "--json", "query", "client", "consensus", "--chain", string(chain), - `--client`, clientID, - ) - - cmdReader, err := configureNodeCmd.StdoutPipe() - if err != nil { - log.Fatal(err) - } - - configureNodeCmd.Stderr = configureNodeCmd.Stdout - - if err := configureNodeCmd.Start(); err != nil { - log.Fatal(err) - } - - scanner := bufio.NewScanner(cmdReader) - - var trustedHeight gjson.Result - // iterate on the relayer's response - // and parse the command "result" - for scanner.Scan() { - out := scanner.Text() - if len(gjson.Get(out, "result").Array()) > 0 { - trustedHeight = gjson.Get(out, "result").Array()[index] - break - } - } - - revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) - if err != nil { - log.Fatal(err) - } - - revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) - if err != nil { - log.Fatal(err) - } - return uint64(revHeight), uint64(revNumber) -} - -func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { - binaryName := tr.chainConfigs[chain].BinaryName - cmd := tr.target.ExecCommand(binaryName, - "query", "provider", "list-consumer-chains", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - arr := gjson.Get(string(bz), "chains").Array() - chains := []string{} - for _, c := range arr { - phase := c.Get("phase").String() - if phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_INITIALIZED)] || - phase == types.ConsumerPhase_name[int32(types.CONSUMER_PHASE_REGISTERED)] { - cid := ConsumerID(c.Get("consumer_id").String()) - for chainRef, chainCfg := range tr.chainConfigs { - if chainCfg.ConsumerId == cid { - chains = append(chains, string(chainRef)) - } - } - } - } - - sort.Slice(chains, func(i, j int) bool { - return chains[i] < chains[j] - }) - return chains -} - -// getQueryNode returns query node tcp address on chain. -func (tr Commands) GetQueryNode(chain ChainID) string { - return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) -} - -func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { - return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) -} - -// getQueryNodeIP returns query node IP for chain, -// ipSuffix is hardcoded to be 253 on all query nodes -// except for "sover" chain where there's only one node -func (tr Commands) GetQueryNodeIP(chain ChainID) string { - if chain == ChainID("sover") { - // return address of first and only validator - return fmt.Sprintf("%s.%s", - tr.chainConfigs[chain].IpPrefix, - tr.validatorConfigs[ValidatorID("alice")].IpSuffix) - } - return fmt.Sprintf("%s.253", tr.chainConfigs[chain].IpPrefix) -} - -// 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(consumerId), tr.validatorConfigs[validator].ValconsAddress, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - rate, err := strconv.ParseFloat(gjson.Get(string(bz), "rate").String(), 64) - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - return rate -} - func (tr Chain) GetConsumerCommissionRates(chain ChainID, modelState map[ValidatorID]float64) map[ValidatorID]float64 { actualState := map[ValidatorID]float64{} for k := range modelState { @@ -979,3 +234,11 @@ func (tr Chain) GetConsumerCommissionRates(chain ChainID, modelState map[Validat return actualState } + +func uintPtr(i uint) *uint { + return &i +} + +func intPtr(i int) *int { + return &i +} diff --git a/tests/e2e/step_delegation.go b/tests/e2e/step_delegation.go index 531f0cbceb..30ec399246 100644 --- a/tests/e2e/step_delegation.go +++ b/tests/e2e/step_delegation.go @@ -29,10 +29,11 @@ func stepsDelegate(consumerName string) []Step { }, { Action: SendTokensAction{ - Chain: ChainID(consumerName), - From: ValidatorID("alice"), - To: ValidatorID("bob"), - Amount: 1, + Chain: ChainID(consumerName), + From: ValidatorID("alice"), + To: ValidatorID("bob"), + Amount: 1, + ExpectErr: true, }, State: State{ ChainID(consumerName): ChainState{ diff --git a/tests/e2e/steps_downtime.go b/tests/e2e/steps_downtime.go index 70fd81c130..b2407644b3 100644 --- a/tests/e2e/steps_downtime.go +++ b/tests/e2e/steps_downtime.go @@ -1,6 +1,10 @@ package main -import "time" +import ( + "time" + + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" +) // stepsDowntime tests validator jailing and slashing. // @@ -434,7 +438,7 @@ func stepsThrottledDowntime(consumerName string) []Step { ValidatorID("bob"): 500, ValidatorID("carol"): 500, }, - ConsumerPendingPacketQueueSize: uintPtr(1), // bob's downtime slash packet is queued + ConsumerPendingPacketQueueSize: e2e.UintPtr(1), // bob's downtime slash packet is queued }, }, }, @@ -463,7 +467,7 @@ func stepsThrottledDowntime(consumerName string) []Step { ValidatorID("bob"): 500, ValidatorID("carol"): 500, }, - ConsumerPendingPacketQueueSize: uintPtr(0), // slash packet handled ack clears consumer queue + ConsumerPendingPacketQueueSize: e2e.UintPtr(0), // slash packet handled ack clears consumer queue }, }, }, @@ -487,7 +491,7 @@ func stepsThrottledDowntime(consumerName string) []Step { ValidatorID("bob"): 500, // VSC packet jailing bob is not yet relayed to consumer ValidatorID("carol"): 500, }, - ConsumerPendingPacketQueueSize: uintPtr(1), // carol's downtime slash packet is queued + ConsumerPendingPacketQueueSize: e2e.UintPtr(1), // carol's downtime slash packet is queued }, }, }, @@ -513,7 +517,7 @@ func stepsThrottledDowntime(consumerName string) []Step { ValidatorID("bob"): 0, // VSC packet applying bob jailing is also relayed and recv by consumer ValidatorID("carol"): 500, }, - ConsumerPendingPacketQueueSize: uintPtr(1), // slash packet bounced ack keeps carol's downtime slash packet queued + ConsumerPendingPacketQueueSize: e2e.UintPtr(1), // slash packet bounced ack keeps carol's downtime slash packet queued }, }, }, @@ -541,7 +545,7 @@ func stepsThrottledDowntime(consumerName string) []Step { ValidatorID("bob"): 0, ValidatorID("carol"): 500, }, - ConsumerPendingPacketQueueSize: uintPtr(1), // packet still queued + ConsumerPendingPacketQueueSize: e2e.UintPtr(1), // packet still queued }, }, }, @@ -561,7 +565,7 @@ func stepsThrottledDowntime(consumerName string) []Step { }, }, ChainID(consumerName): ChainState{ - ConsumerPendingPacketQueueSize: uintPtr(1), // packet still queued + ConsumerPendingPacketQueueSize: e2e.UintPtr(1), // packet still queued }, }, }, @@ -582,7 +586,7 @@ func stepsThrottledDowntime(consumerName string) []Step { }, }, ChainID(consumerName): ChainState{ - ConsumerPendingPacketQueueSize: uintPtr(0), // relayed slash packet handled ack clears consumer queue + ConsumerPendingPacketQueueSize: e2e.UintPtr(0), // relayed slash packet handled ack clears consumer queue }, }, }, diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index dab57b4eba..783ae0cead 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -3,6 +3,7 @@ package main import ( gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" ) // stepsInactiveValidatorsOnConsumer tests situations where validators that are *not* in the active set on the @@ -901,7 +902,7 @@ func stepsInactiveValsMint() []Step { 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 + InflationRateChange: e2e.IntPtr(1), // inflation rate goes up because less than the goal is bonded, since only carol is active }, }, }, @@ -919,7 +920,7 @@ func stepsInactiveValsMint() []Step { ValidatorID("bob"): 0, ValidatorID("carol"): 79, }, - InflationRateChange: intPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal + InflationRateChange: e2e.IntPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal }, }, }, @@ -947,7 +948,7 @@ func stepsMintBasecase() []Step { ValidatorID("bob"): 28, ValidatorID("carol"): 29, }, - InflationRateChange: intPtr(-1), // inflation rate goes down because more than the goal is bonded + InflationRateChange: e2e.IntPtr(-1), // inflation rate goes down because more than the goal is bonded }, }, }, @@ -965,7 +966,7 @@ func stepsMintBasecase() []Step { ValidatorID("bob"): 28, ValidatorID("carol"): 79, }, - InflationRateChange: intPtr(-1), // inflation rate *still* goes down + InflationRateChange: e2e.IntPtr(-1), // inflation rate *still* goes down }, }, }, diff --git a/tests/e2e/steps_permissionless_ics.go b/tests/e2e/steps_permissionless_ics.go index db3a389586..e671162803 100644 --- a/tests/e2e/steps_permissionless_ics.go +++ b/tests/e2e/steps_permissionless_ics.go @@ -121,7 +121,7 @@ func stepsPermissionlessICS() []Step { Chain: ChainID("provi"), From: ValidatorID("bob"), ConsumerChain: ChainID("cons1"), - NewOwner: getDefaultValidators()[ValidatorID("carol")].ValconsAddress, + NewOwner: getDefaultValidators()[ValidatorID("carol")].ValoperAddress, InitParams: &InitializationParameters{ InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, SpawnTime: 0, // launch now diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index b20c194072..3b918912ae 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -11,7 +11,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" - v4 "github.com/cosmos/interchain-security/v6/tests/e2e/v4" + v5 "github.com/cosmos/interchain-security/v6/tests/e2e/v5" ) // TestCaseDriver knows how different TC can be executed @@ -20,7 +20,8 @@ type TestCaseDriver interface { } func GetTestCaseDriver(testCfg TestConfig) TestCaseDriver { - return &DefaultDriver{testCfg: testCfg} + driver := DefaultDriver{testCfg: testCfg} + return &driver } type DefaultDriver struct { @@ -36,7 +37,7 @@ func (td *DefaultDriver) Run(steps []Step, target ExecutionTarget, verbose bool) for i, step := range steps { fmt.Printf("running %s: step %d/%d == %s \n", - td.testCfg.name, i+1, len(steps), reflect.TypeOf(step.Action).Name()) + td.testCfg.Name, i+1, len(steps), reflect.TypeOf(step.Action).Name()) err := td.runStep(step) if err != nil { @@ -57,7 +58,7 @@ func (td *DefaultDriver) runStep(step Step) error { // Check state if !reflect.DeepEqual(actualState, modelState) { - fmt.Printf("=============== %s FAILED ===============\n", td.testCfg.name) + fmt.Printf("=============== %s FAILED ===============\n", td.testCfg.Name) fmt.Println("FAILED action", reflect.TypeOf(step.Action).Name()) pretty.Print("actual state", actualState) pretty.Print("model state", modelState) @@ -68,10 +69,10 @@ func (td *DefaultDriver) runStep(step Step) error { func (td *DefaultDriver) getIcsVersion(chainID ChainID) string { version := "" - if td.testCfg.chainConfigs[chainID].BinaryName == "interchain-security-pd" { - version = td.testCfg.providerVersion + if td.testCfg.ChainConfigs[chainID].BinaryName == "interchain-security-pd" { + version = td.testCfg.ProviderVersion } else { - version = td.testCfg.consumerVersion + version = td.testCfg.ConsumerVersion } ics := getIcsVersion(version) if !semver.IsValid(ics) { @@ -85,24 +86,69 @@ func (td *DefaultDriver) getTargetDriver(chainID ChainID) Chain { target := Chain{ testConfig: &td.testCfg, } + icsVersion := td.getIcsVersion(chainID) switch icsVersion { case "v3", "v4": + panic("Not supported anymore") + case "v5": if td.verbose { - fmt.Println("Using 'v4' driver for chain ", chainID) + fmt.Println("Using 'v5' driver for chain ", chainID) } - target.target = v4.Commands{ - ContainerConfig: td.testCfg.containerConfig, - ValidatorConfigs: td.testCfg.validatorConfigs, - ChainConfigs: td.testCfg.chainConfigs, + target.target = v5.Commands{ + ContainerConfig: td.testCfg.ContainerConfig, + ValidatorConfigs: td.testCfg.ValidatorConfigs, + ChainConfigs: td.testCfg.ChainConfigs, Target: td.target, } + default: target.target = Commands{ - containerConfig: &td.testCfg.containerConfig, - validatorConfigs: td.testCfg.validatorConfigs, - chainConfigs: td.testCfg.chainConfigs, - target: td.target, + ContainerConfig: &td.testCfg.ContainerConfig, + ValidatorConfigs: td.testCfg.ValidatorConfigs, + ChainConfigs: td.testCfg.ChainConfigs, + Target: td.target, + } + if td.verbose { + fmt.Println("Using default driver for version", icsVersion, " for chain ", chainID) + } + } + + return target +} + +func (td *DefaultDriver) getChainDriver(chainID ChainID) e2e.ChainIF { + + var target e2e.ChainIF + + icsVersion := td.getIcsVersion(chainID) + switch icsVersion { + case "v3", "v4": + panic(fmt.Sprintf("Version %s not supported anymore", icsVersion)) + case "v5": + if td.verbose { + fmt.Println("Using 'v5' driver for chain ", chainID) + } + target = &v5.Chain{ + TestConfig: &td.testCfg, + Target: v5.Commands{ + Verbose: td.verbose, + ContainerConfig: td.testCfg.ContainerConfig, + ValidatorConfigs: td.testCfg.ValidatorConfigs, + ChainConfigs: td.testCfg.ChainConfigs, + Target: td.target, + }, + } + default: + target = &Chain{ + testConfig: &td.testCfg, + target: Commands{ + Verbose: td.verbose, + ContainerConfig: &td.testCfg.ContainerConfig, + ValidatorConfigs: td.testCfg.ValidatorConfigs, + ChainConfigs: td.testCfg.ChainConfigs, + Target: td.target, + }, } if td.verbose { fmt.Println("Using default driver for version", icsVersion, " for chain ", chainID) @@ -125,7 +171,7 @@ func (td *DefaultDriver) getState(modelState State) State { } func (td *DefaultDriver) GetChainState(chain ChainID, modelState ChainState) e2e.ChainState { - if _, exists := td.testCfg.chainConfigs[chain]; !exists { + if _, exists := td.testCfg.ChainConfigs[chain]; !exists { log.Fatalf("getting chain state failed. unknown chain: '%s'", chain) } @@ -240,7 +286,7 @@ func (td *DefaultDriver) GetChainState(chain ChainID, modelState ChainState) e2e } if *verbose { - log.Println("Done getting chain state:\n" + pretty.Sprint(chainState)) + log.Printf("Chain state for '%s':\n%s\n", chain, pretty.Sprint(chainState)) } return chainState @@ -250,7 +296,7 @@ func (td *DefaultDriver) runAction(action interface{}) error { switch action := action.(type) { case StartChainAction: target := td.getTargetDriver(action.Chain) - target.startChain(action, td.verbose) + target.StartChain(action, td.verbose) case StartSovereignChainAction: target := td.getTargetDriver(action.Chain) target.startSovereignChain(action, td.verbose) @@ -260,7 +306,7 @@ func (td *DefaultDriver) runAction(action interface{}) error { case WaitUntilBlockAction: target := td.getTargetDriver(action.Chain) target.waitUntilBlockOnChain(action) - case ChangeoverChainAction: + case e2e.ChangeoverChainAction: target := td.getTargetDriver("") target.changeoverChain(action, td.verbose) case SendTokensAction: @@ -269,29 +315,19 @@ func (td *DefaultDriver) runAction(action interface{}) error { case SubmitTextProposalAction: target := td.getTargetDriver(action.Chain) target.submitTextProposal(action, td.verbose) - case SubmitConsumerAdditionProposalAction: - target := td.getTargetDriver(action.Chain) - version := target.testConfig.providerVersion - if semver.IsValid(version) && semver.Compare(semver.Major(version), "v5") < 0 { - target.submitConsumerAdditionLegacyProposal(action, td.verbose) - } else { - target.submitConsumerAdditionProposal(action, td.verbose) - } + case e2e.SubmitConsumerAdditionProposalAction: + // use chainDriver instead of targetDriver + target := td.getChainDriver(action.Chain) + target.SubmitConsumerAdditionProposal(action, td.verbose) case SubmitConsumerRemovalProposalAction: - target := td.getTargetDriver(action.Chain) - version := target.testConfig.providerVersion - target = td.getTargetDriver(action.Chain) - if semver.IsValid(version) && semver.Compare(semver.Major(version), "v5") < 0 { - target.submitConsumerRemovalLegacyProposal(action, td.verbose) - } else { - target.submitConsumerRemovalProposal(action, td.verbose) - } + target := td.getChainDriver(action.Chain) + target.SubmitConsumerRemovalProposal(action, td.verbose) case SubmitEnableTransfersProposalAction: target := td.getTargetDriver(action.Chain) target.submitEnableTransfersProposalAction(action, td.verbose) case SubmitConsumerModificationProposalAction: target := td.getTargetDriver(action.Chain) - version := target.testConfig.providerVersion + version := target.testConfig.ProviderVersion if semver.IsValid(version) && semver.Compare(semver.Major(version), "v5") < 0 { target.submitConsumerModificationLegacyProposal(action, td.verbose) } else { @@ -301,8 +337,9 @@ func (td *DefaultDriver) runAction(action interface{}) error { target := td.getTargetDriver(action.Chain) target.voteGovProposal(action, td.verbose) case StartConsumerChainAction: - target := td.getTargetDriver(action.ProviderChain) - target.startConsumerChain(action, td.verbose) + //target := td.getTargetDriver(action.ProviderChain) + target := td.getChainDriver(action.ProviderChain) + target.StartConsumerChain(action, td.verbose) case AddChainToRelayerAction: target := td.getTargetDriver(action.Chain) target.addChainToRelayer(action, td.verbose) @@ -331,11 +368,13 @@ func (td *DefaultDriver) runAction(action interface{}) error { target := td.getTargetDriver("") target.relayRewardPacketsToProvider(action, td.verbose) case DelegateTokensAction: - target := td.getTargetDriver(action.Chain) - target.delegateTokens(action, td.verbose) + //target := td.getTargetDriver(action.Chain) + target := td.getChainDriver(action.Chain) + target.DelegateTokens(action, td.verbose) case UnbondTokensAction: - target := td.getTargetDriver(action.Chain) - target.unbondTokens(action, td.verbose) + // target := td.getTargetDriver(action.Chain) + target := td.getChainDriver(action.Chain) + target.UnbondTokens(action, td.verbose) case CancelUnbondTokensAction: target := td.getTargetDriver(action.Chain) target.cancelUnbondTokens(action, td.verbose) @@ -364,8 +403,9 @@ func (td *DefaultDriver) runAction(action interface{}) error { target := td.getTargetDriver(action.Chain) target.registerRepresentative(action, td.verbose) case e2e.AssignConsumerPubKeyAction: - target := td.getTargetDriver(ChainID("provi")) - target.assignConsumerPubKey(action, td.verbose) + target := td.getChainDriver(ChainID("provi")) + //target := td.getTargetDriver(ChainID("provi")) + target.AssignConsumerPubKey(action, td.verbose) case SlashMeterReplenishmentAction: target := td.getTargetDriver(ChainID("provi")) target.waitForSlashMeterReplenishment(action, td.verbose) @@ -386,7 +426,7 @@ func (td *DefaultDriver) runAction(action interface{}) error { target.detectConsumerEvidence(action, false, td.verbose) case SubmitChangeRewardDenomsProposalAction: target := td.getTargetDriver(action.Chain) - version := target.testConfig.providerVersion + version := target.testConfig.ProviderVersion if semver.IsValid(version) && semver.Compare(semver.Major(version), "v5") < 0 { target.submitChangeRewardDenomsLegacyProposal(action, td.verbose) } else { @@ -421,7 +461,7 @@ func (td *DefaultDriver) runAction(action interface{}) error { target := td.getTargetDriver(action.Chain) target.transferIbcToken(action, td.verbose) default: - log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.name, action) + log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.Name, action) } return nil } diff --git a/tests/e2e/test_runner.go b/tests/e2e/test_runner.go index e07360954e..be074c4409 100644 --- a/tests/e2e/test_runner.go +++ b/tests/e2e/test_runner.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "strconv" "time" ) @@ -15,6 +17,8 @@ const ( TEST_STATUS_NOTRUN = "NO RUN" ) +var runnerId uint = 0 + // A test runner drives the execution of test cases // It sets up the test environment and the test driver to run the tests type TestRunner struct { @@ -90,7 +94,7 @@ func (tr *TestRunner) Run() error { if err != nil { tr.result.Failed() // not tearing down environment for troubleshooting reasons on container - return fmt.Errorf("test run '%s' failed: %v", tr.config.name, err) + return fmt.Errorf("test run '%s' failed: %v", tr.config.Name, err) } tr.result.Passed() @@ -100,7 +104,7 @@ func (tr *TestRunner) Run() error { } func (tr *TestRunner) checkConfig() error { - tr.config.validateStringLiterals() + tr.config.ValidateStringLiterals() return nil } @@ -110,15 +114,39 @@ func (tr *TestRunner) setupEnvironment(target ExecutionTarget) error { } func (tr *TestRunner) teardownEnvironment() error { + if tr.skipCleanUp() { + fmt.Println("Skip tear down !") + return nil + } return tr.target.Stop() } +func (tr *TestRunner) skipCleanUp() bool { + if value, present := os.LookupEnv("ICS_E2E_SKIP_CLEANUP"); present { + if len(value) > 0 { + if skipCleanup, err := strconv.ParseBool(value); err == nil { + return skipCleanup + } + } + return true + } + return false +} + func (tr *TestRunner) Setup(testCfg TestConfig) error { tr.config = testCfg return nil } +func (tr *TestRunner) CleanUp() error { + if tr.skipCleanUp() { + return nil + } + return tr.target.Delete() +} + func CreateTestRunner(config TestConfig, stepChoice StepChoice, target ExecutionTarget, verbose bool) TestRunner { + return TestRunner{ target: target, stepChoice: stepChoice, @@ -137,7 +165,7 @@ Config: %s Target: %s -------------------------------------------------`, tr.stepChoice.name, - tr.config.name, + tr.config.Name, tr.target.Info(), ) } @@ -154,7 +182,7 @@ Target: %s - StartTime: %s -------------------------------------------------`, tr.stepChoice.name, - tr.config.name, + tr.config.Name, tr.target.Info(), tr.result.Status, tr.result.Result, diff --git a/tests/e2e/test_target.go b/tests/e2e/test_target.go index 140ed565e6..385965fe8a 100644 --- a/tests/e2e/test_target.go +++ b/tests/e2e/test_target.go @@ -18,6 +18,7 @@ type ExecutionTarget interface { ExecCommand(name string, arg ...string) *exec.Cmd // ExecDetachedCommand: when executed the command will be run in the background and call will return immediately ExecDetachedCommand(name string, args ...string) *exec.Cmd + UseCometMock() bool Start() error Stop() error Build() error @@ -30,6 +31,7 @@ type TargetConfig struct { useGaia bool providerVersion string consumerVersion string + useCometMock bool } type DockerContainer struct { targetConfig TargetConfig @@ -38,25 +40,42 @@ type DockerContainer struct { ImageName string } -func createTarget(testCfg TestConfig, targetCfg TargetConfig) (DockerContainer, error) { - targetCfg.providerVersion = testCfg.providerVersion - targetCfg.consumerVersion = testCfg.consumerVersion +func createTarget(testCfg TestConfig, targetCfg TargetConfig, image string) (DockerContainer, error) { + targetCfg.providerVersion = testCfg.ProviderVersion + targetCfg.consumerVersion = testCfg.ConsumerVersion target := DockerContainer{ targetConfig: targetCfg, - containerCfg: testCfg.containerConfig, + containerCfg: testCfg.ContainerConfig, } - err := target.Build() - if err != nil { - return target, fmt.Errorf("failed building target %s\n: %v", target.Info(), err) + if len(image) > 0 { + if err := target.SetImage(image); err != nil { + return target, err + } + } else { + err := target.Build() + if err != nil { + return target, fmt.Errorf("failed building target %s\n: %v", target.Info(), err) + } } return target, nil } +func (dc *DockerContainer) UseCometMock() bool { + return dc.targetConfig.useCometMock +} + func (dc *DockerContainer) GetTargetConfig() TargetConfig { return dc.targetConfig } +// Build the docker image for the target container +func (dc *DockerContainer) SetImage(image string) error { + dc.ImageName = image + // TODO: check if image exists + return nil +} + // Build the docker image for the target container func (dc *DockerContainer) Build() error { consumerVersion := dc.targetConfig.consumerVersion @@ -133,7 +152,7 @@ func (dc *DockerContainer) Delete() error { // ExecCommand returns the command struct to execute the named program with // given arguments on the current target (docker container) func (dc *DockerContainer) ExecCommand(name string, arg ...string) *exec.Cmd { - args := []string{"exec", dc.containerCfg.InstanceName, name} + args := []string{"exec", dc.containerCfg.ContainerName, name} args = append(args, arg...) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. return exec.Command("docker", args...) @@ -142,7 +161,7 @@ func (dc *DockerContainer) ExecCommand(name string, arg ...string) *exec.Cmd { // ExecDetachedCommand returns the command struct to execute the named program with // given arguments on the current target (docker container) in _detached_ mode func (dc *DockerContainer) ExecDetachedCommand(name string, arg ...string) *exec.Cmd { - args := []string{"exec", "-d", dc.containerCfg.InstanceName, name} + args := []string{"exec", "-d", dc.containerCfg.ContainerName, name} args = append(args, arg...) //#nosec G204 -- Bypass linter warning for spawning subprocess with variable return exec.Command("docker", args...) @@ -173,14 +192,14 @@ func (dc *DockerContainer) Start() error { if err := dc.Stop(); err != nil { return err } - fmt.Println("Starting container: ", dc.containerCfg.InstanceName) + fmt.Println("Starting container: ", dc.containerCfg.ContainerName) // Run new test container instance with extended privileges. // Extended privileges are granted to the container here to allow for network namespace manipulation (bringing a node up/down) // See: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities beaconScript := dc.GetTestScriptPath(false, "beacon.sh") //#nosec G204 -- subprocess launched with potential tainted input (no production code) - cmd := exec.Command("docker", "run", "--name", dc.containerCfg.InstanceName, + cmd := exec.Command("docker", "run", "--name", dc.containerCfg.ContainerName, "--cap-add=NET_ADMIN", "--privileged", dc.ImageName, "/bin/bash", beaconScript) @@ -215,16 +234,16 @@ func (dc *DockerContainer) Start() error { // Stop will stop the container and remove it func (dc *DockerContainer) Stop() error { - fmt.Println("Stopping existing containers: ", dc.containerCfg.InstanceName) + fmt.Println("Stopping existing containers: ", dc.containerCfg.ContainerName) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "stop", dc.containerCfg.InstanceName) + cmd := exec.Command("docker", "stop", dc.containerCfg.ContainerName) bz, err := cmd.CombinedOutput() if err != nil && !strings.Contains(string(bz), "No such container") { return fmt.Errorf("error stopping docker container: %v, %s", err, string(bz)) } //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd = exec.Command("docker", "rm", dc.containerCfg.InstanceName) + cmd = exec.Command("docker", "rm", dc.containerCfg.ContainerName) bz, err = cmd.CombinedOutput() if err != nil && !strings.Contains(string(bz), "No such container") { return fmt.Errorf("error removing docker container: %v, %s", err, string(bz)) @@ -253,5 +272,5 @@ Docker consumerVersion, providerVersion, dc.ImageName, - dc.containerCfg.InstanceName) + dc.containerCfg.ContainerName) } diff --git a/tests/e2e/testlib/config.go b/tests/e2e/testlib/config.go new file mode 100644 index 0000000000..f43edcc599 --- /dev/null +++ b/tests/e2e/testlib/config.go @@ -0,0 +1,183 @@ +package e2e + +import ( + "fmt" + "log" + "strconv" + "time" + + "golang.org/x/mod/semver" +) + +// hermesTemplates maps hermes configuration templates to hermes versions +var hermesTemplates = map[string]string{ + "v1.4": ` + + [[chains]] + account_prefix = "%s" + clock_drift = "5s" + gas_multiplier = 1.1 + grpc_addr = "%s" + id = "%s" + key_name = "%s" + max_gas = 20000000 + rpc_addr = "%s" + rpc_timeout = "10s" + store_prefix = "ibc" + trusting_period = "14days" + websocket_addr = "%s" + + [chains.gas_price] + denom = "stake" + price = 0.000 + + [chains.trust_threshold] + denominator = "3" + numerator = "1" + `, + // introduction of event_source + "v1.6": ` + + [[chains]] + account_prefix = "%s" + clock_drift = "5s" + gas_multiplier = 1.1 + grpc_addr = "%s" + id = "%s" + key_name = "%s" + max_gas = 20000000 + rpc_addr = "%s" + rpc_timeout = "10s" + store_prefix = "ibc" + trusting_period = "14days" + event_source = { mode = "push", url = "%s", batch_delay = "50ms" } + ccv_consumer_chain = %v + + [chains.gas_price] + denom = "stake" + price = 0.000 + + [chains.trust_threshold] + denominator = "3" + numerator = "1" + `, +} + +type TestConfig struct { + // These are the non altered values during a typical test run, where multiple test runs can exist + // to validate different action sequences and corresponding state checks. + ContainerConfig ContainerConfig + ValidatorConfigs map[ValidatorID]ValidatorConfig + ChainConfigs map[ChainID]ChainConfig + ConsumerChains map[ConsumerID]ChainConfig + ProviderVersion string + ConsumerVersion string + // override config.toml parameters + // usually used to override timeout_commit + // having shorter timeout_commit reduces the test runtime because blocks are produced faster + // lengthening the timeout_commit increases the test runtime because blocks are produced slower but the test is more reliable + TendermintConfigOverride string + UseCometmock bool // if false, nodes run CometBFT + UseGorelayer bool // if false, Hermes is used as the relayer + // chains which are running, i.e. producing blocks, at the moment + RunningChains map[ChainID]bool + // Used with CometMock. The time by which chains have been advanced. Used to keep chains in sync: when a new chain is started, advance its time by this value to keep chains in sync. + TimeOffset time.Duration + TransformGenesis bool + Name string +} + +// Initialize initializes the TestConfig instance by setting the runningChains field to an empty map. +func (tr *TestConfig) Initialize() { + tr.RunningChains = make(map[ChainID]bool) +} + +func (s *TestConfig) SetCometMockConfig(useCometmock bool) { + s.UseCometmock = useCometmock +} + +func (s *TestConfig) SetRelayerConfig(useRly bool) { + s.UseGorelayer = useRly +} + +// ValidateStringLiterals enforces that configs follow the constraints +// necessary to execute the tests +// +// Note: Network interfaces (name of virtual ethernet interfaces for ip link) +// within the container will be named as "$CHAIN_ID-$VAL_ID-out" etc. +// where this name is constrained to 15 bytes or less. Therefore each string literal +// used as a validatorID or chainID needs to be 5 char or less. +func (s *TestConfig) ValidateStringLiterals() { + for valID, valConfig := range s.ValidatorConfigs { + if len(valID) > 5 { + panic("validator id string literal must be 5 char or less") + } + + ipSuffix, err := strconv.Atoi(valConfig.IpSuffix) + if err != nil { + panic(fmt.Sprintf("ip suffix must be an int: %v\n", err)) + } + + if ipSuffix == 253 { + panic("ip suffix 253 is reserved for query node") + } + + if ipSuffix == 252 { + panic("ip suffix 252 is reserved for double signing node") + } + + if ipSuffix < 1 || 251 < ipSuffix { + panic("ip suffix out of range, need to change config") + } + } + + for chainID, chainConfig := range s.ChainConfigs { + if len(chainID) > 5 { + panic(fmt.Sprintf("chain id string literal must be 5 char or less: %s", chainID)) + } + + if chainID != chainConfig.ChainId { + log.Println("chain config is mapped to a chain id that is different than what's stored in the config") + } + } +} + +// GetHermesConfig returns a configuration string for a given hermes version +// +// Currently templates for Hermes v1.6.0 and v1.4 are supported. +// If provided version is before v1.6.0 then a configuration based on template for v1.4.x is returned +// otherwise the returned configuration is based on template v1.4. +func GetHermesConfig(hermesVersion, queryNodeIP string, chainCfg ChainConfig, isConsumer bool) string { + + ChainId := chainCfg.ChainId + keyName := "query" + rpcAddr := "http://" + queryNodeIP + ":26658" + grpcAddr := "tcp://" + queryNodeIP + ":9091" + wsAddr := "ws://" + queryNodeIP + ":26658/websocket" + + hermesConfig := "" + if semver.Compare(hermesVersion, "1.6.0") < 0 { + fmt.Println("Using hermes config template", "1.4") + template := hermesTemplates["v1.4"] + hermesConfig = fmt.Sprintf(template, + chainCfg.AccountPrefix, + grpcAddr, + ChainId, + keyName, + rpcAddr, + wsAddr) + } else { + // added event_source (v1.6) + ccv_consumer_chain (v1.5) + fmt.Println("Using hermes config template", "1.6") + template := hermesTemplates["v1.6"] + hermesConfig = fmt.Sprintf(template, + chainCfg.AccountPrefix, + grpcAddr, + ChainId, + keyName, + rpcAddr, + wsAddr, + isConsumer) + } + return hermesConfig +} diff --git a/tests/e2e/testlib/types.go b/tests/e2e/testlib/types.go index 0fd7ada259..f5fce3df09 100644 --- a/tests/e2e/testlib/types.go +++ b/tests/e2e/testlib/types.go @@ -8,6 +8,7 @@ import ( "time" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" ) type ( @@ -27,7 +28,84 @@ type AssignConsumerPubKeyAction struct { ExpectedError string } +type SubmitConsumerAdditionProposalAction struct { + PreCCV bool + Chain ChainID + From ValidatorID + Deposit uint + ConsumerChain ChainID + SpawnTime uint + InitialHeight clienttypes.Height + DistributionChannel string + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string + MinStake uint64 + AllowInactiveVals bool + Prioritylist []string +} + +type SubmitConsumerRemovalProposalAction struct { + Chain ChainID + From ValidatorID + Deposit uint + ConsumerChain ChainID + StopTimeOffset time.Duration // offset from time.Now() +} + +type StartChainAction struct { + Chain ChainID + Validators []StartChainValidator + // Genesis changes specific to this action, appended to genesis changes defined in chain config + GenesisChanges string + IsConsumer bool +} + +type StartChainValidator struct { + Id ValidatorID + Allocation uint + Stake uint +} + +type StartConsumerChainAction struct { + ConsumerChain ChainID + ProviderChain ChainID + Validators []StartChainValidator + GenesisChanges string +} + +type ChangeoverChainAction struct { + SovereignChain ChainID + ProviderChain ChainID + Validators []StartChainValidator + GenesisChanges string +} + +type StartSovereignChainAction struct { + Chain ChainID + Validators []StartChainValidator + // Genesis changes specific to this action, appended to genesis changes defined in chain config + GenesisChanges string +} + +type DelegateTokensAction struct { + Chain ChainID + From ValidatorID + To ValidatorID + Amount uint +} + +type UnbondTokensAction struct { + Chain ChainID + Sender ValidatorID + UnbondFrom ValidatorID + Amount uint +} + type ChainCommands interface { + // State commands - functions use by test driver to get state information GetBlockHeight(chain ChainID) uint GetBalance(chain ChainID, validator ValidatorID) uint GetConsumerChains(chain ChainID) map[ChainID]bool @@ -51,16 +129,36 @@ type ChainCommands interface { GetQueryNodeIP(chain ChainID) string GetInflationRate(chain ChainID) float64 GetConsumerCommissionRate(chain ChainID, validator ValidatorID) float64 - // Action commands - AssignConsumerPubKey(action AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) + QueryTransaction(chain ChainID, txhash string) ([]byte, error) + + CreateConsumer(providerChain, consumerChain ChainID, validator ValidatorID, metadata types.ConsumerMetadata, initParams *types.ConsumerInitializationParameters, powerShapingParams *types.PowerShapingParameters) ([]byte, error) + UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer, verbose bool) ([]byte, error) + SubmitGovProposal(chain ChainID, from ValidatorID, command string, proposal string, verbose bool) ([]byte, error) + AssignConsumerPubKey(identifier string, pubKey string, from ValidatorID, gas, home, node string, verbose bool) ([]byte, error) } +type ActionCommands interface { + SubmitConsumerAdditionProposal(action SubmitConsumerAdditionProposalAction, verbose bool) + AssignConsumerPubKey(action AssignConsumerPubKeyAction, verbose bool) + StartChain(action StartChainAction, verbose bool) + StartConsumerChain(action StartConsumerChainAction, verbose bool) + SubmitConsumerRemovalProposal(action SubmitConsumerRemovalProposalAction, verbose bool) + DelegateTokens(action DelegateTokensAction, verbose bool) + UnbondTokens(action UnbondTokensAction, verbose bool) +} +type ChainIF interface { + ActionCommands +} + +type ActionHandler func(action interface{}, verbose bool) error + // TODO: replace ExecutionTarget with new TargetDriver interface type PlatformDriver interface { ExecCommand(name string, arg ...string) *exec.Cmd // ExecDetachedCommand: when executed the command will be run in the background and call will return immediately ExecDetachedCommand(name string, args ...string) *exec.Cmd GetTestScriptPath(isConsumer bool, script string) string + UseCometMock() bool } type TargetDriver interface { // ChainCommands @@ -71,7 +169,6 @@ type TargetDriver interface { // TODO: this should not be here. mv 'Now' to a better suited type here and then move ContainerConfig back type ContainerConfig struct { ContainerName string - InstanceName string CcvVersion string Now time.Time } diff --git a/tests/e2e/testlib/utils.go b/tests/e2e/testlib/utils.go index 081142d5be..abadcac034 100644 --- a/tests/e2e/testlib/utils.go +++ b/tests/e2e/testlib/utils.go @@ -23,6 +23,13 @@ type GovernanceProposal struct { Expedited bool `json:"expedited"` } +type TxResponse struct { + TxHash string `json:"txhash"` + Code int `json:"code"` + RawLog string `json:"raw_log"` + Events []sdk.Event `json:"events"` +} + // 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 @@ -55,6 +62,16 @@ func GenerateGovProposalContent(title, summary, metadata, deposit, description s return string(raw) } +func GetTxResponse(rawResponse []byte) TxResponse { + txResponse := &TxResponse{} + err := json.Unmarshal(rawResponse, txResponse) + if err != nil { + log.Fatalf("failed unmarshalling tx response: %s, json: %s", + err.Error(), string(rawResponse)) + } + return *txResponse +} + func ExecuteCommand(cmd *exec.Cmd, cmdName string, verbose bool) { if verbose { fmt.Println(cmdName+" cmd:", cmd.String()) @@ -82,3 +99,11 @@ func ExecuteCommand(cmd *exec.Cmd, cmdName string, verbose bool) { log.Fatal(err) } } + +func UintPtr(i uint) *uint { + return &i +} + +func IntPtr(i int) *int { + return &i +} diff --git a/tests/e2e/v4/actions.go b/tests/e2e/v4/actions.go deleted file mode 100644 index 6348b3d4c4..0000000000 --- a/tests/e2e/v4/actions.go +++ /dev/null @@ -1,33 +0,0 @@ -package v4 - -import ( - "fmt" - - e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" -) - -func (tr Commands) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) { - - 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.ChainConfigs[ChainID("provi")].BinaryName, - string(tr.ChainConfigs[action.Chain].ChainId), - action.ConsumerPubkey, - action.Validator, - tr.ChainConfigs[ChainID("provi")].ChainId, - home, - node, - gas, - ) - - cmd := tr.ExecCommand( - "/bin/bash", "-c", - assignKey, - ) - - if verbose { - fmt.Println("assignConsumerPubKey cmd:", cmd.String()) - } - - return cmd.CombinedOutput() -} diff --git a/tests/e2e/v5/actions.go b/tests/e2e/v5/actions.go new file mode 100644 index 0000000000..8dd3a5f1ce --- /dev/null +++ b/tests/e2e/v5/actions.go @@ -0,0 +1,2577 @@ +package v5 + +import ( + "bufio" + "encoding/json" + "fmt" + "log" + "math" + "os" + "os/exec" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/tidwall/gjson" + "golang.org/x/mod/semver" + + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/client" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" +) + +const ( + done = "done!!!!!!!!" + + VLatest = "latest" + V400 = "v4.0.0" + V330 = "v3.3.0" + V300 = "v3.0.0" +) + +type ( + TestConfig = e2e.TestConfig +) + +// type aliases +type ( + AssignConsumerPubKeyAction = e2e.AssignConsumerPubKeyAction + StartChainAction = e2e.StartChainAction + StartChainValidator = e2e.StartChainValidator + StartConsumerChainAction = e2e.StartConsumerChainAction + StartSovereignChainAction = e2e.StartSovereignChainAction + SubmitConsumerRemovalProposalAction = e2e.SubmitConsumerRemovalProposalAction + DelegateTokensAction = e2e.DelegateTokensAction + ChangeoverChainAction = e2e.ChangeoverChainAction + UnbondTokensAction = e2e.UnbondTokensAction +) + +type SendTokensAction struct { + Chain ChainID + From ValidatorID + To ValidatorID + Amount uint +} + +type Chain struct { + Target e2e.TargetDriver + TestConfig *e2e.TestConfig +} + +func (tr Chain) sendTokens( + action SendTokensAction, + verbose bool, +) { + fromValCfg := tr.TestConfig.ValidatorConfigs[action.From] + toValCfg := tr.TestConfig.ValidatorConfigs[action.To] + fromAddress := fromValCfg.DelAddress + toAddress := toValCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if fromValCfg.UseConsumerKey { + fromAddress = fromValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + fromAddress = fromValCfg.DelAddressOnConsumer + } + if toValCfg.UseConsumerKey { + toAddress = toValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + toAddress = toValCfg.DelAddressOnConsumer + } + } + + binaryName := tr.TestConfig.ChainConfigs[action.Chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "tx", "bank", "send", + fromAddress, + toAddress, + fmt.Sprint(action.Amount)+`stake`, + + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ) + if verbose { + fmt.Println("sendTokens cmd:", cmd.String()) + } + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 30*time.Second) +} + +func (tr *Chain) StartChain( + action StartChainAction, + verbose bool, +) { + chainConfig := tr.TestConfig.ChainConfigs[action.Chain] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.Validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.TestConfig.ValidatorConfigs[val.Id].Mnemonic, + NodeKey: tr.TestConfig.ValidatorConfigs[val.Id].NodeKey, + ValId: fmt.Sprint(val.Id), + PrivValidatorKey: tr.TestConfig.ValidatorConfigs[val.Id].PrivValidatorKey, + Allocation: fmt.Sprint(val.Allocation) + "stake", + Stake: fmt.Sprint(val.Stake) + "stake", + IpSuffix: tr.TestConfig.ValidatorConfigs[val.Id].IpSuffix, + + ConsumerMnemonic: tr.TestConfig.ValidatorConfigs[val.Id].ConsumerMnemonic, + ConsumerPrivValidatorKey: tr.TestConfig.ValidatorConfigs[val.Id].ConsumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.TestConfig.ValidatorConfigs[val.Id].UseConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.GenesisChanges != "" { + genesisChanges = chainConfig.GenesisChanges + " | " + action.GenesisChanges + } else { + genesisChanges = chainConfig.GenesisChanges + } + + var cometmockArg string + if tr.TestConfig.UseCometmock { + cometmockArg = "true" + } else { + cometmockArg = "false" + } + + chainHome := string(action.Chain) + startChainScript := tr.Target.GetTestScriptPath(action.IsConsumer, "start-chain.sh") + cmd := tr.Target.ExecCommand("/bin/bash", + startChainScript, chainConfig.BinaryName, string(vals), + string(chainConfig.ChainId), chainConfig.IpPrefix, genesisChanges, + fmt.Sprint(action.IsConsumer), + // override config/config.toml for each node on chain + // usually timeout_commit and peer_gossip_sleep_duration are changed to vary the test run duration + // lower timeout_commit means the blocks are produced faster making the test run shorter + // with short timeout_commit (eg. timeout_commit = 1s) some nodes may miss blocks causing the test run to fail + tr.TestConfig.TendermintConfigOverride, + cometmockArg, + chainHome, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startChain: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + tr.addChainToRelayer(AddChainToRelayerAction{ + Chain: action.Chain, + Validator: action.Validators[0].Id, + IsConsumer: action.IsConsumer, + }, verbose) + + // store the fact that we started the chain + tr.TestConfig.RunningChains[action.Chain] = true + fmt.Println("Started chain", action.Chain) + if tr.TestConfig.TimeOffset != 0 { + // advance time for this chain so that it is in sync with the rest of the network + tr.AdvanceTimeForChain(action.Chain, tr.TestConfig.TimeOffset) + } +} + +type SubmitTextProposalAction struct { + Chain ChainID + From ValidatorID + Deposit uint + Title string + Description string +} + +func (tr Chain) submitTextProposal( + action SubmitTextProposalAction, + verbose bool, +) { + // TEXT PROPOSAL + bz, err := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-legacy-proposal", + `--title`, action.Title, + `--description`, action.Description, + `--deposit`, fmt.Sprint(action.Deposit)+`stake`, + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 1, 10*time.Second) +} + +func (tr Chain) SubmitConsumerAdditionProposal( + action e2e.SubmitConsumerAdditionProposalAction, + verbose bool, +) { + spawnTime := tr.TestConfig.ContainerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) + params := ccvtypes.DefaultParams() + prop := client.ConsumerAdditionProposalJSON{ + Title: "Propose the addition of a new chain", + Summary: "Gonna be a great chain", + ChainId: string(tr.TestConfig.ChainConfigs[action.ConsumerChain].ChainId), + InitialHeight: action.InitialHeight, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: spawnTime, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + UnbondingPeriod: params.UnbondingPeriod, + Deposit: fmt.Sprint(action.Deposit) + `stake`, + DistributionTransmissionChannel: action.DistributionChannel, + TopN: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + } + + bz, err := json.Marshal(prop) + if err != nil { + log.Fatal(err) + } + + jsonStr := string(bz) + if strings.Contains(jsonStr, "'") { + log.Fatal("prop json contains single quote") + } + + //#nosec G204 -- bypass unsafe quoting warning (no production code) + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json")) + bz, err = cmd.CombinedOutput() + if verbose { + log.Println("submitConsumerAdditionProposal cmd: ", cmd.String()) + } + + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // CONSUMER ADDITION PROPOSAL + cmd = tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-legacy-proposal", "consumer-addition", "/temp-proposal.json", + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("submitConsumerAdditionProposal cmd:", cmd.String()) + fmt.Println("submitConsumerAdditionProposal json:", jsonStr) + } + bz, err = cmd.CombinedOutput() + + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if verbose { + fmt.Println("submitConsumerAdditionProposal output:", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 10*time.Second) +} + +func (tr Chain) SubmitConsumerRemovalProposal( + action SubmitConsumerRemovalProposalAction, + verbose bool, +) { + stopTime := tr.TestConfig.ContainerConfig.Now.Add(action.StopTimeOffset) + prop := client.ConsumerRemovalProposalJSON{ + Title: fmt.Sprintf("Stop the %v chain", action.ConsumerChain), + Summary: "It was a great chain", + ChainId: string(tr.TestConfig.ChainConfigs[action.ConsumerChain].ChainId), + StopTime: stopTime, + Deposit: fmt.Sprint(action.Deposit) + `stake`, + } + + bz, err := json.Marshal(prop) + if err != nil { + log.Fatal(err) + } + + jsonStr := string(bz) + if strings.Contains(jsonStr, "'") { + log.Fatal("prop json contains single quote") + } + + bz, err = tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json")).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + bz, err = tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-legacy-proposal", "consumer-removal", + "/temp-proposal.json", + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--gas`, "900000", + `--keyring-backend`, `test`, + `-o`, `json`, + `-y`, + ).CombinedOutput() + if err != nil { + log.Fatalf("error submitting consumer-removal proposal %s\n%s\n", + err, string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitForTx(ChainID("provi"), bz, 20*time.Second) +} + +type SubmitConsumerModificationProposalAction struct { + Chain ChainID + From ValidatorID + Deposit uint + ConsumerChain ChainID + TopN uint32 + ValidatorsPowerCap uint32 + ValidatorSetCap uint32 + Allowlist []string + Denylist []string +} + +func (tr Chain) submitConsumerModificationProposal( + action SubmitConsumerModificationProposalAction, + verbose bool, +) { + prop := client.ConsumerModificationProposalJSON{ + Title: "Propose the modification of the PSS parameters of a chain", + Summary: "summary of a modification proposal", + ChainId: string(tr.TestConfig.ChainConfigs[action.ConsumerChain].ChainId), + Deposit: fmt.Sprint(action.Deposit) + `stake`, + TopN: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + } + + bz, err := json.Marshal(prop) + if err != nil { + log.Fatal(err) + } + + jsonStr := string(bz) + if strings.Contains(jsonStr, "'") { + log.Fatal("prop json contains single quote") + } + + //#nosec G204 -- bypass unsafe quoting warning (no production code) + bz, err = tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json"), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // CONSUMER MODIFICATION PROPOSAL + cmd := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-legacy-proposal", "consumer-modification", "/temp-proposal.json", + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ) + if verbose { + log.Println("submitConsumerModificationProposal cmd: ", cmd.String()) + log.Println("submitConsumerModificationProposal json: ", jsonStr) + } + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if verbose { + log.Println("submitConsumerModificationProposal output: ", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 10*time.Second) +} + +type SubmitEnableTransfersProposalAction struct { + Chain ChainID + From ValidatorID + Title string + Deposit uint +} + +func (tr Chain) submitEnableTransfersProposalAction( + action SubmitEnableTransfersProposalAction, + verbose bool, +) { + // 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) + + //#nosec G204 -- bypass unsafe quoting warning (no production code) + bz, err := tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/params-proposal.json"), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + cmd := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-proposal", "/params-proposal.json", + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--gas`, "900000", + `--keyring-backend`, `test`, + `-y`, + ) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 60*time.Second) +} + +type VoteGovProposalAction struct { + Chain ChainID + From []ValidatorID + Vote []string + PropNumber uint +} + +func (tr *Chain) voteGovProposal( + action VoteGovProposalAction, + verbose bool, +) { + var wg sync.WaitGroup + for i, val := range action.From { + wg.Add(1) + vote := action.Vote[i] + go func(val ValidatorID, vote string) { + defer wg.Done() + bz, err := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + + "tx", "gov", "vote", + fmt.Sprint(action.PropNumber), vote, + + `--from`, `validator`+fmt.Sprint(val), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, val), + `--node`, tr.getValidatorNode(action.Chain, val), + `--keyring-backend`, `test`, + `--gas`, "900000", + `-y`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + }(val, vote) + } + + wg.Wait() + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 1, 10*time.Second) + tr.WaitTime(time.Duration(tr.TestConfig.ChainConfigs[action.Chain].VotingWaitTime) * time.Second) +} + +func (tr *Chain) StartConsumerChain( + action StartConsumerChainAction, + verbose bool, +) { + fmt.Println("Starting consumer chain ", action.ConsumerChain) + consumerGenesis := ".app_state.ccvconsumer = " + tr.getConsumerGenesis(action.ProviderChain, action.ConsumerChain) + consumerGenesisChanges := tr.TestConfig.ChainConfigs[action.ConsumerChain].GenesisChanges + if consumerGenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + } + if action.GenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + action.GenesisChanges + } + + tr.StartChain(StartChainAction{ + Chain: action.ConsumerChain, + Validators: action.Validators, + GenesisChanges: consumerGenesis, + IsConsumer: true, + }, verbose) +} + +// Get consumer genesis from provider +func (tr *Chain) getConsumerGenesis(providerChain, consumerChain ChainID) string { + fmt.Println("Exporting consumer genesis from provider") + providerBinaryName := tr.TestConfig.ChainConfigs[providerChain].BinaryName + + cmd := tr.Target.ExecCommand( + providerBinaryName, + + "query", "provider", "consumer-genesis", + string(tr.TestConfig.ChainConfigs[consumerChain].ChainId), + + `--node`, tr.Target.GetQueryNode(providerChain), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if tr.TestConfig.TransformGenesis || needsGenesisTransform(*tr.TestConfig) { + return string(tr.transformConsumerGenesis(consumerChain, bz)) + } else { + fmt.Println("No genesis transformation performed") + } + return string(bz) +} + +// needsGenesisTransform tries to identify if a genesis transformation should be performed +func needsGenesisTransform(cfg TestConfig) bool { + // no genesis transformation needed for same versions + if cfg.ConsumerVersion == cfg.ProviderVersion { + return false + } + + // use v4.0.0 (after genesis transform breakages) for the checks if 'latest' is used + consumerVersion := cfg.ConsumerVersion + if cfg.ConsumerVersion == VLatest { + consumerVersion = V400 + } + providerVersion := cfg.ProviderVersion + if cfg.ProviderVersion == VLatest { + providerVersion = V400 + } + + if !semver.IsValid(consumerVersion) || !semver.IsValid(providerVersion) { + fmt.Printf("unable to identify the need for genesis transformation: invalid sem-version: consumer='%s', provider='%s'", + consumerVersion, providerVersion) + return false + } + + breakages := []string{V300, V330, V400} + for _, breakage := range breakages { + if (semver.Compare(consumerVersion, breakage) < 0 && semver.Compare(providerVersion, breakage) >= 0) || + (semver.Compare(providerVersion, breakage) < 0 && semver.Compare(consumerVersion, breakage) >= 0) { + fmt.Println("genesis transformation needed for versions:", providerVersion, consumerVersion) + return true + } + } + fmt.Println("NO genesis transformation needed for versions:", providerVersion, consumerVersion) + return false +} + +// getTransformParameter identifies the needed transformation parameter for current `transformGenesis` implementation +// based on consumer and provider versions. +func getTransformParameter(consumerVersion string) (string, error) { + switch consumerVersion { + case "": + // For "" (default: local workspace) use HEAD as reference point + consumerVersion = "HEAD" + case VLatest: + // For 'latest' originated from latest-image use "origin/main" as ref point + consumerVersion = "origin/main" + } + + // Hash of breakage due to preHashKey release in version 2.x + // ics23/go v.0.10.0 adding 'prehash_key_before_comparison' in ProofSpec + breakage_prehash := "d4dde74b062c2fded0d3b3dbef4b3b0229e317f3" // first released in v3.2.0-consumer + + // breakage 2: split of genesis + breakage_splitgenesisMain := "946f6ec626d3de3fe2e00cbb386ccf9c2f05d94d" + breakage_splitgenesisV33x := "1d2641a3b2ba706ae0a307d9019b48c62d86133b" + + // breakage 3: split of genesis + delay_period + breakage_retry_delay := "88499b7c650ea0fb2c448af2b182ad5fee94d795" + + // mapping of the accepted parameter values of the `genesis transform` command + // to the related git refs introducing a breakage + transformParams := map[string][]string{ + "v2.x": {breakage_prehash}, + "v3.3.x": {breakage_splitgenesisMain, breakage_splitgenesisV33x}, + "v4.x": {breakage_retry_delay}, + } + + // set default consumer target version to "v4.x" + // and iterate in order of breakage history [oldest first] to identify + // the "--to" target for consumer version used + targetVersion := "v4.x" + keys := make([]string, 0, len(transformParams)) + for k := range transformParams { + keys = append(keys, k) + } + sort.Slice(keys, func(k, l int) bool { return keys[k] < keys[l] }) + + for _, version := range keys { + for _, breakageHash := range transformParams[version] { + // Check if the 'breakage' is an ancestor of the 'consumerVersion' + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments + cmd := exec.Command("git", "merge-base", "--is-ancestor", breakageHash, consumerVersion) + fmt.Println("running ", cmd) + out, err := cmd.CombinedOutput() + if err == nil { + // breakage is already part of the consumer version -> goto next breakage + fmt.Println(" consumer >= breakage ", transformParams[version], " ... going to next one") + targetVersion = version + break + } + + if rc, ok := err.(*exec.ExitError); ok { + if rc.ExitCode() != 1 { + return "", fmt.Errorf("error identifying transform parameter '%v': %s", err, string(out)) + } + // not an ancestor -- ignore this breakage + fmt.Println("breakage :", transformParams[version], " is not an ancestor of version ", version) + continue + } + return "", fmt.Errorf("unexpected error when running '%v': %v", cmd, err) // unable to get return code + } + } + // consumer > latest known breakage (use default target version 'v4.x') + return fmt.Sprintf("--to=%s", targetVersion), nil +} + +// Transform consumer genesis content from older version +func (tr *Chain) transformConsumerGenesis(consumerChain ChainID, genesis []byte) []byte { + fmt.Println("Transforming consumer genesis") + + fileName := "consumer_genesis.json" + file, err := os.CreateTemp("", fileName) + if err != nil { + panic(fmt.Sprintf("failed writing ccv consumer file : %v", err)) + } + defer file.Close() + err = os.WriteFile(file.Name(), genesis, 0o600) + if err != nil { + log.Panicf("Failed writing consumer genesis to file: %v", err) + } + + containerInstance := tr.TestConfig.ContainerConfig.ContainerName + targetFile := fmt.Sprintf("/tmp/%s", fileName) + sourceFile := file.Name() + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "cp", sourceFile, + fmt.Sprintf("%s:%s", containerInstance, targetFile)) + genesis, err = cmd.CombinedOutput() + if err != nil { + log.Panic(err, "\n", string(genesis)) + } + + // check if genesis transform supports --to target + bz, err := tr.Target.ExecCommand( + "interchain-security-transformer", + "genesis", "transform", "--to").CombinedOutput() + if err != nil && !strings.Contains(string(bz), "unknown flag: --to") { + targetVersion, err := getTransformParameter(tr.TestConfig.ConsumerVersion) + if err != nil { + log.Panic("Failed getting genesis transformation parameter: ", err) + } + cmd = tr.Target.ExecCommand( + "interchain-security-transformer", + "genesis", "transform", targetVersion, targetFile) + } else { + cmd = tr.Target.ExecCommand( + "interchain-security-transformer", + "genesis", "transform", targetFile) + } + + result, err := cmd.CombinedOutput() + if err != nil { + log.Panic(err, "CCV consumer genesis transformation failed: %s", string(result)) + } + return result +} + +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 = " + string(bz) + consumerGenesisChanges := tr.TestConfig.ChainConfigs[action.SovereignChain].GenesisChanges + if consumerGenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + } + if action.GenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + action.GenesisChanges + } + + tr.startChangeover(ChangeoverChainAction{ + Validators: action.Validators, + GenesisChanges: consumerGenesis, + }, verbose) +} + +func (tr Chain) startChangeover( + action ChangeoverChainAction, + verbose bool, +) { + chainConfig := tr.TestConfig.ChainConfigs[ChainID("sover")] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.Validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.TestConfig.ValidatorConfigs[val.Id].Mnemonic, + NodeKey: tr.TestConfig.ValidatorConfigs[val.Id].NodeKey, + ValId: fmt.Sprint(val.Id), + PrivValidatorKey: tr.TestConfig.ValidatorConfigs[val.Id].PrivValidatorKey, + Allocation: fmt.Sprint(val.Allocation) + "stake", + Stake: fmt.Sprint(val.Stake) + "stake", + IpSuffix: tr.TestConfig.ValidatorConfigs[val.Id].IpSuffix, + + ConsumerMnemonic: tr.TestConfig.ValidatorConfigs[val.Id].ConsumerMnemonic, + ConsumerPrivValidatorKey: tr.TestConfig.ValidatorConfigs[val.Id].ConsumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.TestConfig.ValidatorConfigs[val.Id].UseConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.GenesisChanges != "" { + genesisChanges = chainConfig.GenesisChanges + " | " + action.GenesisChanges + } else { + genesisChanges = chainConfig.GenesisChanges + } + + isConsumer := true + changeoverScript := tr.Target.GetTestScriptPath(isConsumer, "start-changeover.sh") + cmd := tr.Target.ExecCommand( + "/bin/bash", + changeoverScript, chainConfig.UpgradeBinary, string(vals), + "sover", chainConfig.IpPrefix, genesisChanges, + tr.TestConfig.TendermintConfigOverride, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startChangeover: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal("startChangeover died", err) + } +} + +type AddChainToRelayerAction struct { + Chain ChainID + Validator ValidatorID + IsConsumer bool +} + +// Set up the config for a new chain for gorelayer. +// This config is added to the container as a file. +// We then add the chain to the relayer, using this config as the chain config with `rly chains add --file` +// This is functionally similar to the config used by Hermes for chains, e.g. gas is free. +const gorelayerChainConfigTemplate = ` +{ + "type": "cosmos", + "value": { + "key": "default", + "chain-id": "%s", + "rpc-addr": "%s", + "account-prefix": "%s", + "keyring-backend": "test", + "gas-adjustment": 1.2, + "gas-prices": "0.00stake", + "debug": true, + "timeout": "20s", + "output-format": "json", + "sign-mode": "direct" + } +}` + +func (tr Chain) addChainToRelayer( + action AddChainToRelayerAction, + verbose bool, +) { + if !tr.TestConfig.UseGorelayer { + tr.addChainToHermes(action, verbose) + } else { + tr.addChainToGorelayer(action, verbose) + } +} + +func (tr Chain) addChainToGorelayer( + action AddChainToRelayerAction, + verbose bool, +) { + queryNodeIP := tr.Target.GetQueryNodeIP(action.Chain) + ChainId := tr.TestConfig.ChainConfigs[action.Chain].ChainId + rpcAddr := "http://" + queryNodeIP + ":26658" + + chainConfig := fmt.Sprintf(gorelayerChainConfigTemplate, + ChainId, + rpcAddr, + tr.TestConfig.ChainConfigs[action.Chain].AccountPrefix, + ) + + bz, err := tr.Target.ExecCommand( + "rly", "config", "init").CombinedOutput() + if err != nil && !strings.Contains(string(bz), "config already exists") { + log.Fatal(err, "\n", string(bz)) + } + + chainConfigFileName := fmt.Sprintf("/root/%s_config.json", ChainId) + + bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, chainConfigFileName) + bz, err = tr.Target.ExecCommand("bash", "-c", + bashCommand).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + addChainCommand := tr.Target.ExecCommand("rly", "chains", "add", "--file", chainConfigFileName, string(ChainId)) + e2e.ExecuteCommand(addChainCommand, "add chain", verbose) + + keyRestoreCommand := tr.Target.ExecCommand("rly", "keys", "restore", string(ChainId), "default", tr.TestConfig.ValidatorConfigs[action.Validator].Mnemonic) + e2e.ExecuteCommand(keyRestoreCommand, "restore keys", verbose) +} + +func (tr Chain) addChainToHermes( + action AddChainToRelayerAction, + verbose bool, +) { + + bz, err := tr.Target.ExecCommand("bash", "-c", "hermes", "version").CombinedOutput() + if err != nil { + log.Fatal(err, "\n error getting hermes version", string(bz)) + } + re := regexp.MustCompile(`hermes\s+(\d+.\d+.\d+)`) + match := re.FindStringSubmatch(string(bz)) + if match == nil { + log.Fatalln("error identifying hermes version from", string(bz)) + } + + hermesVersion := match[1] + queryNodeIP := tr.Target.GetQueryNodeIP(action.Chain) + hermesConfig := e2e.GetHermesConfig(hermesVersion, queryNodeIP, tr.TestConfig.ChainConfigs[action.Chain], action.IsConsumer) + + bashCommand := fmt.Sprintf(`echo '%s' >> %s`, hermesConfig, "/root/.hermes/config.toml") + + bz, err = tr.Target.ExecCommand("bash", "-c", bashCommand).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // Save mnemonic to file within container + var mnemonic string + if tr.TestConfig.ValidatorConfigs[action.Validator].UseConsumerKey && action.IsConsumer { + mnemonic = tr.TestConfig.ValidatorConfigs[action.Validator].ConsumerMnemonic + } else { + mnemonic = tr.TestConfig.ValidatorConfigs[action.Validator].Mnemonic + } + + saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") + fmt.Println("Add to hermes", action.Validator) + bz, err = tr.Target.ExecCommand("bash", "-c", saveMnemonicCommand).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + bz, err = tr.Target.ExecCommand("hermes", + "keys", "add", + "--chain", string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + "--mnemonic-file", "/root/.hermes/mnemonic.txt", + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } +} + +// This config file is used by gorelayer to create a path between chains. +// Since the tests assume we use a certain client-id for certain paths, +// in the config we specify the client id, e.g. 07-tendermint-5. +// The src-channel-filter is empty because we want to relay all channels. +const gorelayerPathConfigTemplate = `{ + "src": { + "chain-id": "%s", + "client-id": "07-tendermint-%v" + }, + "dst": { + "chain-id": "%s", + "client-id": "07-tendermint-%v" + }, + "src-channel-filter": { + "rule": "", + "channel-list": [] + } +} +` + +type AddIbcConnectionAction struct { + ChainA ChainID + ChainB ChainID + ClientA uint + ClientB uint +} + +func (tr Chain) addIbcConnection( + action AddIbcConnectionAction, + verbose bool, +) { + if !tr.TestConfig.UseGorelayer { + tr.addIbcConnectionHermes(action, verbose) + } else { + tr.addIbcConnectionGorelayer(action, verbose) + } +} + +func (tr Chain) addIbcConnectionGorelayer( + action AddIbcConnectionAction, + verbose bool, +) { + pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) + + pathConfig := fmt.Sprintf(gorelayerPathConfigTemplate, action.ChainA, action.ClientA, action.ChainB, action.ClientB) + + pathConfigFileName := fmt.Sprintf("/root/%s_config.json", pathName) + + bashCommand := fmt.Sprintf(`echo '%s' >> %s`, pathConfig, pathConfigFileName) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + pathConfigCommand := tr.Target.ExecCommand("bash", "-c", bashCommand) + e2e.ExecuteCommand(pathConfigCommand, "add path config", verbose) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newPathCommand := tr.Target.ExecCommand("rly", + "paths", "add", + string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + string(tr.TestConfig.ChainConfigs[action.ChainB].ChainId), + pathName, + "--file", pathConfigFileName, + ) + + e2e.ExecuteCommand(newPathCommand, "new path", verbose) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newClientsCommand := tr.Target.ExecCommand("rly", "transact", "clients", pathName) + + e2e.ExecuteCommand(newClientsCommand, "new clients", verbose) + + tr.waitBlocks(action.ChainA, 1, 10*time.Second) + tr.waitBlocks(action.ChainB, 1, 10*time.Second) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newConnectionCommand := tr.Target.ExecCommand("rly", "transact", "connection", pathName) + + e2e.ExecuteCommand(newConnectionCommand, "new connection", verbose) + + tr.waitBlocks(action.ChainA, 1, 10*time.Second) + tr.waitBlocks(action.ChainB, 1, 10*time.Second) +} + +type CreateIbcClientsAction struct { + ChainA ChainID + ChainB ChainID +} + +// if clients are not provided hermes will first +// create new clients and then a new connection +// otherwise, it would use client provided as CLI argument (-a-client) +func (tr Chain) createIbcClientsHermes( + action CreateIbcClientsAction, + verbose bool, +) { + cmd := tr.Target.ExecCommand("hermes", + "create", "connection", + "--a-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--b-chain", string(tr.TestConfig.ChainConfigs[action.ChainB].ChainId), + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("createIbcClientsHermes: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + +func (tr Chain) addIbcConnectionHermes( + action AddIbcConnectionAction, + verbose bool, +) { + cmd := tr.Target.ExecCommand("hermes", + "create", "connection", + "--a-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--a-client", "07-tendermint-"+fmt.Sprint(action.ClientA), + "--b-client", "07-tendermint-"+fmt.Sprint(action.ClientB), + ) + fmt.Println("running: ", cmd) + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("addIbcConnection: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + +type AddIbcChannelAction struct { + ChainA ChainID + ChainB ChainID + ConnectionA uint + PortA string + PortB string + Order string + Version string +} + +type StartRelayerAction struct{} + +func (tr Chain) startRelayer( + action StartRelayerAction, + verbose bool, +) { + if tr.TestConfig.UseGorelayer { + tr.startGorelayer(action, verbose) + } else { + tr.startHermes(action, verbose) + } +} + +func (tr Chain) startGorelayer( + action StartRelayerAction, + verbose bool, +) { + // gorelayer start is running in detached mode + cmd := tr.Target.ExecDetachedCommand("rly", "start") + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + if verbose { + fmt.Println("started gorelayer") + } +} + +func (tr Chain) startHermes( + action StartRelayerAction, + verbose bool, +) { + // hermes start is running in detached mode + cmd := tr.Target.ExecDetachedCommand("hermes", "start") + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + if verbose { + fmt.Println("started Hermes") + } +} + +func (tr Chain) addIbcChannel( + action AddIbcChannelAction, + verbose bool, +) { + if tr.TestConfig.UseGorelayer { + tr.addIbcChannelGorelayer(action, verbose) + } else { + tr.addIbcChannelHermes(action, verbose) + } +} + +func (tr Chain) addIbcChannelGorelayer( + action AddIbcChannelAction, + verbose bool, +) { + pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) + cmd := tr.Target.ExecCommand("rly", + "transact", "channel", + pathName, + "--src-port", action.PortA, + "--dst-port", action.PortB, + "--version", tr.TestConfig.ContainerConfig.CcvVersion, + "--order", action.Order, + "--debug", + ) + e2e.ExecuteCommand(cmd, "addChannel", verbose) +} + +func (tr Chain) addIbcChannelHermes( + action AddIbcChannelAction, + verbose bool, +) { + // if version is not specified, use the default version when creating ccv connections + // otherwise, use the provided version schema (usually it is ICS20-1 for IBC transfer) + chanVersion := action.Version + if chanVersion == "" { + chanVersion = tr.TestConfig.ContainerConfig.CcvVersion + } + + cmd := tr.Target.ExecCommand("hermes", + "create", "channel", + "--a-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--a-connection", "connection-"+fmt.Sprint(action.ConnectionA), + "--a-port", action.PortA, + "--b-port", action.PortB, + "--channel-version", chanVersion, + "--order", action.Order, + ) + + if verbose { + fmt.Println("addIbcChannel cmd:", cmd.String()) + } + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("addIBCChannel: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + +type TransferChannelCompleteAction struct { + ChainA ChainID + ChainB ChainID + ConnectionA uint + PortA string + PortB string + Order string + ChannelA uint + ChannelB uint +} + +func (tr Chain) transferChannelComplete( + action TransferChannelCompleteAction, + verbose bool, +) { + if tr.TestConfig.UseGorelayer { + log.Fatal("transferChannelComplete is not implemented for rly") + } + + chanOpenTryCmd := tr.Target.ExecCommand("hermes", + "tx", "chan-open-try", + "--dst-chain", string(tr.TestConfig.ChainConfigs[action.ChainB].ChainId), + "--src-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), + "--dst-port", action.PortB, + "--src-port", action.PortA, + "--src-channel", "channel-"+fmt.Sprint(action.ChannelA), + ) + e2e.ExecuteCommand(chanOpenTryCmd, "transferChanOpenTry", verbose) + + chanOpenAckCmd := tr.Target.ExecCommand("hermes", + "tx", "chan-open-ack", + "--dst-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--src-chain", string(tr.TestConfig.ChainConfigs[action.ChainB].ChainId), + "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), + "--dst-port", action.PortA, + "--src-port", action.PortB, + "--dst-channel", "channel-"+fmt.Sprint(action.ChannelA), + "--src-channel", "channel-"+fmt.Sprint(action.ChannelB), + ) + + e2e.ExecuteCommand(chanOpenAckCmd, "transferChanOpenAck", verbose) + + chanOpenConfirmCmd := tr.Target.ExecCommand("hermes", + "tx", "chan-open-confirm", + "--dst-chain", string(tr.TestConfig.ChainConfigs[action.ChainB].ChainId), + "--src-chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--dst-connection", "connection-"+fmt.Sprint(action.ConnectionA), + "--dst-port", action.PortB, + "--src-port", action.PortA, + "--dst-channel", "channel-"+fmt.Sprint(action.ChannelB), + "--src-channel", "channel-"+fmt.Sprint(action.ChannelA), + ) + e2e.ExecuteCommand(chanOpenConfirmCmd, "transferChanOpenConfirm", verbose) +} + +type RelayPacketsAction struct { + ChainA ChainID + ChainB ChainID + Port string + Channel uint +} + +func (tr Chain) relayPackets( + action RelayPacketsAction, + verbose bool, +) { + if tr.TestConfig.UseGorelayer { + tr.relayPacketsGorelayer(action, verbose) + } else { + tr.relayPacketsHermes(action, verbose) + } +} + +func (tr Chain) relayPacketsGorelayer( + action RelayPacketsAction, + verbose bool, +) { + // Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks + // before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get + // queued and are subsequently relayed. + tr.waitBlocks(action.ChainA, 3, 90*time.Second) + tr.waitBlocks(action.ChainB, 3, 90*time.Second) + + pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) + + // rly transact relay-packets [path-name] --channel [channel-id] + cmd := tr.Target.ExecCommand("rly", "transact", "flush", + pathName, + "channel-"+fmt.Sprint(action.Channel), + ) + if verbose { + log.Println("relayPackets cmd:", cmd.String()) + } + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tr.waitBlocks(action.ChainA, 1, 30*time.Second) + tr.waitBlocks(action.ChainB, 1, 30*time.Second) +} + +func (tr Chain) relayPacketsHermes( + action RelayPacketsAction, + verbose bool, +) { + // Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks + // before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get + // queued and are subsequently relayed. + tr.waitBlocks(action.ChainA, 3, 90*time.Second) + tr.waitBlocks(action.ChainB, 3, 90*time.Second) + + // hermes clear packets ibc0 transfer channel-13 + cmd := tr.Target.ExecCommand("hermes", "clear", "packets", + "--chain", string(tr.TestConfig.ChainConfigs[action.ChainA].ChainId), + "--port", action.Port, + "--channel", "channel-"+fmt.Sprint(action.Channel), + ) + if verbose { + log.Println("relayPackets cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tr.waitBlocks(action.ChainA, 1, 30*time.Second) + tr.waitBlocks(action.ChainB, 1, 30*time.Second) +} + +type RelayRewardPacketsToProviderAction struct { + ConsumerChain ChainID + ProviderChain ChainID + Port string + Channel uint +} + +func (tr Chain) relayRewardPacketsToProvider( + action RelayRewardPacketsToProviderAction, + verbose bool, +) { + blockPerDistribution, _ := strconv.ParseUint(strings.Trim(tr.Target.GetParam(action.ConsumerChain, Param{Subspace: "ccvconsumer", Key: "BlocksPerDistributionTransmission"}), "\""), 10, 64) + currentBlock := uint64(tr.Target.GetBlockHeight(action.ConsumerChain)) + if currentBlock <= blockPerDistribution { + tr.waitBlocks(action.ConsumerChain, uint(blockPerDistribution-currentBlock+1), 60*time.Second) + } + + tr.relayPackets(RelayPacketsAction{ChainA: action.ConsumerChain, ChainB: action.ProviderChain, Port: action.Port, Channel: action.Channel}, verbose) + tr.waitBlocks(action.ProviderChain, 1, 10*time.Second) +} + +func (tr Chain) DelegateTokens( + action DelegateTokensAction, + verbose bool, +) { + toValCfg := tr.TestConfig.ValidatorConfigs[action.To] + validatorAddress := toValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if toValCfg.UseConsumerKey { + validatorAddress = toValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = toValCfg.ValoperAddressOnConsumer + } + } + + cmd := tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + + "tx", "staking", "delegate", + validatorAddress, + fmt.Sprint(action.Amount)+`stake`, + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("delegate cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 10*time.Second) +} + +func (tr Chain) UnbondTokens( + action UnbondTokensAction, + verbose bool, +) { + unbondFromValCfg := tr.TestConfig.ValidatorConfigs[action.UnbondFrom] + validatorAddress := unbondFromValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if unbondFromValCfg.UseConsumerKey { + validatorAddress = unbondFromValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = unbondFromValCfg.ValoperAddressOnConsumer + } + } + + cmd := tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + + "tx", "staking", "unbond", + validatorAddress, + fmt.Sprint(action.Amount)+`stake`, + + `--from`, `validator`+fmt.Sprint(action.Sender), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.Sender), + `--node`, tr.getValidatorNode(action.Chain, action.Sender), + `--gas`, "900000", + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("unbond cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 20*time.Second) +} + +type CancelUnbondTokensAction struct { + Chain ChainID + Delegator ValidatorID + Validator ValidatorID + Amount uint +} + +func (tr Chain) cancelUnbondTokens( + action CancelUnbondTokensAction, + verbose bool, +) { + valCfg := tr.TestConfig.ValidatorConfigs[action.Validator] + delCfg := tr.TestConfig.ValidatorConfigs[action.Delegator] + validatorAddress := valCfg.ValoperAddress + delegatorAddress := delCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + validatorAddress = valCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = valCfg.ValoperAddressOnConsumer + } + if delCfg.UseConsumerKey { + delegatorAddress = delCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delegatorAddress = delCfg.DelAddressOnConsumer + } + } + + // get creation-height from state + cmd := tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "q", "staking", "unbonding-delegation", + delegatorAddress, + validatorAddress, + `--home`, tr.getValidatorHome(action.Chain, action.Delegator), + `--node`, tr.getValidatorNode(action.Chain, action.Delegator), + `-o`, `json`, + ) + if verbose { + fmt.Println("get unbonding delegations cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + creationHeight := gjson.Get(string(bz), "unbond.entries.0.creation_height").Int() + if creationHeight == 0 { + log.Fatal("invalid creation height") + } + + cmd = tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "staking", "cancel-unbond", + validatorAddress, + fmt.Sprint(action.Amount)+`stake`, + fmt.Sprint(creationHeight), + `--from`, `validator`+fmt.Sprint(action.Delegator), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.Delegator), + `--node`, tr.getValidatorNode(action.Chain, action.Delegator), + `--gas`, "900000", + `--keyring-backend`, `test`, + `-o`, `json`, + `-y`, + ) + + if verbose { + fmt.Println("unbond cmd:", cmd.String()) + } + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 20*time.Second) +} + +type RedelegateTokensAction struct { + Chain ChainID + Src ValidatorID + Dst ValidatorID + TxSender ValidatorID + Amount uint +} + +func (tr Chain) redelegateTokens(action RedelegateTokensAction, verbose bool) { + srcCfg := tr.TestConfig.ValidatorConfigs[action.Src] + dstCfg := tr.TestConfig.ValidatorConfigs[action.Dst] + redelegateSrc := srcCfg.ValoperAddress + redelegateDst := dstCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if srcCfg.UseConsumerKey { + redelegateSrc = srcCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateSrc = srcCfg.ValoperAddressOnConsumer + } + if dstCfg.UseConsumerKey { + redelegateDst = dstCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateDst = dstCfg.ValoperAddressOnConsumer + } + } + + cmd := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + + "tx", "staking", "redelegate", + redelegateSrc, + redelegateDst, + fmt.Sprint(action.Amount)+`stake`, + `--from`, `validator`+fmt.Sprint(action.TxSender), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.TxSender), + `--node`, tr.getValidatorNode(action.Chain, action.TxSender), + // Need to manually set gas limit past default (200000), since redelegate has a lot of operations + `--gas`, "900000", + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("redelegate cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 10*time.Second) +} + +type DowntimeSlashAction struct { + Chain ChainID + Validator ValidatorID +} + +// takes a string representation of the private key like +// `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}` +// and returns the value of the "address" field +func (tr Chain) getValidatorKeyAddressFromString(keystring string) string { + var key struct { + Address string `json:"address"` + } + err := json.Unmarshal([]byte(keystring), &key) + if err != nil { + log.Fatal(err) + } + return key.Address +} + +func (tr Chain) invokeDowntimeSlash(action DowntimeSlashAction, verbose bool) { + // Bring validator down + tr.setValidatorDowntime(action.Chain, action.Validator, true, verbose) + // Wait appropriate amount of blocks for validator to be slashed + tr.waitBlocks(action.Chain, 11, 3*time.Minute) + // Bring validator back up + tr.setValidatorDowntime(action.Chain, action.Validator, false, verbose) +} + +// Sets validator downtime by setting the virtual ethernet interface of a node to "up" or "down" +func (tr Chain) setValidatorDowntime(chain ChainID, validator ValidatorID, down bool, verbose bool) { + var lastArg string + if down { + lastArg = "down" + } else { + lastArg = "up" + } + + if tr.TestConfig.UseCometmock { + // send set_signing_status either to down or up for validator + validatorPrivateKeyAddress := tr.GetValidatorPrivateKeyAddress(chain, validator) + + method := "set_signing_status" + params := fmt.Sprintf(`{"private_key_address":"%s","status":"%s"}`, validatorPrivateKeyAddress, lastArg) + address := tr.Target.GetQueryNodeRPCAddress(chain) + + tr.curlJsonRPCRequest(method, params, address) + tr.waitBlocks(chain, 1, 10*time.Second) + return + } + + cmd := tr.Target.ExecCommand( + "ip", + "link", + "set", + string(chain)+"-"+string(validator)+"-out", + lastArg, + ) + + if verbose { + fmt.Println("toggle downtime cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } +} + +func (tr Chain) GetValidatorPrivateKeyAddress(chain ChainID, validator ValidatorID) string { + var validatorPrivateKeyAddress string + if chain == ChainID("provi") { + validatorPrivateKeyAddress = tr.getValidatorKeyAddressFromString(tr.TestConfig.ValidatorConfigs[validator].PrivValidatorKey) + } else { + var valAddressString string + if tr.TestConfig.ValidatorConfigs[validator].UseConsumerKey { + valAddressString = tr.TestConfig.ValidatorConfigs[validator].ConsumerPrivValidatorKey + } else { + valAddressString = tr.TestConfig.ValidatorConfigs[validator].PrivValidatorKey + } + validatorPrivateKeyAddress = tr.getValidatorKeyAddressFromString(valAddressString) + } + return validatorPrivateKeyAddress +} + +type UnjailValidatorAction struct { + Provider ChainID + Validator ValidatorID +} + +// Sends an unjail transaction to the provider chain +func (tr Chain) unjailValidator(action UnjailValidatorAction, verbose bool) { + // wait until downtime_jail_duration has elapsed, to make sure the validator can be unjailed + tr.WaitTime(61 * time.Second) + + cmd := tr.Target.ExecCommand( + tr.TestConfig.ChainConfigs[action.Provider].BinaryName, + "tx", "slashing", "unjail", + // Validator is sender here + `--from`, `validator`+fmt.Sprint(action.Validator), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Provider].ChainId), + `--home`, tr.getValidatorHome(action.Provider, action.Validator), + `--node`, tr.getValidatorNode(action.Provider, action.Validator), + `--gas`, "900000", + `--keyring-backend`, `test`, + `--keyring-dir`, tr.getValidatorHome(action.Provider, action.Validator), + `-o`, `json`, + `-y`, + ) + + if verbose { + fmt.Println("unjail cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tr.waitForTx(action.Provider, bz, time.Minute) + // wait for 1 blocks to make sure that tx got included + // in a block and packets committed before proceeding + tr.waitBlocks(action.Provider, 1, time.Minute) +} + +type RegisterRepresentativeAction struct { + Chain ChainID + Representatives []ValidatorID + Stakes []uint +} + +func (tr Chain) registerRepresentative( + action RegisterRepresentativeAction, + verbose bool, +) { + fileTempl := `{ + "pubkey": %s, + "amount": "%s", + "moniker": "%s", + "identity": "", + "website": "", + "security": "", + "details": "", + "commission-rate": "0.1", + "commission-max-rate": "0.2", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" + }` + var wg sync.WaitGroup + for i, val := range action.Representatives { + wg.Add(1) + stake := action.Stakes[i] + go func(val ValidatorID, stake uint) { + defer wg.Done() + + pubKeycmd := tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tendermint", "show-validator", + `--home`, tr.getValidatorHome(action.Chain, val), + ) + + bzPubKey, err := pubKeycmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bzPubKey)) + } + + fileContent := fmt.Sprintf(fileTempl, string(bzPubKey), fmt.Sprint(stake)+"stake", fmt.Sprint(val)) + fileName := fmt.Sprintf("%s_democracy_representative.json", val) + file, err := os.CreateTemp("", fileName) + if err != nil { + panic(fmt.Sprintf("failed writing ccv consumer file : %v", err)) + } + defer file.Close() + err = os.WriteFile(file.Name(), []byte(fileContent), 0600) + if err != nil { + log.Fatalf("Failed writing consumer genesis to file: %v", err) + } + + containerInstance := tr.TestConfig.ContainerConfig.ContainerName + targetFile := fmt.Sprintf("/tmp/%s", fileName) + sourceFile := file.Name() + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + copyCmd := exec.Command("docker", "cp", sourceFile, + fmt.Sprintf("%s:%s", containerInstance, targetFile)) + writeResult, err := copyCmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(writeResult)) + } + + cmd := tr.Target.ExecCommand(tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + "tx", "staking", "create-validator", + targetFile, + `--from`, `validator`+fmt.Sprint(val), + `--chain-id`, string(tr.TestConfig.ChainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, val), + `--node`, tr.getValidatorNode(action.Chain, val), + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("register representative cmd:", cmd.String()) + fmt.Println("Tx json:", fileContent) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 10*time.Second) + }(val, stake) + } + + wg.Wait() +} + +type SubmitChangeRewardDenomsProposalAction struct { + Denom string + Deposit uint + From ValidatorID +} + +func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { + providerChain := tr.TestConfig.ChainConfigs[ChainID("provi")] + + prop := client.ChangeRewardDenomsProposalJSON{ + Summary: "Change reward denoms", + ChangeRewardDenomsProposal: types.ChangeRewardDenomsProposal{ + Title: "Change reward denoms", + Description: "Change reward denoms", + DenomsToAdd: []string{action.Denom}, + DenomsToRemove: []string{"stake"}, + }, + Deposit: fmt.Sprint(action.Deposit) + `stake`, + } + + bz, err := json.Marshal(prop) + if err != nil { + log.Fatal(err) + } + + jsonStr := string(bz) + if strings.Contains(jsonStr, "'") { + log.Fatal("prop json contains single quote") + } + + bz, err = tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/change-reward-denoms-proposal.json")).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // CHANGE REWARDS DENOM PROPOSAL + bz, err = tr.Target.ExecCommand(providerChain.BinaryName, + "tx", "gov", "submit-legacy-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(providerChain.ChainId), + `--home`, tr.getValidatorHome(providerChain.ChainId, action.From), + `--node`, tr.getValidatorNode(providerChain.ChainId, action.From), + `--gas`, "9000000", + `--keyring-backend`, `test`, + `-y`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +// Creates an additional node on selected chain +// by copying an existing validator's home folder +// +// Steps needed to double sign: +// - copy existing validator's state and configs +// - use existing priv_validator_key.json +// - use new node_key.json (otherwise node gets rejected) +// - reset priv_validator_state.json to initial values +// - start the new node +// Double sign should be registered within couple blocks. +type DoublesignSlashAction struct { + // start another node for this Validator + Validator ValidatorID + Chain ChainID +} + +func (tr Chain) invokeDoublesignSlash( + action DoublesignSlashAction, + verbose bool, +) { + if !tr.TestConfig.UseCometmock { + chainConfig := tr.TestConfig.ChainConfigs[action.Chain] + doubleSignScript := tr.Target.GetTestScriptPath(false, "cause-doublesign.sh") + bz, err := tr.Target.ExecCommand("/bin/bash", + doubleSignScript, chainConfig.BinaryName, string(action.Validator), + string(chainConfig.ChainId), chainConfig.IpPrefix).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + tr.waitBlocks("provi", 20, 4*time.Minute) + } else { // tr.UseCometmock + validatorPrivateKeyAddress := tr.GetValidatorPrivateKeyAddress(action.Chain, action.Validator) + + method := "cause_double_sign" + params := fmt.Sprintf(`{"private_key_address":"%s"}`, validatorPrivateKeyAddress) + + address := tr.Target.GetQueryNodeRPCAddress(action.Chain) + + tr.curlJsonRPCRequest(method, params, address) + tr.waitBlocks(action.Chain, 1, 10*time.Second) + return + } +} + +// Cause light client attack evidence for a certain validator to appear on the given chain. +// The evidence will look like the validator equivocated to a light client. +// See https://github.com/cometbft/cometbft/tree/main/spec/light-client/accountability +// for more information about light client attacks. +type LightClientEquivocationAttackAction struct { + Validator ValidatorID + Chain ChainID +} + +func (tr Chain) lightClientEquivocationAttack( + action LightClientEquivocationAttackAction, + verbose bool, +) { + tr.lightClientAttack(action.Validator, action.Chain, LightClientEquivocationAttack) +} + +// Cause light client attack evidence for a certain validator to appear on the given chain. +// The evidence will look like the validator tried to perform an amnesia attack. +// See https://github.com/cometbft/cometbft/tree/main/spec/light-client/accountability +// for more information about light client attacks. +type LightClientAmnesiaAttackAction struct { + Validator ValidatorID + Chain ChainID +} + +func (tr Chain) lightClientAmnesiaAttack( + action LightClientAmnesiaAttackAction, + verbose bool, +) { + tr.lightClientAttack(action.Validator, action.Chain, LightClientAmnesiaAttack) +} + +// Cause light client attack evidence for a certain validator to appear on the given chain. +// The evidence will look like the validator tried to perform a lunatic attack. +// See https://github.com/cometbft/cometbft/tree/main/spec/light-client/accountability +// for more information about light client attacks. +type LightClientLunaticAttackAction struct { + Validator ValidatorID + Chain ChainID +} + +func (tr Chain) lightClientLunaticAttack( + action LightClientLunaticAttackAction, + verbose bool, +) { + tr.lightClientAttack(action.Validator, action.Chain, LightClientLunaticAttack) +} + +type LightClientAttackType string + +const ( + LightClientEquivocationAttack LightClientAttackType = "Equivocation" + LightClientAmnesiaAttack LightClientAttackType = "Amnesia" + LightClientLunaticAttack LightClientAttackType = "Lunatic" +) + +func (tr Chain) lightClientAttack( + validator ValidatorID, + chain ChainID, + attackType LightClientAttackType, +) { + if !tr.TestConfig.UseCometmock { + log.Fatal("light client attack is only supported with CometMock") + } + validatorPrivateKeyAddress := tr.GetValidatorPrivateKeyAddress(chain, validator) + + method := "cause_light_client_attack" + params := fmt.Sprintf(`{"private_key_address":"%s", "misbehaviour_type": "%s"}`, validatorPrivateKeyAddress, attackType) + + address := tr.Target.GetQueryNodeRPCAddress(chain) + + tr.curlJsonRPCRequest(method, params, address) + tr.waitBlocks(chain, 1, 10*time.Second) +} + +func (tr Chain) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, verbose bool) { + valCfg := tr.TestConfig.ValidatorConfigs[action.Validator] + + // 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 + 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), + action.ConsumerPubkey, + action.Validator, + tr.TestConfig.ChainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", + assignKey, + ) + + if verbose { + fmt.Println("assignConsumerPubKey cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil && !action.ExpectError { + log.Fatalf("unexpected error during key assignment - output: %s, err: %s", string(bz), err) + } + + if action.ExpectError && !tr.TestConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if err == nil || !strings.Contains(string(bz), action.ExpectedError) { + log.Fatalf("expected error not raised: expected: '%s', got '%s'", action.ExpectedError, (bz)) + } + + if verbose { + fmt.Printf("got expected error during key assignment | err: %s | output: %s \n", err, string(bz)) + } + } + + // node was started with provider key + // we swap the nodes's keys for consumer keys and restart it + if action.ReconfigureNode { + isConsumer := tr.TestConfig.ChainConfigs[action.Chain].BinaryName != "interchain-security-pd" + reconfigureScript := tr.Target.GetTestScriptPath(isConsumer, "reconfigure-node.sh") + configureNodeCmd := tr.Target.ExecCommand("/bin/bash", + reconfigureScript, tr.TestConfig.ChainConfigs[action.Chain].BinaryName, + string(action.Validator), string(action.Chain), + tr.TestConfig.ChainConfigs[action.Chain].IpPrefix, valCfg.IpSuffix, + valCfg.ConsumerMnemonic, valCfg.ConsumerPrivValidatorKey, + valCfg.ConsumerNodeKey, + ) + + if verbose { + fmt.Println("assignConsumerPubKey - reconfigure node cmd:", configureNodeCmd.String()) + } + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("assign key - reconfigure: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + // TODO: @MSalopek refactor this so test config is not changed at runtime + // make the validator use consumer key + // @POfftermatt I am currently using this for downtime slashing with cometmock + // (I need to find the currently used validator key address)Í + valCfg.UseConsumerKey = true + tr.TestConfig.ValidatorConfigs[action.Validator] = valCfg + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +// SlashMeterReplenishmentAction polls the slash meter on provider until value is achieved +type SlashMeterReplenishmentAction struct { + TargetValue int64 + // panic if timeout is exceeded + Timeout time.Duration +} + +func (tr Chain) waitForSlashMeterReplenishment( + action SlashMeterReplenishmentAction, + verbose bool, +) { + timeout := time.Now().Add(action.Timeout) + initialSlashMeter := tr.Target.GetSlashMeter() + + if initialSlashMeter >= 0 { + panic(fmt.Sprintf("No need to wait for slash meter replenishment, current value: %d", initialSlashMeter)) + } + + for { + slashMeter := tr.Target.GetSlashMeter() + if verbose { + fmt.Printf("waiting for slash meter to be replenished, current value: %d\n", slashMeter) + } + + // check if meter has reached target value + if slashMeter >= action.TargetValue { + break + } + + if time.Now().After(timeout) { + panic(fmt.Sprintf("\n\nwaitForSlashMeterReplenishment has timed out after: %s\n\n", action.Timeout)) + } + + tr.WaitTime(5 * time.Second) + } +} + +type WaitTimeAction struct { + WaitTime time.Duration +} + +func (tr Chain) waitForTime( + action WaitTimeAction, + verbose bool, +) { + tr.WaitTime(action.WaitTime) +} + +// GetPathNameForGorelayer returns the name of the path between two given chains used by Gorelayer. +// Since paths are bidirectional, we need either chain to be able to be provided as first or second argument +// and still return the same name, so we sort the chain names alphabetically. +func (tr Chain) GetPathNameForGorelayer(chainA, chainB ChainID) string { + var pathName string + if string(chainA) < string(chainB) { + pathName = string(chainA) + "-" + string(chainB) + } else { + pathName = string(chainB) + "-" + string(chainA) + } + + return pathName +} + +// Run an instance of the Hermes relayer using the "evidence" command, +// which detects evidences committed to the blocks of a consumer chain. +// Each infraction detected is reported to the provider chain using +// either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. +type StartConsumerEvidenceDetectorAction struct { + Chain ChainID +} + +func (tr Chain) startConsumerEvidenceDetector( + action StartConsumerEvidenceDetectorAction, + verbose bool, +) { + chainConfig := tr.TestConfig.ChainConfigs[action.Chain] + // run in detached mode so it will keep running in the background + bz, err := tr.Target.ExecDetachedCommand( + "hermes", "evidence", "--chain", string(chainConfig.ChainId)).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + tr.waitBlocks("provi", 10, 2*time.Minute) +} + +type OptInAction struct { + Chain ChainID + Validator ValidatorID +} + +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 + if tr.TestConfig.UseCometmock { + gas = "9000000" + } + + // Use: "opt-in [consumer-chain-id] [consumer-pubkey]", + 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), + action.Validator, + tr.TestConfig.ChainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", + optIn, + ) + + if verbose { + fmt.Println("optIn cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if !tr.TestConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if err != nil && verbose { + fmt.Printf("got error during opt in | err: %s | output: %s \n", err, string(bz)) + } + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +type OptOutAction struct { + Chain ChainID + Validator ValidatorID + ExpectError 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 + if tr.TestConfig.UseCometmock { + gas = "9000000" + } + + // Use: "opt-out [consumer-chain-id]", + 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), + action.Validator, + tr.TestConfig.ChainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", + optOut, + ) + + if verbose { + fmt.Println("optOut cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if action.ExpectError { + if err != nil { + if verbose { + fmt.Printf("got expected error during opt out | err: %s | output: %s \n", err, string(bz)) + } + } else { + log.Fatal("expected error during opt-out but got none") + } + } else { + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +type SetConsumerCommissionRateAction struct { + Chain ChainID + Validator ValidatorID + CommissionRate float64 + + // depending on the execution, this action might throw an error (e.g., when no consumer chain exists) + ExpectError bool + ExpectedError string +} + +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 + if tr.TestConfig.UseCometmock { + gas = "9000000" + } + + // 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), + action.CommissionRate, + action.Validator, + tr.TestConfig.ChainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", + setCommissionRate, + ) + + if verbose { + fmt.Println("setConsumerCommissionRate cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil && !action.ExpectError { + log.Fatalf("unexpected error during commssion rate set - output: %s, err: %s", string(bz), err) + } + + if action.ExpectError && !tr.TestConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if err == nil || !strings.Contains(string(bz), action.ExpectedError) { + log.Fatalf("expected error not raised: expected: '%s', got '%s'", action.ExpectedError, (bz)) + } + + if verbose { + fmt.Printf("got expected error during commssion rate set | err: %s | output: %s \n", err, string(bz)) + } + } + + if !tr.TestConfig.UseCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if err != nil && verbose { + fmt.Printf("got error during commssion rate set | err: %s | output: %s \n", err, string(bz)) + } + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +// WaitTime waits for the given duration. +// To make sure that the new timestamp is visible on-chain, it also waits until at least one block has been +// produced on each chain after waiting. +// The CometMock version of this takes a pointer to the TestConfig as it needs to manipulate +// information in the testrun that stores how much each chain has waited, to keep times in sync. +// Be careful that all functions calling WaitTime should therefore also take a pointer to the TestConfig. +func (tr *Chain) WaitTime(duration time.Duration) { + if !tr.TestConfig.UseCometmock { + time.Sleep(duration) + } else { + tr.TestConfig.TimeOffset += duration + for chain, running := range tr.TestConfig.RunningChains { + if !running { + continue + } + tr.AdvanceTimeForChain(chain, duration) + tr.waitBlocks(chain, 1, 2*time.Second) + } + } +} + +func (tr Chain) AdvanceTimeForChain(chain ChainID, duration time.Duration) { + // cometmock avoids sleeping, and instead advances time for all chains + method := "advance_time" + params := fmt.Sprintf(`{"duration_in_seconds": "%d"}`, int(math.Ceil(duration.Seconds()))) + + address := tr.Target.GetQueryNodeRPCAddress(chain) + + tr.curlJsonRPCRequest(method, params, address) + + // wait for 1 block of the chain to get a block with the advanced timestamp + tr.waitBlocks(chain, 1, time.Minute) +} + +func (tr Chain) getValidatorNode(chain ChainID, validator ValidatorID) string { + // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) + if tr.TestConfig.UseCometmock { + return tr.Target.GetQueryNode(chain) + } + + return "tcp://" + tr.getValidatorIP(chain, validator) + ":26658" +} + +func (tr Chain) getValidatorIP(chain ChainID, validator ValidatorID) string { + return tr.TestConfig.ChainConfigs[chain].IpPrefix + "." + tr.TestConfig.ValidatorConfigs[validator].IpSuffix +} + +func (tr Chain) getValidatorHome(chain ChainID, validator ValidatorID) string { + return `/` + string(chain) + `/validator` + fmt.Sprint(validator) +} + +func (tr Chain) curlJsonRPCRequest(method, params, address string) { + cmd_template := `curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"%s","params":%s,"id":1}' %s` + + cmd := tr.Target.ExecCommand("bash", "-c", fmt.Sprintf(cmd_template, method, params, address)) + + verbosity := false + e2e.ExecuteCommand(cmd, "curlJsonRPCRequest", verbosity) +} + +// waitForTx waits until a transaction is seen in a block or panics if a timeout occurs +func (tr Chain) waitForTx(chain ChainID, txResponse []byte, timeout time.Duration) e2e.TxResponse { + // remove any gas estimate as when command is run with gas=auto the output contains gas estimation mixed with json output + re, err := regexp.Compile("gas estimate: [0-9]+") + if err != nil { + panic(fmt.Sprintf("error compiling regexp: %s", err.Error())) + } + txResponse = re.ReplaceAll(txResponse, []byte{}) + + // check transaction + response := e2e.GetTxResponse(txResponse) + if response.Code != 0 { + log.Fatalf("sending transaction failed with error code %d, Log:'%s'", response.Code, response.RawLog) + } + + // wait for the transaction + start := time.Now() + for { + res, err := tr.Target.QueryTransaction(chain, response.TxHash) + if err == nil { + return e2e.GetTxResponse(res) + } + if time.Since(start) > timeout { + log.Printf("query transaction failed with err=%s, resp=%s", err.Error(), res) + panic(fmt.Sprintf("\n\nwaitForTx on chain '%s' has timed out after: %s\n\n", chain, timeout)) + } + } +} + +func (tr Chain) waitBlocks(chain ChainID, blocks uint, timeout time.Duration) { + if tr.TestConfig.UseCometmock { + // call advance_blocks method on cometmock + // curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"advance_blocks","params":{"num_blocks": "36000000"},"id":1}' 127.0.0.1:22331 + tcpAddress := tr.Target.GetQueryNodeRPCAddress(chain) + method := "advance_blocks" + params := fmt.Sprintf(`{"num_blocks": "%d"}`, blocks) + + tr.curlJsonRPCRequest(method, params, tcpAddress) + return + } + startBlock := tr.Target.GetBlockHeight(chain) + + start := time.Now() + for { + thisBlock := tr.Target.GetBlockHeight(chain) + if thisBlock >= startBlock+blocks { + return + } + if time.Since(start) > timeout { + panic(fmt.Sprintf("\n\n\nwaitBlocks method on chain '%s' has timed out after: %s\n\n", chain, timeout)) + } + time.Sleep(time.Second) + } +} + +func (tr Chain) waitUntilBlock(chain ChainID, block uint, timeout time.Duration) { + start := time.Now() + for { + thisBlock := tr.Target.GetBlockHeight(chain) + if thisBlock >= block { + return + } + if time.Since(start) > timeout { + panic(fmt.Sprintf("\n\n\nwaitBlocks method has timed out after: %s\n\n", timeout)) + } + time.Sleep(500 * time.Millisecond) + } +} diff --git a/tests/e2e/v4/state.go b/tests/e2e/v5/commands.go similarity index 67% rename from tests/e2e/v4/state.go rename to tests/e2e/v5/commands.go index f0ba1bca4b..a25888713f 100644 --- a/tests/e2e/v4/state.go +++ b/tests/e2e/v5/commands.go @@ -1,15 +1,20 @@ -package v4 +package v5 import ( "bufio" + "encoding/json" "fmt" "log" "os/exec" "regexp" "strconv" "strings" + "time" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + providertypes "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" + "github.com/kylelemons/godebug/pretty" "github.com/tidwall/gjson" "gopkg.in/yaml.v2" @@ -17,6 +22,8 @@ import ( gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/client" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" ) type ( @@ -42,12 +49,17 @@ type ( type State map[ChainID]ChainState type Commands struct { + Verbose bool ContainerConfig ContainerConfig // FIXME only needed for 'Now' time tracking ValidatorConfigs map[ValidatorID]ValidatorConfig ChainConfigs map[ChainID]ChainConfig Target e2e.PlatformDriver } +func (tr Commands) UseCometMock() bool { + return tr.Target.UseCometMock() +} + func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { return tr.Target.ExecCommand(name, arg...) } @@ -86,7 +98,11 @@ type ValPubKey struct { } type TmValidatorSetYaml struct { - Total string `yaml:"total"` + BlockHeight string `yaml:"block_height"` + Pagination struct { + NextKey string `yaml:"next_key"` + Total string `yaml:"total"` + } `yaml:"pagination"` Validators []struct { Address string `yaml:"address"` VotingPower string `yaml:"voting_power"` @@ -95,10 +111,10 @@ type TmValidatorSetYaml struct { } func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { - /* if *verbose { - log.Println("getting validator power for chain: ", chain, " validator: ", validator) - } - */ + if tr.Verbose { + log.Println("getting validator power for chain: ", chain, " validator: ", validator) + } + binaryName := tr.ChainConfigs[chain].BinaryName command := tr.Target.ExecCommand(binaryName, @@ -117,15 +133,15 @@ func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { log.Fatalf("yaml.Unmarshal returned an error while unmarshalling validator set: %v, input: %s", err, string(bz)) } - total, err := strconv.Atoi(valset.Total) + total, err := strconv.Atoi(valset.Pagination.Total) if err != nil { - log.Fatalf("v4: strconv.Atoi returned an error while converting total for validator set: %v, input: %s, validator set: %s, src: %s", - err, valset.Total, pretty.Sprint(valset), string(bz)) + log.Fatalf("v4: strconv.Atoi returned an error while converting total for validator set: %v,\n input: %s,\n validator set: %s,\n src: %s", + err, valset.Pagination.Total, pretty.Sprint(valset), string(bz)) } if total != len(valset.Validators) { log.Fatalf("Total number of validators %v does not match number of validators in list %v. Probably a query pagination issue. Validator set: %v", - valset.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) + valset.Pagination.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) } for _, val := range valset.Validators { @@ -233,43 +249,56 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - + cmd := tr.Target.ExecCommand(binaryName, "query", "gov", "proposal", fmt.Sprint(proposal), - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() + `-o`, `json`) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("Error getting governance proposal", proposal, "\n\t cmd: ", cmd, "\n\t err:", err, "\n\t out: ", string(bz)) + } prop := TextProposal{} - + propRaw := string(bz) if err != nil { if noProposalRegex.Match(bz) { return prop } - log.Fatal(err, "\n", string(bz)) + log.Fatal("Error parsing proposal\n", propRaw) } - propType := gjson.Get(string(bz), `messages.0.content.@type`).String() - deposit := gjson.Get(string(bz), `total_deposit.#(denom=="stake").amount`).Uint() - status := gjson.Get(string(bz), `status`).String() + // 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`) + + // for current (>= v47) prop types submitted using "tx submit-proposal" + if propType == "" { + propType = gjson.Get(propRaw, `proposal.messages.0.type`).String() + rawContent = gjson.Get(propRaw, `proposal.messages.0.value`) + } + + deposit := gjson.Get(propRaw, `proposal.total_deposit.#(denom=="stake").amount`).Uint() + status := gjson.Get(propRaw, `proposal.status`).String() + + x, err := strconv.Atoi(status) + if err != nil { + panic("error converting proposal status:" + err.Error()) + } - // This is a breaking change in the query output for proposals: bug in SDK?? - proposal_value, exists := gov.ProposalStatus_value[status] + status, exists := gov.ProposalStatus_name[int32(x)] if !exists { - panic("invalid proposal status value: " + status) + panic("invalid proposal status value:" + string(bz)) } - status = strconv.Itoa(int(proposal_value)) chainConfigs := tr.ChainConfigs containerConfig := tr.ContainerConfig switch propType { case "/cosmos.gov.v1beta1.TextProposal": - title := gjson.Get(string(bz), `content.title`).String() - description := gjson.Get(string(bz), `content.description`).String() + title := rawContent.Get("title").String() + description := rawContent.Get("description").String() return TextProposal{ Deposit: uint(deposit), @@ -278,8 +307,8 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Description: description, } case "/interchain_security.ccv.provider.v1.ConsumerAdditionProposal": - chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() - spawnTime := gjson.Get(string(bz), `messages.0.content.spawn_time`).Time().Sub(containerConfig.Now) + chainId := rawContent.Get(`chain_id`).String() + spawnTime := rawContent.Get(`spawn_time`).Time().Sub(containerConfig.Now) var chain ChainID for i, conf := range chainConfigs { @@ -295,13 +324,13 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Chain: chain, SpawnTime: int(spawnTime.Milliseconds()), InitialHeight: clienttypes.Height{ - RevisionNumber: gjson.Get(string(bz), `messages.0.content.initial_height.revision_number`).Uint(), - RevisionHeight: gjson.Get(string(bz), `messages.0.content.initial_height.revision_height`).Uint(), + RevisionNumber: rawContent.Get("initial_height.revision_number").Uint(), + RevisionHeight: rawContent.Get("initial_height.revision_height").Uint(), }, } case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": - height := gjson.Get(string(bz), `messages.0.content.plan.height`).Uint() - title := gjson.Get(string(bz), `messages.0.content.plan.name`).String() + height := rawContent.Get("plan.height").Uint() + title := rawContent.Get("plan.name").String() return UpgradeProposal{ Deposit: uint(deposit), Status: status, @@ -310,7 +339,7 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", } case "/interchain_security.ccv.provider.v1.ConsumerRemovalProposal": - chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() + chainId := rawContent.Get(`chain_id`).String() var chain ChainID for i, conf := range chainConfigs { @@ -329,13 +358,13 @@ func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { return ParamsProposal{ Deposit: uint(deposit), Status: status, - Subspace: gjson.Get(string(bz), `messages.0.content.changes.0.subspace`).String(), - Key: gjson.Get(string(bz), `messages.0.content.changes.0.key`).String(), - Value: gjson.Get(string(bz), `messages.0.content.changes.0.value`).String(), + Subspace: rawContent.Get(`changes.0.subspace`).String(), + Key: rawContent.Get(`changes.0.key`).String(), + Value: rawContent.Get(`changes.0.value`).String(), } } - log.Fatal("unknown proposal type", string(bz)) + log.Fatal("received unknown proposal type: '", propType, "', proposal JSON:", propRaw) return nil } @@ -499,17 +528,8 @@ func (tr Commands) GetPendingPacketQueueSize(chain ChainID) uint { return uint(len(packetData)) } -func (tr Commands) GetValidatorIP(chain ChainID, validator ValidatorID) string { - return tr.ChainConfigs[chain].IpPrefix + "." + tr.ValidatorConfigs[validator].IpSuffix -} - -// getQueryNode returns query node tcp address on chain. -func (tr Commands) GetQueryNode(chain ChainID) string { - return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) -} - -func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { - return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) +func (tr Commands) GetValidatorHome(chain ChainID, validator ValidatorID) string { + return `/` + string(chain) + `/validator` + fmt.Sprint(validator) } // getQueryNodeIP returns query node IP for chain, @@ -628,6 +648,109 @@ func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { return chains } +func (tr Commands) AssignConsumerPubKey(chain string, pubKey string, from ValidatorID, gas, home, node string, verbose bool) ([]byte, error) { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand( + binaryName, + "tx", "provider", "assign-consensus-key", + chain, + pubKey, + + `--from`, fmt.Sprintf("validator%s", from), + `--chain-id`, string(tr.ChainConfigs[ChainID("provi")].ChainId), + `--home`, home, + `--node`, node, + `--gas`, gas, + `--keyring-backend`, `test`, + `-y`, + `-o`, `json`, + ) + + if verbose { + fmt.Println("assignConsumerPubKey cmd:", cmd.String()) + } + + return cmd.CombinedOutput() +} + +// SubmitGovProposal sends a gov legacy-proposal transaction with given command and proposal content +func (tr Commands) SubmitGovProposal(chain ChainID, from ValidatorID, command string, proposal string, verbose bool) ([]byte, error) { + //#nosec G204 -- bypass unsafe quoting warning (no production code) + cmd := tr.Target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, proposal, "/temp-proposal.json")) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("submit legacy-proposal failed err='%s'\n%s\n", err.Error(), string(bz)) + } + + cmd = tr.Target.ExecCommand( + tr.ChainConfigs[chain].BinaryName, + "tx", "gov", "submit-legacy-proposal", command, "/temp-proposal.json", + `--from`, `validator`+fmt.Sprint(from), + `--chain-id`, string(tr.ChainConfigs[chain].ChainId), + `--home`, tr.GetValidatorHome(chain, from), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(chain, from), + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("running cmd:", cmd.String()) + fmt.Println("proposal content json:", proposal) + } + return cmd.CombinedOutput() +} + +func (tr Commands) SubmitConsumerAdditionProposal( + action e2e.SubmitConsumerAdditionProposalAction, + verbose bool, +) ([]byte, error) { + + spawnTime := tr.ContainerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) + params := ccvtypes.DefaultParams() + prop := client.ConsumerAdditionProposalJSON{ + Title: "Propose the addition of a new chain", + Summary: "Gonna be a great chain", + ChainId: string(tr.ChainConfigs[action.ConsumerChain].ChainId), + InitialHeight: action.InitialHeight, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + SpawnTime: spawnTime, + ConsumerRedistributionFraction: params.ConsumerRedistributionFraction, + BlocksPerDistributionTransmission: params.BlocksPerDistributionTransmission, + HistoricalEntries: params.HistoricalEntries, + CcvTimeoutPeriod: params.CcvTimeoutPeriod, + TransferTimeoutPeriod: params.TransferTimeoutPeriod, + UnbondingPeriod: params.UnbondingPeriod, + Deposit: fmt.Sprint(action.Deposit) + `stake`, + DistributionTransmissionChannel: action.DistributionChannel, + TopN: action.TopN, + ValidatorsPowerCap: action.ValidatorsPowerCap, + ValidatorSetCap: action.ValidatorSetCap, + Allowlist: action.Allowlist, + Denylist: action.Denylist, + MinStake: action.MinStake, + AllowInactiveVals: action.AllowInactiveVals, + } + + bz, err := json.Marshal(prop) + if err != nil { + log.Fatal(err) + } + + jsonStr := string(bz) + if strings.Contains(jsonStr, "'") { + log.Fatal("p rop json contains single quote") + } + + bz, err = tr.SubmitGovProposal(action.Chain, action.From, "consumer-addition", jsonStr, verbose) + if verbose { + fmt.Println("submitConsumerAdditionProposal output:", string(bz)) + } + return bz, err +} + // Breaking forward compatibility func (tr Commands) GetIBCTransferParams(chain ChainID) IBCTransferParams { panic("'GetIBCTransferParams' is not implemented in this version") @@ -646,3 +769,44 @@ func (tr Commands) GetInflationRate( ) float64 { panic("'GetInflationRate' is not implemented in this version") } + +func (tr Commands) CreateConsumer(providerChain, consumerChain ChainID, validator ValidatorID, metadata providertypes.ConsumerMetadata, initParams *types.ConsumerInitializationParameters, powerShapingParams *types.PowerShapingParameters) ([]byte, error) { + panic("'CreateConsumer' is not implemented in this version") +} + +func (tr Commands) UpdateConsumer(providerChain ChainID, validator ValidatorID, update providertypes.MsgUpdateConsumer, verbose bool) ([]byte, error) { + panic("'UpdateConsumer' is not implemented in this version") +} + +// QueryTransaction returns the content of the transaction or an error e.g. when a transaction coudl +func (tr Commands) QueryTransaction(chain ChainID, txhash string) ([]byte, error) { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "tx", txhash, + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + return cmd.CombinedOutput() +} + +// TODO: refactor the following APIs as they are not version dependent commands on the target +func (tr Commands) GetValidatorIP(chain ChainID, validator ValidatorID) string { + return tr.ChainConfigs[chain].IpPrefix + "." + tr.ValidatorConfigs[validator].IpSuffix +} + +// getQueryNode returns query node tcp address on chain. +func (tr Commands) GetQueryNode(chain ChainID) string { + return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) +} + +func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { + return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) +} + +func (tr Commands) getValidatorNode(chain ChainID, validator ValidatorID) string { + // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) + if tr.UseCometMock() { + return tr.GetQueryNode(chain) + } + return "tcp://" + tr.GetValidatorIP(chain, validator) + ":26658" +}