diff --git a/tests/e2e/action_rapid_test.go b/tests/e2e/action_rapid_test.go index d51cb64129..fb0dac0cf0 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -90,7 +90,7 @@ func GetActionGen() *rapid.Generator[any] { CreateLightClientEquivocationAttackActionGen().AsAny(), CreateLightClientAmnesiaAttackActionGen().AsAny(), CreateLightClientLunaticAttackActionGen().AsAny(), - GetStartConsumerEvidenceDetectorActionGen().AsAny(), + GetDetectorConsumerEvidenceActionGen().AsAny(), GetForkConsumerChainActionGen().AsAny(), GetUpdateLightClientActionGen().AsAny(), ) @@ -504,9 +504,9 @@ func GetForkConsumerChainActionGen() *rapid.Generator[ForkConsumerChainAction] { }) } -func GetStartConsumerEvidenceDetectorActionGen() *rapid.Generator[StartConsumerEvidenceDetectorAction] { - return rapid.Custom(func(t *rapid.T) StartConsumerEvidenceDetectorAction { - return StartConsumerEvidenceDetectorAction{ +func GetDetectorConsumerEvidenceActionGen() *rapid.Generator[DetectorConsumerEvidenceAction] { + return rapid.Custom(func(t *rapid.T) DetectorConsumerEvidenceAction { + return DetectorConsumerEvidenceAction{ Chain: GetChainIDGen().Draw(t, "Chain"), } }) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index ac3bc1ed7a..c13abbb0b5 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -2857,26 +2857,162 @@ func (tr Chain) GetPathNameForGorelayer(chainA, chainB ChainID) string { return pathName } -// Run an instance of the Hermes relayer using the "evidence" command, -// which detects evidences committed to the blocks of a consumer chain. +// detect evidences committed to the blocks of a consumer chain +// either by running an instance of the Hermes relayer using the "evidence" command, +// or by queyring manually the consumer chain. // Each infraction detected is reported to the provider chain using // either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. -type StartConsumerEvidenceDetectorAction struct { - Chain ChainID +type DetectorConsumerEvidenceAction struct { + Chain ChainID + Submitter ValidatorID } -func (tr Chain) startConsumerEvidenceDetector( - action StartConsumerEvidenceDetectorAction, +func (tr Chain) detectConsumerEvidence( + action DetectorConsumerEvidenceAction, + useRelayer bool, 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)) + // the Hermes doesn't support evidence handling for Permissionless ICS yet + // TODO: @Simon refactor once https://github.com/informalsystems/hermes/pull/4182 is merged. + if useRelayer { + // 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) + } else { + // detect the evidence on the consumer chain + 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) + infractionHeight := int64(0) + for { + cmd := tr.target.ExecCommand( + consumerBinaryName, + "query", "evidence", "list", + `--node`, tr.target.GetQueryNode(action.Chain), + `-o`, `json`, + ) + + if verbose { + fmt.Println("query evidence cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err == nil { + evidence := gjson.Get(string(bz), "evidence") + // we only expect only one evidence + if len(evidence.Array()) == 1 { + infractionHeight = evidence.Array()[0].Get("value.height").Int() + break + } + } + + if err != nil || time.Now().After(timeout) { + log.Print("Failed running command: ", cmd) + log.Fatal(err, "\n", string(bz)) + } + time.Sleep(2 * time.Second) + } + + // get the evidence data from the block + // note that the evidence is added to the next block after the infraction height + cmd := tr.target.ExecCommand( + consumerBinaryName, + "query", "block", "--type=height", strconv.Itoa(int(infractionHeight+1)), + `--node`, tr.target.GetQueryNode(action.Chain), + `-o`, `json`, + ) + + if verbose { + fmt.Println("query block for evidence cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + evidence := gjson.Get(string(bz), "evidence.evidence").Array() + if len(evidence) == 0 { + log.Fatal("expected at least one evidence in block but found zero") + } + + if equivocation := evidence[0].Get("duplicate_vote_evidence"); equivocation.String() != "" { + // persist evidence in the json format + evidenceJson := equivocation.Raw + evidencePath := "/temp-evidence.json" + bz, err = tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, evidenceJson, evidencePath), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // query IBC header at the infraction height + cmd = tr.target.ExecCommand( + consumerBinaryName, + "query", "ibc", "client", "header", "--height", strconv.Itoa(int(infractionHeight)), + `--node`, tr.target.GetQueryNode(action.Chain), + `-o`, `json`, + ) + + if verbose { + fmt.Println("query IBC header cmd:", cmd.String()) + } + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // persist IBC header in json format + headerPath := "/temp-header.json" + bz, err = tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, string(bz), headerPath), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // submit consumer equivocation to provider + 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), + evidencePath, + headerPath, + action.Submitter, + tr.testConfig.chainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Submitter), + tr.getValidatorNode(ChainID("provi"), action.Submitter), + gas, + ) + + cmd = tr.target.ExecCommand( + "/bin/bash", "-c", + submitEquivocation, + ) + + if verbose { + fmt.Println("submit consumer equivocation cmd:", cmd.String()) + } + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + } else { + log.Fatal("invalid evidence type", evidence[0].String()) + } + + tr.waitBlocks("provi", 3, 1*time.Minute) } - tr.waitBlocks("provi", 10, 2*time.Minute) } type OptInAction struct { diff --git a/tests/e2e/json_utils.go b/tests/e2e/json_utils.go index 4d7d2bbee7..e2c98cf885 100644 --- a/tests/e2e/json_utils.go +++ b/tests/e2e/json_utils.go @@ -264,8 +264,8 @@ func UnmarshalMapToActionType(rawAction json.RawMessage, actionTypeString string if err == nil { return a, nil } - case "main.StartConsumerEvidenceDetectorAction": - var a StartConsumerEvidenceDetectorAction + case "main.DetectorConsumerEvidenceAction": + var a DetectorConsumerEvidenceAction err := json.Unmarshal(rawAction, &a) if err == nil { return a, nil diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 0374e60256..3dc10d7de4 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -219,7 +219,7 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { // run Hermes relayer instance to detect the ICS misbehaviour // and jail alice on the provider { - Action: StartConsumerEvidenceDetectorAction{ + Action: DetectorConsumerEvidenceAction{ Chain: ChainID(consumerName), }, State: State{ diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index bdc4a68cb4..779a2b56ea 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -88,8 +88,9 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { // detect the double voting infraction // and jail and slashing of bob on the provider { - Action: StartConsumerEvidenceDetectorAction{ - Chain: ChainID(consumerName), + Action: DetectorConsumerEvidenceAction{ + Chain: ChainID(consumerName), + Submitter: ValidatorID("bob"), }, State: State{ ChainID(providerName): ChainState{ diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index 87cc66647c..c546649fc3 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -377,9 +377,9 @@ func (td *DefaultDriver) runAction(action interface{}) error { case UpdateLightClientAction: target := td.getTargetDriver("") target.updateLightClient(action, td.verbose) - case StartConsumerEvidenceDetectorAction: + case DetectorConsumerEvidenceAction: target := td.getTargetDriver("") - target.startConsumerEvidenceDetector(action, td.verbose) + target.detectConsumerEvidence(action, false, td.verbose) case SubmitChangeRewardDenomsProposalAction: target := td.getTargetDriver(action.Chain) version := target.testConfig.providerVersion diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 2bd46f5cd3..c3153ee0fd 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -170,24 +170,23 @@ Example: txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) submitter := clientCtx.GetFromAddress() + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) - ev := tmproto.DuplicateVoteEvidence{} - evidenceJson, err := os.ReadFile(args[0]) + evidenceJson, err := os.ReadFile(args[1]) if err != nil { return err } - if err := json.Unmarshal(evidenceJson, &ev); err != nil { + ev := tmproto.DuplicateVoteEvidence{} + if err := cdc.UnmarshalJSON(evidenceJson, &ev); err != nil { return fmt.Errorf("duplicate vote evidence unmarshalling failed: %s", err) } - headerJson, err := os.ReadFile(args[1]) + headerJson, err := os.ReadFile(args[2]) if err != nil { return err } - cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) - header := ibctmtypes.Header{} if err := cdc.UnmarshalJSON(headerJson, &header); err != nil { return fmt.Errorf("infraction IBC header unmarshalling failed: %s", err)